VOR single channel: initial commit

This commit is contained in:
f4exb 2020-11-28 04:10:27 +01:00
parent 366dd13868
commit 97154a3896
31 changed files with 5315 additions and 0 deletions

View File

@ -3,6 +3,7 @@ project(demod)
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)
add_subdirectory(demodadsb)
add_subdirectory(demodvor)
add_subdirectory(demodvorsc)
endif()
add_subdirectory(demodam)
add_subdirectory(demodbfm)

View File

@ -0,0 +1,65 @@
project(vorsc)
set(vorsc_SOURCES
vordemodsc.cpp
vordemodscsettings.cpp
vordemodscbaseband.cpp
vordemodscsink.cpp
vordemodscplugin.cpp
vordemodscwebapiadapter.cpp
vordemodscreport.cpp
)
set(vorsc_HEADERS
vordemodsc.h
vordemodscsettings.h
vordemodscbaseband.h
vordemodscsink.h
vordemodscplugin.h
vordemodscwebapiadapter.h
vordemodscreport.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(vorsc_SOURCES
${vorsc_SOURCES}
vordemodscgui.cpp
vordemodscgui.ui
map.qrc
icons.qrc
)
set(vorsc_HEADERS
${vorsc_HEADERS}
vordemodscgui.h
navaid.h
../demodadsb/csv.h
)
set(TARGET_NAME demodvorsc)
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demodvorscsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${vorsc_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/demodvor/">
<file>icons/compass.png</file>
<file>icons/vor.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

View File

@ -0,0 +1,10 @@
<RCC>
<qresource prefix="/demodvor/">
<file>map/map.qml</file>
<file>map/MapStation.qml</file>
<file>map/antenna.png</file>
<file>map/VOR.png</file>
<file>map/VOR-DME.png</file>
<file>map/VORTAC.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,40 @@
import QtQuick 2.12
import QtLocation 5.12
import QtPositioning 5.12
MapQuickItem {
id: station
property string stationName // Name of the station, E.g. Home
coordinate: QtPositioning.coordinate(51.5, 0.125) // Location of the antenna (QTH) - London
zoomLevel: 11
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
sourceItem: Grid {
columns: 1
Grid {
horizontalItemAlignment: Grid.AlignHCenter
layer.enabled: true
layer.smooth: true
Image {
id: image
source: "antenna.png"
}
Rectangle {
id: bubble
color: "lightblue"
border.width: 1
width: text.width * 1.3
height: text.height * 1.3
radius: 5
Text {
id: text
anchors.centerIn: parent
text: stationName
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,115 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtLocation 5.12
import QtPositioning 5.12
Item {
id: qmlMap
property int vorZoomLevel: 11
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
objectName: "map"
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(51.5, 0.125) // London
zoomLevel: 10
MapItemView {
model: vorModel
delegate: vorRadialComponent
}
MapStation {
id: station
objectName: "station"
stationName: "Home"
coordinate: QtPositioning.coordinate(51.5, 0.125)
}
MapItemView {
model: vorModel
delegate: vorComponent
}
onZoomLevelChanged: {
if (zoomLevel > 11) {
station.zoomLevel = zoomLevel
vorZoomLevel = zoomLevel
} else {
station.zoomLevel = 11
vorZoomLevel = 11
}
}
}
Component {
id: vorRadialComponent
MapPolyline {
line.width: 2
line.color: 'gray'
path: vorRadial
}
}
Component {
id: vorComponent
MapQuickItem {
id: vor
anchorPoint.x: image.width/2
anchorPoint.y: bubble.height/2
coordinate: position
zoomLevel: vorZoomLevel
sourceItem: Grid {
columns: 1
Grid {
horizontalItemAlignment: Grid.AlignHCenter
verticalItemAlignment: Grid.AlignVCenter
columnSpacing: 5
layer.enabled: true
layer.smooth: true
Image {
id: image
source: vorImage
MouseArea {
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: (mouse) => {
selected = !selected
}
}
}
Rectangle {
id: bubble
color: bubbleColour
border.width: 1
width: text.width + 5
height: text.height + 5
radius: 5
Text {
id: text
anchors.centerIn: parent
text: vorData
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onDoubleClicked: (mouse) => {
selected = !selected
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,368 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_NAVAID_H
#define INCLUDE_NAVAID_H
#include <QString>
#include <QFile>
#include <QByteArray>
#include <QHash>
#include <QList>
#include <QDebug>
#include <QXmlStreamReader>
#include <stdio.h>
#include <string.h>
#include "util/units.h"
#include "../demodadsb/csv.h"
#define OURAIRPORTS_NAVAIDS_URL "https://ourairports.com/data/navaids.csv"
#define OPENAIP_NAVAIDS_URL "https://www.openaip.net/customer_export_akfshb9237tgwiuvb4tgiwbf/%1_nav.aip"
struct NavAid {
int m_id;
QString m_ident; // 2 or 3 character ident
QString m_type; // VOR, VOR-DME or VORTAC
QString m_name;
float m_latitude;
float m_longitude;
float m_elevation;
int m_frequencykHz;
QString m_channel;
int m_range; // Nautical miles
float m_magneticDeclination;
bool m_alignedTrueNorth; // Is the VOR aligned to true North, rather than magnetic (may be the case at high latitudes)
static QString trimQuotes(const QString s)
{
if (s.startsWith('\"') && s.endsWith('\"'))
return s.mid(1, s.size() - 2);
else
return s;
}
int getRangeMetres()
{
return Units::nauticalMilesToIntegerMetres((float)m_range);
}
// OpenAIP XML file
static void readNavAidsXML(QHash<int, NavAid *> *navAidInfo, const QString &filename)
{
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QXmlStreamReader xmlReader(&file);
while(!xmlReader.atEnd() && !xmlReader.hasError())
{
if (xmlReader.readNextStartElement())
{
if (xmlReader.name() == "NAVAID")
{
QStringRef typeRef = xmlReader.attributes().value("TYPE");
if ((typeRef == QLatin1String("VOR"))
|| (typeRef== QLatin1String("VOR-DME"))
|| (typeRef == QLatin1String("VORTAC")))
{
QString type = typeRef.toString();
int identifier = 0;
QString name;
QString id;
float lat = 0.0f;
float lon = 0.0f;
float elevation = 0.0f;
int frequency = 0;
QString channel;
int range = 25;
float declination = 0.0f;
bool alignedTrueNorth = false;
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("IDENTIFIER"))
identifier = xmlReader.readElementText().toInt();
else if (xmlReader.name() == QLatin1String("NAME"))
name = xmlReader.readElementText();
else if (xmlReader.name() == QLatin1String("ID"))
id = xmlReader.readElementText();
else if (xmlReader.name() == QLatin1String("GEOLOCATION"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("LAT"))
lat = xmlReader.readElementText().toFloat();
else if (xmlReader.name() == QLatin1String("LON"))
lon = xmlReader.readElementText().toFloat();
else if (xmlReader.name() == QLatin1String("ELEV"))
elevation = xmlReader.readElementText().toFloat();
else
xmlReader.skipCurrentElement();
}
}
else if (xmlReader.name() == QLatin1String("RADIO"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("FREQUENCY"))
frequency = (int)(xmlReader.readElementText().toFloat() * 1000);
else if (xmlReader.name() == QLatin1String("CHANNEL"))
channel = xmlReader.readElementText();
else
xmlReader.skipCurrentElement();
}
}
else if (xmlReader.name() == QLatin1String("PARAMS"))
{
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("RANGE"))
range = xmlReader.readElementText().toInt();
else if (xmlReader.name() == QLatin1String("DECLINATION"))
declination = xmlReader.readElementText().toFloat();
else if (xmlReader.name() == QLatin1String("ALIGNEDTOTRUENORTH"))
alignedTrueNorth = xmlReader.readElementText() == "TRUE";
else
xmlReader.skipCurrentElement();
}
}
else
xmlReader.skipCurrentElement();
}
NavAid *vor = new NavAid();
vor->m_id = identifier;
vor->m_ident = id;
// Check idents conform to our filtering rules
if (vor->m_ident.size() < 2)
qDebug() << "Warning: VOR Ident less than 2 characters: " << vor->m_ident;
else if (vor->m_ident.size() > 3)
qDebug() << "Warning: VOR Ident greater than 3 characters: " << vor->m_ident;
vor->m_type = type;
vor->m_name = name;
vor->m_frequencykHz = frequency;
vor->m_channel = channel;
vor->m_latitude = lat;
vor->m_longitude = lon;
vor->m_elevation = elevation;
vor->m_range = range;
vor->m_magneticDeclination = declination;
vor->m_alignedTrueNorth = alignedTrueNorth;
navAidInfo->insert(identifier, vor);
}
}
}
}
file.close();
}
else
qDebug() << "NavAid::readNavAidsXML: Could not open " << filename << " for reading.";
}
// Read OurAirport's NavAids CSV file
// See comments for readOSNDB
static QHash<int, NavAid *> *readNavAidsDB(const QString &filename)
{
int cnt = 0;
QHash<int, NavAid *> *navAidInfo = nullptr;
// Column numbers used for the data as of 2020/10/28
int idCol = 0;
int identCol = 2;
int typeCol = 4;
int nameCol = 3;
int frequencyCol = 5;
int latitudeCol = 6;
int longitudeCol = 7;
int elevationCol = 8;
int powerCol = 18;
qDebug() << "NavAid::readNavAidsDB: " << filename;
FILE *file;
QByteArray utfFilename = filename.toUtf8();
if ((file = fopen(utfFilename.constData(), "r")) != NULL)
{
char row[2048];
if (fgets(row, sizeof(row), file))
{
navAidInfo = new QHash<int, NavAid *>();
navAidInfo->reserve(15000);
// Read header
int idx = 0;
char *p = strtok(row, ",");
while (p != NULL)
{
if (!strcmp(p, "id"))
idCol = idx;
else if (!strcmp(p, "ident"))
identCol = idx;
else if (!strcmp(p, "type"))
typeCol = idx;
else if (!strcmp(p, "name"))
nameCol = idx;
else if (!strcmp(p, "frequency_khz"))
frequencyCol = idx;
else if (!strcmp(p, "latitude_deg"))
latitudeCol = idx;
else if (!strcmp(p, "longitude_deg"))
longitudeCol = idx;
else if (!strcmp(p, "elevation_ft"))
elevationCol = idx;
else if (!strcmp(p, "power"))
powerCol = idx;
p = strtok(NULL, ",");
idx++;
}
// Read data
while (fgets(row, sizeof(row), file))
{
int id = 0;
char *idString = NULL;
char *ident = NULL;
size_t identLen = 0;
char *type = NULL;
size_t typeLen = 0;
char *name = NULL;
size_t nameLen = 0;
char *frequencyString = NULL;
int frequency;
float latitude = 0.0f;
char *latitudeString = NULL;
size_t latitudeLen = 0;
float longitude = 0.0f;
char *longitudeString = NULL;
size_t longitudeLen = 0;
float elevation = 0.0f;
char *elevationString = NULL;
size_t elevationLen = 0;
char *power = NULL;
size_t powerLen = 0;
char *q = row;
idx = 0;
while ((p = csvNext(&q)) != nullptr)
{
// Read strings, stripping quotes
if (idx == idCol)
{
idString = p;
idString[strlen(idString)] = '\0';
id = strtol(idString, NULL, 10);
}
else if ((idx == identCol) && (p[0] == '\"'))
{
ident = p+1;
identLen = strlen(ident)-1;
ident[identLen] = '\0';
}
else if ((idx == typeCol) && (p[0] == '\"'))
{
type = p+1;
typeLen = strlen(type)-1;
type[typeLen] = '\0';
}
else if ((idx == nameCol) && (p[0] == '\"'))
{
name = p+1;
nameLen = strlen(name)-1;
name[nameLen] = '\0';
}
if (idx == frequencyCol)
{
frequencyString = p;
frequencyString[strlen(frequencyString)] = '\0';
frequency = strtol(frequencyString, NULL, 10);
}
else if (idx == latitudeCol)
{
latitudeString = p;
latitudeLen = strlen(latitudeString)-1;
latitudeString[latitudeLen] = '\0';
latitude = atof(latitudeString);
}
else if (idx == longitudeCol)
{
longitudeString = p;
longitudeLen = strlen(longitudeString)-1;
longitudeString[longitudeLen] = '\0';
longitude = atof(longitudeString);
}
else if (idx == elevationCol)
{
elevationString = p;
elevationLen = strlen(elevationString)-1;
elevationString[elevationLen] = '\0';
elevation = atof(elevationString);
}
else if ((idx == powerCol) && (p[0] == '\"'))
{
power = p+1;
powerLen = strlen(power)-1;
power[powerLen] = '\0';
}
idx++;
}
// For now, we only want VORs
if (type && !strncmp(type, "VOR", 3))
{
NavAid *vor = new NavAid();
vor->m_id = id;
vor->m_ident = QString(ident);
// Check idents conform to our filtering rules
if (vor->m_ident.size() < 2)
qDebug() << "Warning: VOR Ident less than 2 characters: " << vor->m_ident;
else if (vor->m_ident.size() > 3)
qDebug() << "Warning: VOR Ident greater than 3 characters: " << vor->m_ident;
vor->m_type = QString(type);
vor->m_name = QString(name);
vor->m_frequencykHz = frequency;
vor->m_latitude = latitude;
vor->m_longitude = longitude;
vor->m_elevation = elevation;
if (power && !strcmp(power, "HIGH"))
vor->m_range = 100;
else if (power && !strcmp(power, "MEDIUM"))
vor->m_range = 40;
else
vor->m_range = 25;
vor->m_magneticDeclination = 0.0f;
vor->m_alignedTrueNorth = false;
navAidInfo->insert(id, vor);
cnt++;
}
}
}
fclose(file);
}
else
qDebug() << "NavAid::readNavAidsDB: Failed to open " << filename;
qDebug() << "NavAid::readNavAidsDB: Read " << cnt << " VORs";
return navAidInfo;
}
};
#endif // INCLUDE_NAVAID_H

View File

@ -0,0 +1,89 @@
<h1>VOR demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate VOR (VHF omnidirectional range) navaids (navigation aids). VORs are radio naviation aids in the VHF 108 - 117.975MHz band commonly used for aircraft navigation.
VORs transmit two 30Hz signals, one AM at the VOR center frequency and one FM on a 9960Hz sub-carrier. The FM reference signal's phase is set so 0 degrees corresponds to magnetic north from the VOR (Some VORs at high latitudes use true North). The phase of the AM variable signal is such that the phase difference to the reference signal corresponds to the bearing from the VOR to the receiver. For example, if a receiver is North from the VOR, the AM and FM 30Hz signals will be received in phase. If a receiver is East from the VOR, the phase difference will be 90 degrees.
VORs also transmit a Morse code ident signal at a 1020Hz offset. This is a 2 or 3 character identifier used to identify the VOR, as multiple VORs can be transmitted on the same frequency. For example, the VOR at London Heathrow transmits .-.. --- -. for LON. The Morse code ident is typically transmitted at 10 seconds intervals at between 7 and 10 wpm. VORs that are under maintainance may transmit TST.
Some VORs also transmit an AM voice identification or information signal between 300-3kHz.
This plugin can demodulate all four signals from multiple VORs simultaneously, allowing your position to be determined and plotted on a map. It can also demodulate the Morse code ident signal and and check they are correct for each VOR. The Morse code ident and any voice signal will also be heard as audio.
Note that for aircraft, there is typically a direct line-of-sight to the VOR. This is unlikely to be the case when using an SDR on the ground. To get good results, ideally you want to be on a nice high hill or close to the VOR.
<h2>Interface</h2>
![VOR Demodulator plugin GUI](../../../doc/img/VORDemod_plugin.png)
<h3>1: Level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Audio mute and audio output select</h3>
Left click on this button to toggle audio mute for this channel. The button will light up in green if the squelch is open. This helps identifying which channels are active in a multi-channel configuration.
If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details.
<h3>4: Download VOR Database</h3>
Pressing this button downloads the OpenAIP.net Navaid database, which contains the details (position, frequencies, name and ident) for each VOR. This needs to be performed at least once.
<h3>5: Draw Radials Adjusted for Magnetic Declination</h3>
When checked, radials on the map will drawn adjusted for magnetic declination. For example, if a VOR has a magnetic declination of 5 degrees, and the radial is calculated at 0 degrees, the radial will be drawn to magnetic North, i.e. -5 degress from true North. If not checked, the same radial would be drawn to true North (i.e 0 degrees), which may result in a less accurate position estimate.
<h3>6: Morse ident threshold</h3>
This is the Morse code ident threshold, expressed as a linear signal to noise (SNR) ratio. This is effectively the signal level required for the Morse demodulator to detect a dot or dash. Setting this to low values will allow the Morse demodulator to detect weak signals, but it also increases the likelyhood that noise will incorrectly be interpreted as a signal, resulting in invalid idents being reported.
<h3>7: Squelch threshold</h3>
This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button.
<h3>8: Volume</h3>
This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button.
<h3>VOR Table</h3>
The VOR table displays information about selected VORs. To select or deselect a VOR, double click it on the map. The information displayed includes:
![VOR Demodulator Table](../../../doc/img/VORDemod_plugin_table.png)
* Name - The name of the VOR. For example: 'LONDON'.
* Freq (MHz) - The center frequency the VOR transmits on in MHz.
* Offset (kHz) - This is the current difference between the VOR's center frequency and SDRangle's device center frequency. If displayed in red, the VOR is out of range and it's signal will not be able to be received.
* Ident - A 2 or 3 character identifier for the VOR. For example: 'LON'.
* Morse - The Morse code identifier for the VOR. For example: '.-.. --- -.'
* RX Ident - This contains the demodulated ident. If it matches the expected ident, it will be displayed in green, if not, it will be displayed in red. If an ident is received that is not 2 or 3 characters, it will not be displayed, but the last received ident will be displayed in yellow.
* RX Morse - This contains the demodulated Morse code ident. Colour coding is as for RX Ident.
* Radial - This contains the demodulated radial direction in degrees (unadjusted for magnetic declination). If there is a low confidence the value is correct (due to a weak signal), it will be displayed in red.
* Ref (dB) - This displays the magnitude of the received 30Hz FM reference signal in dB.
* Var (dB) - This displays the mangitude of the received 30Hz AM variable signal in dB.
* Mute - This button allows you to mute or unmute the audio from the corresponding VOR.
<h3>Map</h3>
The map displays the locations of each VOR, with an information box containing the information about the VOR, such as it's name, frequency, ident (in text and Morse), range and magnetic declination.
To initialise the VORs on the map, first set your position using the Preferences > My position menu, then open the VOR Demodulator channel (close and reopen it, if already open). Then press the Download VOR Database button (This only needs to be performed once). The map should then display VORs in your vicinity.
Double clicking on a VOR will select and add it to the list of VORs to demodulate. It will be added to the VOR table and will be highlighted green. Double clicking a selected VOR, will remove it from the list of VORs to demodulate and it will be removed from the VOR table.
When a signal from a VOR is correctly being demodulated, a radial line will be drawn on the map, at the angle corresponding to the phase difference between the AM and FM 30Hz signals. Your receiver should be somewhere along this radial line. The length of the radial line is set according to the range of the VOR as recorded in the database, which is valid for aircraft at altitude. Range on the ground will be considerably less. An approximate position for the receiver is where the radial lines from two or more VORs intersect.
![VOR Demodulator Map](../../../doc/img/VORDemod_plugin_map.png)
<h2>Attribution</h2>
Icons by Denelson83 and mamayer, via Wikimedia Commons and RULI from the Noun Project https://thenounproject.com/

View File

@ -0,0 +1,506 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "vordemodsc.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGVORDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGVORDemodReport.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(VORDemodSC::MsgConfigureVORDemod, Message)
const char * const VORDemodSC::m_channelIdURI = "sdrangel.channel.vordemodsc";
const char * const VORDemodSC::m_channelId = "VORDemodSC";
VORDemodSC::VORDemodSC(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new VORDemodSCBaseband();
m_basebandSink->moveToThread(&m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
VORDemodSC::~VORDemodSC()
{
qDebug("VORDemodSC::~VORDemodSC");
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_basebandSink->isRunning()) {
stop();
}
delete m_basebandSink;
}
uint32_t VORDemodSC::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void VORDemodSC::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void VORDemodSC::start()
{
qDebug("VORDemodSC::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
VORDemodSCBaseband::MsgConfigureVORDemodBaseband *msg = VORDemodSCBaseband::MsgConfigureVORDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void VORDemodSC::stop()
{
qDebug("VORDemodSC::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool VORDemodSC::handleMessage(const Message& cmd)
{
if (MsgConfigureVORDemod::match(cmd))
{
MsgConfigureVORDemod& cfg = (MsgConfigureVORDemod&) cmd;
qDebug() << "VORDemodSC::handleMessage: MsgConfigureVORDemod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "VORDemodSC::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (m_guiMessageQueue)
{
rep = new DSPSignalNotification(notif);
m_guiMessageQueue->push(rep);
}
return true;
}
else
{
return false;
}
}
void VORDemodSC::applySettings(const VORDemodSCSettings& settings, bool force)
{
qDebug() << "VORDemodSC::applySettings:"
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_squelch != settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((m_settings.m_audioMute != settings.m_audioMute) || force) {
reverseAPIKeys.append("audioMute");
}
if ((m_settings.m_volume != settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
if ((m_settings.m_identThreshold != settings.m_identThreshold) || force) {
reverseAPIKeys.append("identThreshold");
}
if ((m_settings.m_magDecAdjust != settings.m_magDecAdjust) || force) {
reverseAPIKeys.append("magDecAdjust");
}
VORDemodSCBaseband::MsgConfigureVORDemodBaseband *msg = VORDemodSCBaseband::MsgConfigureVORDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
if (m_featuresSettingsFeedback.size() > 0) {
featuresSendSettings(reverseAPIKeys, settings, force);
}
m_settings = settings;
}
QByteArray VORDemodSC::serialize() const
{
return m_settings.serialize();
}
bool VORDemodSC::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureVORDemod *msg = MsgConfigureVORDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureVORDemod *msg = MsgConfigureVORDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int VORDemodSC::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setVorDemodSettings(new SWGSDRangel::SWGVORDemodSettings());
response.getVorDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int VORDemodSC::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
VORDemodSCSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureVORDemod *msg = MsgConfigureVORDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("VORDemodSC::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureVORDemod *msgToGUI = MsgConfigureVORDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void VORDemodSC::webapiUpdateChannelSettings(
VORDemodSCSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("audioMute")) {
settings.m_audioMute = response.getVorDemodSettings()->getAudioMute() != 0;
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getVorDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("squelch")) {
settings.m_squelch = response.getVorDemodSettings()->getSquelch();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getVorDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("volume")) {
settings.m_volume = response.getVorDemodSettings()->getVolume();
}
if (channelSettingsKeys.contains("audioDeviceName")) {
settings.m_audioDeviceName = *response.getVorDemodSettings()->getAudioDeviceName();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getVorDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getVorDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getVorDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getVorDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getVorDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getVorDemodSettings()->getReverseApiChannelIndex();
}
if (channelSettingsKeys.contains("identThreshold")) {
settings.m_identThreshold = response.getVorDemodSettings()->getIdentThreshold();
}
if (channelSettingsKeys.contains("magDecAdjust")) {
settings.m_magDecAdjust = response.getVorDemodSettings()->getMagDecAdjust() != 0;
}
}
int VORDemodSC::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setVorDemodReport(new SWGSDRangel::SWGVORDemodReport());
response.getVorDemodReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void VORDemodSC::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const VORDemodSCSettings& settings)
{
response.getVorDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getVorDemodSettings()->setRgbColor(settings.m_rgbColor);
response.getVorDemodSettings()->setSquelch(settings.m_squelch);
response.getVorDemodSettings()->setVolume(settings.m_volume);
if (response.getVorDemodSettings()->getTitle()) {
*response.getVorDemodSettings()->getTitle() = settings.m_title;
} else {
response.getVorDemodSettings()->setTitle(new QString(settings.m_title));
}
if (response.getVorDemodSettings()->getAudioDeviceName()) {
*response.getVorDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
} else {
response.getVorDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
response.getVorDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getVorDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getVorDemodSettings()->getReverseApiAddress()) {
*response.getVorDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getVorDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getVorDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getVorDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getVorDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
response.getVorDemodSettings()->setIdentThreshold(settings.m_identThreshold);
response.getVorDemodSettings()->setMagDecAdjust(settings.m_magDecAdjust ? 1 : 0);
}
void VORDemodSC::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
response.getVorDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
response.getVorDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0);
response.getVorDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
}
void VORDemodSC::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const VORDemodSCSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void VORDemodSC::featuresSendSettings(QList<QString>& channelSettingsKeys, const VORDemodSCSettings& settings, bool force)
{
QList<Feature*>::iterator it = m_featuresSettingsFeedback.begin();
MainCore *mainCore = MainCore::instance();
for (; it != m_featuresSettingsFeedback.end(); ++it)
{
if (mainCore->existsFeature(*it))
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
Feature::MsgChannelSettings *msg = Feature::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
(*it)->getInputMessageQueue()->push(msg);
}
else
{
m_featuresSettingsFeedback.removeOne(*it);
}
}
}
void VORDemodSC::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const VORDemodSCSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("VORDemodSC"));
swgChannelSettings->setVorDemodSettings(new SWGSDRangel::SWGVORDemodSettings());
SWGSDRangel::SWGVORDemodSettings *swgVORDemodSettings = swgChannelSettings->getVorDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("audioMute") || force) {
swgVORDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgVORDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("squelch") || force) {
swgVORDemodSettings->setSquelch(settings.m_squelch);
}
if (channelSettingsKeys.contains("title") || force) {
swgVORDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("volume") || force) {
swgVORDemodSettings->setVolume(settings.m_volume);
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgVORDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgVORDemodSettings->setStreamIndex(settings.m_streamIndex);
}
if (channelSettingsKeys.contains("identThreshold") || force) {
swgVORDemodSettings->setAudioMute(settings.m_identThreshold);
}
if (channelSettingsKeys.contains("magDecAdjust") || force) {
swgVORDemodSettings->setAudioMute(settings.m_magDecAdjust ? 1 : 0);
}
}
void VORDemodSC::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "VORDemodSC::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("VORDemodSC::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,160 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSC_H
#define INCLUDE_VORDEMODSC_H
#include <vector>
#include <QNetworkRequest>
#include <QThread>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "vordemodscbaseband.h"
#include "vordemodscsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class VORDemodSC : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureVORDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const VORDemodSCSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureVORDemod* create(const VORDemodSCSettings& settings, bool force)
{
return new MsgConfigureVORDemod(settings, force);
}
private:
VORDemodSCSettings m_settings;
bool m_force;
MsgConfigureVORDemod(const VORDemodSCSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
VORDemodSC(DeviceAPI *deviceAPI);
virtual ~VORDemodSC();
virtual void destroy() { delete this; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return 0; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const VORDemodSCSettings& settings);
static void webapiUpdateChannelSettings(
VORDemodSCSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); }
double getMagSq() const { return m_basebandSink->getMagSq(); }
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
void setMessageQueueToGUI(MessageQueue* queue) override {
BasebandSampleSink::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
VORDemodSCBaseband* m_basebandSink;
VORDemodSCSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const VORDemodSCSettings& settings, bool force = false);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const VORDemodSCSettings& settings, bool force);
void featuresSendSettings(QList<QString>& channelSettingsKeys, const VORDemodSCSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const VORDemodSCSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_VORDEMODSC_H

View File

@ -0,0 +1,282 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "vordemodscbaseband.h"
#include "vordemodscreport.h"
MESSAGE_CLASS_DEFINITION(VORDemodSCBaseband::MsgConfigureVORDemodBaseband, Message)
VORDemodSCBaseband::VORDemodSCBaseband() :
m_running(false),
m_mutex(QMutex::Recursive),
m_messageQueueToGUI(nullptr),
m_basebandSampleRate(0)
{
qDebug("VORDemodSCBaseband::VORDemodSCBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
// FIXME: If we remove this audio stops working when this demod is closed
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifoBug, getInputMessageQueue());
}
VORDemodSCBaseband::~VORDemodSCBaseband()
{
m_inputMessageQueue.clear();
for (int i = 0; i < m_sinks.size(); i++)
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sinks[i]->getAudioFifo());
delete m_sinks[i];
}
m_sinks.clear();
// FIXME: If we remove this audio stops working when this demod is closed
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifoBug);
for (int i = 0; i < m_channelizers.size(); i++)
delete m_channelizers[i];
m_channelizers.clear();
}
void VORDemodSCBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void VORDemodSCBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&VORDemodSCBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void VORDemodSCBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&VORDemodSCBaseband::handleData
);
m_running = false;
}
void VORDemodSCBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void VORDemodSCBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
for (int i = 0; i < m_channelizers.size(); i++)
m_channelizers[i]->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
for (int i = 0; i < m_channelizers.size(); i++)
m_channelizers[i]->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void VORDemodSCBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool VORDemodSCBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureVORDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureVORDemodBaseband& cfg = (MsgConfigureVORDemodBaseband&) cmd;
qDebug() << "VORDemodSCBaseband::handleMessage: MsgConfigureVORDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "VORDemodSCBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate() << " centerFrequency: " << notif.getCenterFrequency();
m_centerFrequency = notif.getCenterFrequency();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(m_basebandSampleRate));
return true;
}
else
{
return false;
}
}
// Calculate offset of VOR center frequency from sample source center frequency
void VORDemodSCBaseband::calculateOffset(VORDemodSCSink *sink)
{
int frequencyOffset = sink->m_vorFrequencyHz - m_centerFrequency;
bool outOfBand = std::abs(frequencyOffset)+VORDEMOD_CHANNEL_BANDWIDTH > (m_basebandSampleRate/2);
if (m_messageQueueToGUI != nullptr)
{
VORDemodSCReport::MsgReportFreqOffset *msg = VORDemodSCReport::MsgReportFreqOffset::create(sink->m_subChannelId, frequencyOffset, outOfBand);
m_messageQueueToGUI->push(msg);
}
sink->m_frequencyOffset = frequencyOffset;
sink->m_outOfBand = outOfBand;
}
void VORDemodSCBaseband::applySettings(const VORDemodSCSettings& settings, bool force)
{
// Remove sub-channels no longer needed
for (int i = 0; i < m_sinks.size(); i++)
{
if (!settings.m_subChannelSettings.contains(m_sinks[i]->m_subChannelId))
{
qDebug() << "VORDemodSCBaseband::applySettings: Removing sink " << m_sinks[i]->m_subChannelId;
VORDemodSCSink *sink = m_sinks[i];
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sinks[i]->getAudioFifo());
m_sinks.removeAt(i);
delete sink;
DownChannelizer *channelizer = m_channelizers[i];
m_channelizers.removeAt(i);
delete channelizer;
}
}
// Add new sub channels
QHash<int, VORDemodSubChannelSettings *>::const_iterator itr = settings.m_subChannelSettings.begin();
while (itr != settings.m_subChannelSettings.end())
{
VORDemodSubChannelSettings *subChannelSettings = itr.value();
int j;
for (j = 0; j < m_sinks.size(); j++)
{
if (subChannelSettings->m_id == m_sinks[j]->m_subChannelId)
break;
}
if (j == m_sinks.size())
{
// Add a sub-channel sink
qDebug() << "VORDemodSCBaseband::applySettings: Adding sink " << subChannelSettings->m_id;
VORDemodSCSink *sink = new VORDemodSCSink(settings, subChannelSettings->m_id, m_messageQueueToGUI);
DownChannelizer *channelizer = new DownChannelizer(sink);
channelizer->setBasebandSampleRate(m_basebandSampleRate);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(sink->getAudioFifo(), getInputMessageQueue());
sink->applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
m_sinks.append(sink);
m_channelizers.append(channelizer);
calculateOffset(sink);
channelizer->setChannelization(VORDEMOD_CHANNEL_SAMPLE_RATE, sink->m_frequencyOffset);
sink->applyChannelSettings(channelizer->getChannelSampleRate(), channelizer->getChannelFrequencyOffset(), true);
sink->applyAudioSampleRate(sink->getAudioSampleRate());
}
++itr;
}
if (force)
{
for (int i = 0; i < m_sinks.size(); i++)
{
m_channelizers[i]->setChannelization(VORDEMOD_CHANNEL_SAMPLE_RATE, m_sinks[i]->m_frequencyOffset);
m_sinks[i]->applyChannelSettings(m_channelizers[i]->getChannelSampleRate(), m_channelizers[i]->getChannelFrequencyOffset());
m_sinks[i]->applyAudioSampleRate(m_sinks[i]->getAudioSampleRate()); // reapply in case of channel sample rate change
}
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);
for (int i = 0; i < m_sinks.size(); i++)
{
audioDeviceManager->removeAudioSink(m_sinks[i]->getAudioFifo());
audioDeviceManager->addAudioSink(m_sinks[i]->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_sinks[i]->getAudioSampleRate() != audioSampleRate)
{
m_sinks[i]->applyAudioSampleRate(audioSampleRate);
}
}
}
for (int i = 0; i < m_sinks.size(); i++)
m_sinks[i]->applySettings(settings, force);
m_settings = settings;
}
void VORDemodSCBaseband::setBasebandSampleRate(int sampleRate)
{
m_basebandSampleRate = sampleRate;
for (int i = 0; i < m_sinks.size(); i++)
{
m_channelizers[i]->setBasebandSampleRate(sampleRate);
calculateOffset(m_sinks[i]);
m_sinks[i]->applyChannelSettings(m_channelizers[i]->getChannelSampleRate(), m_channelizers[i]->getChannelFrequencyOffset());
m_sinks[i]->applyAudioSampleRate(m_sinks[i]->getAudioSampleRate()); // reapply in case of channel sample rate change
}
}

View File

@ -0,0 +1,136 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCBASEBAND_H
#define INCLUDE_VORDEMODSCBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "vordemodscsink.h"
class DownChannelizer;
class VORDemodSCBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureVORDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const VORDemodSCSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureVORDemodBaseband* create(const VORDemodSCSettings& settings, bool force)
{
return new MsgConfigureVORDemodBaseband(settings, force);
}
private:
VORDemodSCSettings m_settings;
bool m_force;
MsgConfigureVORDemodBaseband(const VORDemodSCSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
VORDemodSCBaseband();
~VORDemodSCBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
avg = 0.0;
peak = 0.0;
nbSamples = 0;
for (int i = 0; i < m_sinks.size(); i++)
{
double avg1, peak1;
int nbSamples1;
m_sinks[i]->getMagSqLevels(avg1, peak1, nbSamples1);
if (avg1 > avg)
{
avg = avg1;
nbSamples = nbSamples1;
}
avg += avg1;
if (peak1 > peak)
peak = peak1;
}
}
void setMessageQueueToGUI(MessageQueue *messageQueue) {
m_messageQueueToGUI = messageQueue;
for (int i = 0; i < m_sinks.size(); i++)
m_sinks[i]->setMessageQueueToGUI(messageQueue);
}
bool getSquelchOpen() const {
for (int i = 0; i < m_sinks.size(); i++)
{
if (m_sinks[i]->getSquelchOpen())
return true;
}
return false;
}
int getAudioSampleRate() const {
if (m_sinks.size() > 0)
return m_sinks[0]->getAudioSampleRate();
else
return 48000;
}
void setBasebandSampleRate(int sampleRate);
double getMagSq() const {
if (m_sinks.size() > 0)
return m_sinks[0]->getMagSq();
else
return 0.0;
}
bool isRunning() const { return m_running; }
private:
SampleSinkFifo m_sampleFifo;
QList<DownChannelizer *> m_channelizers;
QList<VORDemodSCSink *> m_sinks;
AudioFifo m_audioFifoBug; // FIXME: Removing this results in audio stopping when demod is closed
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
VORDemodSCSettings m_settings;
bool m_running;
QMutex m_mutex;
MessageQueue *m_messageQueueToGUI;
int m_basebandSampleRate;
int m_centerFrequency;
bool handleMessage(const Message& cmd);
void calculateOffset(VORDemodSCSink *sink);
void applySettings(const VORDemodSCSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_VORDEMODSCBASEBAND_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,291 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCGUI_H
#define INCLUDE_VORDEMODSCGUI_H
#include <QIcon>
#include <QAbstractListModel>
#include <QModelIndex>
#include <QProgressDialog>
#include <QGeoLocation>
#include <QGeoCoordinate>
#include <QTableWidgetItem>
#include <QPushButton>
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "util/messagequeue.h"
#include "util/httpdownloadmanager.h"
#include "util/azel.h"
#include "vordemodscsettings.h"
#include "navaid.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class VORDemodSC;
class VORDemodSCGUI;
namespace Ui {
class VORDemodSCGUI;
}
class VORDemodSCGUI;
// Table items for each VOR
class VORGUI : public QObject {
Q_OBJECT
public:
NavAid *m_navAid;
QVariantList m_coordinates;
VORDemodSCGUI *m_gui;
QTableWidgetItem *m_nameItem;
QTableWidgetItem *m_frequencyItem;
QTableWidgetItem *m_offsetItem;
QTableWidgetItem *m_identItem;
QTableWidgetItem *m_morseItem;
QTableWidgetItem *m_radialItem;
QTableWidgetItem *m_rxIdentItem;
QTableWidgetItem *m_rxMorseItem;
QTableWidgetItem *m_varMagItem;
QTableWidgetItem *m_refMagItem;
QWidget *m_muteItem;
QToolButton *m_muteButton;
VORGUI(NavAid *navAid, VORDemodSCGUI *gui);
private slots:
void on_audioMute_toggled(bool checked);
};
// VOR model used for each VOR on the map
class VORModel : public QAbstractListModel {
Q_OBJECT
public:
using QAbstractListModel::QAbstractListModel;
enum MarkerRoles{
positionRole = Qt::UserRole + 1,
vorDataRole = Qt::UserRole + 2,
vorImageRole = Qt::UserRole + 3,
vorRadialRole = Qt::UserRole + 4,
bubbleColourRole = Qt::UserRole + 5,
selectedRole = Qt::UserRole + 6
};
VORModel(VORDemodSCGUI *gui) :
m_gui(gui),
m_radialsVisible(true)
{
}
Q_INVOKABLE void addVOR(NavAid *vor) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_vors.append(vor);
m_selected.append(false);
m_radials.append(-1.0f);
m_vorGUIs.append(nullptr);
endInsertRows();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent)
return m_vors.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override {
(void) index;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
void allVORUpdated() {
for (int i = 0; i < m_vors.count(); i++)
{
QModelIndex idx = index(i);
emit dataChanged(idx, idx);
}
}
void removeVOR(NavAid *vor) {
int row = m_vors.indexOf(vor);
if (row >= 0)
{
beginRemoveRows(QModelIndex(), row, row);
m_vors.removeAt(row);
m_selected.removeAt(row);
m_radials.removeAt(row);
m_vorGUIs.removeAt(row);
endRemoveRows();
}
}
void removeAllVORs() {
beginRemoveRows(QModelIndex(), 0, m_vors.count());
m_vors.clear();
m_selected.clear();
m_radials.clear();
m_vorGUIs.clear();
endRemoveRows();
}
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[positionRole] = "position";
roles[vorDataRole] = "vorData";
roles[vorImageRole] = "vorImage";
roles[vorRadialRole] = "vorRadial";
roles[bubbleColourRole] = "bubbleColour";
roles[selectedRole] = "selected";
return roles;
}
void setRadialsVisible(bool radialsVisible)
{
m_radialsVisible = radialsVisible;
allVORUpdated();
}
void setRadial(int id, bool valid, Real radial)
{
for (int i = 0; i < m_vors.count(); i++)
{
if (m_vors[i]->m_id == id)
{
if (valid)
m_radials[i] = radial;
else
m_radials[i] = -1; // -1 to indicate invalid
QModelIndex idx = index(i);
emit dataChanged(idx, idx);
break;
}
}
}
bool findIntersection(float &lat, float &lon);
private:
VORDemodSCGUI *m_gui;
bool m_radialsVisible;
QList<NavAid *> m_vors;
QList<bool> m_selected;
QList<Real> m_radials;
QList<VORGUI *> m_vorGUIs;
};
class VORDemodSCGUI : public ChannelGUI {
Q_OBJECT
public:
static VORDemodSCGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void selectVOR(VORGUI *vorGUI, bool selected);
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
friend class VORGUI;
friend class VORModel;
Ui::VORDemodSCGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
VORDemodSCSettings m_settings;
bool m_doApplySettings;
VORDemodSC* m_vorDemod;
bool m_squelchOpen;
int m_basebandSampleRate;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QMenu *menu; // Column select context menu
HttpDownloadManager m_dlm;
QProgressDialog *m_progressDialog;
int m_countryIndex;
VORModel m_vorModel;
QHash<int, NavAid *> *m_vors;
QHash<int, VORGUI *> m_selectedVORs;
AzEl m_azEl; // Position of station
QIcon m_muteIcon;
explicit VORDemodSCGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~VORDemodSCGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked);
void calculateFreqOffset(VORGUI *vorGUI);
void calculateFreqOffsets();
void updateVORs();
QString getOpenAIPVORDBURL(int i);
QString getOpenAIPVORDBFilename(int i);
QString getVORDBFilename();
void readNavAids();
// Move to util
QString getDataDir();
qint64 fileAgeInDays(QString filename);
bool confirmDownload(QString filename);
private slots:
void on_thresh_valueChanged(int value);
void on_volume_valueChanged(int value);
void on_squelch_valueChanged(int value);
void on_audioMute_toggled(bool checked);
void on_getOurAirportsVORDB_clicked(bool checked = false);
void on_getOpenAIPVORDB_clicked(bool checked = false);
void on_magDecAdjust_clicked(bool checked = false);
void vorData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void vorData_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadFinished(const QString& filename, bool success);
void handleInputMessages();
void audioSelect();
void tick();
};
#endif // INCLUDE_VORDEMODSCGUI_H

View File

@ -0,0 +1,617 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VORDemodSCGUI</class>
<widget class="RollupWidget" name="VORDemodSCGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>893</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>VOR Demodulator</string>
</property>
<property name="statusTip">
<string>VOR Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>61</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="levelLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QToolButton" name="audioMute">
<property name="toolTip">
<string>Left: Mute/Unmute audio Right: view/select audio device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_on.png</normaloff>
<normalon>:/sound_off.png</normalon>:/sound_on.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="QPushButton" name="getOurAirportsVORDB">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Download OurAirports VOR database</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/demodvor/icons/vor.png</normaloff>:/demodvor/icons/vor.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="getOpenAIPVORDB">
<property name="toolTip">
<string>Download OpenAIP VOR database</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/demodvor/icons/vor.png</normaloff>:/demodvor/icons/vor.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="magDecAdjust">
<property name="toolTip">
<string>Draw radials adjusted for magnetic declination</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/demodvor/icons/compass.png</normaloff>:/demodvor/icons/compass.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="threshLabel">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>MT</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="thresh">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Morse code ident threshold (Linear SNR)</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="threshText">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Sq</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="squelch">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio squelch threshold (dB)</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-40</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="squelchText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>-100dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Vol</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="volume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio volume</string>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="volumeText">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>110</y>
<width>391</width>
<height>140</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>VORs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="vorData">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Name of the VOR</string>
</property>
</column>
<column>
<property name="text">
<string>Freq (MHz)</string>
</property>
<property name="toolTip">
<string>Frequency of the VOR in MHz</string>
</property>
</column>
<column>
<property name="text">
<string>Offset (kHz)</string>
</property>
<property name="toolTip">
<string>Offset of the VOR's frequency from the current center frequency. Red indicates out of range.</string>
</property>
</column>
<column>
<property name="text">
<string>Ident</string>
</property>
<property name="toolTip">
<string>Ident for the VOR</string>
</property>
</column>
<column>
<property name="text">
<string>Morse</string>
</property>
<property name="toolTip">
<string>Morse code ident for the VOR</string>
</property>
</column>
<column>
<property name="text">
<string>RX Ident</string>
</property>
<property name="toolTip">
<string>Received ident</string>
</property>
</column>
<column>
<property name="text">
<string>RX Morse</string>
</property>
<property name="toolTip">
<string>Received Morse code ident</string>
</property>
</column>
<column>
<property name="text">
<string>Radial (°)</string>
</property>
<property name="toolTip">
<string>Calculated radial from the VOR</string>
</property>
</column>
<column>
<property name="text">
<string>Ref (dB)</string>
</property>
<property name="toolTip">
<string>Magnitude of received reference signal in dB</string>
</property>
</column>
<column>
<property name="text">
<string>Var (dB)</string>
</property>
<property name="toolTip">
<string>Magnitude of received variable signal in dB</string>
</property>
</column>
<column>
<property name="text">
<string>Mute</string>
</property>
<property name="toolTip">
<string>Mute/unmute audio from selected VORs</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="mapContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>260</y>
<width>391</width>
<height>581</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Map</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutMap" stretch="0">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QQuickWidget" name="map">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>500</height>
</size>
</property>
<property name="toolTip">
<string>VOR map</string>
</property>
<property name="resizeMode">
<enum>QQuickWidget::SizeRootObjectToView</enum>
</property>
<property name="source">
<url>
<string/>
</url>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>QQuickWidget</class>
<extends>QWidget</extends>
<header location="global">QtQuickWidgets/QQuickWidget</header>
</customwidget>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>audioMute</tabstop>
<tabstop>getOurAirportsVORDB</tabstop>
<tabstop>vorData</tabstop>
<tabstop>map</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="map.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "vordemodscgui.h"
#endif
#include "vordemodsc.h"
#include "vordemodscwebapiadapter.h"
#include "vordemodscplugin.h"
const PluginDescriptor VORDemodSCPlugin::m_pluginDescriptor = {
VORDemodSC::m_channelId,
QStringLiteral("VOR Single Channel Demodulator"),
QStringLiteral("6.2.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
VORDemodSCPlugin::VORDemodSCPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& VORDemodSCPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void VORDemodSCPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(VORDemodSC::m_channelIdURI, VORDemodSC::m_channelId, this);
}
void VORDemodSCPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
VORDemodSC *instance = new VORDemodSC(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* VORDemodSCPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* VORDemodSCPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return VORDemodSCGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* VORDemodSCPlugin::createChannelWebAPIAdapter() const
{
return new VORDemodSCWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCPLUGIN_H
#define INCLUDE_VORDEMODSCPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class VORDemodSCPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.vordemodsc")
public:
explicit VORDemodSCPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_VORDEMODSCPLUGIN_H

View File

@ -0,0 +1,22 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "vordemodscreport.h"
MESSAGE_CLASS_DEFINITION(VORDemodSCReport::MsgReportFreqOffset, Message)
MESSAGE_CLASS_DEFINITION(VORDemodSCReport::MsgReportRadial, Message)
MESSAGE_CLASS_DEFINITION(VORDemodSCReport::MsgReportIdent, Message)

View File

@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCREPORT_H
#define INCLUDE_VORDEMODSCREPORT_H
#include <QObject>
#include "util/message.h"
class VORDemodSCReport : public QObject
{
Q_OBJECT
public:
class MsgReportRadial : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSubChannelId() const { return m_subChannelId; }
float getRadial() const { return m_radial; }
float getRefMag() const { return m_refMag; }
float getVarMag() const { return m_varMag; }
static MsgReportRadial* create(int subChannelId, float radial, float refMag, float varMag)
{
return new MsgReportRadial(subChannelId, radial, refMag, varMag);
}
private:
int m_subChannelId;
float m_radial;
float m_refMag;
float m_varMag;
MsgReportRadial(int subChannelId, float radial, float refMag, float varMag) :
Message(),
m_subChannelId(subChannelId),
m_radial(radial),
m_refMag(refMag),
m_varMag(varMag)
{
}
};
class MsgReportFreqOffset : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSubChannelId() const { return m_subChannelId; }
int getFreqOffset() const { return m_freqOffset; }
bool getOutOfBand() const { return m_outOfBand; }
static MsgReportFreqOffset* create(int subChannelId, int freqOffset, bool outOfBand)
{
return new MsgReportFreqOffset(subChannelId, freqOffset, outOfBand);
}
private:
int m_subChannelId;
int m_freqOffset;
bool m_outOfBand;
MsgReportFreqOffset(int subChannelId, int freqOffset, bool outOfBand) :
Message(),
m_subChannelId(subChannelId),
m_freqOffset(freqOffset),
m_outOfBand(outOfBand)
{
}
};
class MsgReportIdent : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSubChannelId() const { return m_subChannelId; }
QString getIdent() const { return m_ident; }
static MsgReportIdent* create(int subChannelId, QString ident)
{
return new MsgReportIdent(subChannelId, ident);
}
private:
int m_subChannelId;
QString m_ident;
MsgReportIdent(int subChannelId, QString ident) :
Message(),
m_subChannelId(subChannelId),
m_ident(ident)
{
}
};
public:
VORDemodSCReport() {}
~VORDemodSCReport() {}
};
#endif // INCLUDE_VORDEMODSCREPORT_H

View File

@ -0,0 +1,157 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "vordemodscsettings.h"
VORDemodSCSettings::VORDemodSCSettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void VORDemodSCSettings::resetToDefaults()
{
m_squelch = -60.0;
m_volume = 2.0;
m_audioMute = false;
m_rgbColor = QColor(255, 255, 0).rgb();
m_title = "VOR Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_identThreshold = 2.0;
m_refThresholdDB = -45.0;
m_varThresholdDB = -90.0;
m_magDecAdjust = true;
for (int i = 0; i < VORDEMOD_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray VORDemodSCSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(3, m_streamIndex);
s.writeS32(4, m_volume*10);
s.writeS32(5, m_squelch);
if (m_channelMarker) {
s.writeBlob(6, m_channelMarker->serialize());
}
s.writeU32(7, m_rgbColor);
s.writeString(9, m_title);
s.writeString(11, m_audioDeviceName);
s.writeBool(14, m_useReverseAPI);
s.writeString(15, m_reverseAPIAddress);
s.writeU32(16, m_reverseAPIPort);
s.writeU32(17, m_reverseAPIDeviceIndex);
s.writeU32(18, m_reverseAPIChannelIndex);
s.writeReal(20, m_identThreshold);
s.writeReal(21, m_refThresholdDB);
s.writeReal(22, m_varThresholdDB);
s.writeBool(23, m_magDecAdjust);
for (int i = 0; i < VORDEMOD_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]);
for (int i = 0; i < VORDEMOD_COLUMNS; i++)
s.writeS32(200 + i, m_columnSizes[i]);
return s.final();
}
bool VORDemodSCSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
QString strtmp;
d.readS32(3, &m_streamIndex, 0);
d.readS32(4, &tmp, 20);
m_volume = tmp * 0.1;
d.readS32(5, &tmp, -40);
m_squelch = tmp;
d.readBlob(6, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readU32(7, &m_rgbColor);
d.readString(9, &m_title, "VOR Demodulator");
d.readString(11, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readBool(14, &m_useReverseAPI, false);
d.readString(15, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(16, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(17, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(18, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readReal(20, &m_identThreshold, 2.0);
d.readReal(21, &m_refThresholdDB, -45.0);
d.readReal(22, &m_varThresholdDB, -90.0);
d.readBool(23, &m_magDecAdjust, true);
for (int i = 0; i < VORDEMOD_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i);
for (int i = 0; i < VORDEMOD_COLUMNS; i++)
d.readS32(200 + i, &m_columnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCSETTINGS_H
#define INCLUDE_VORDEMODSCSETTINGS_H
#include <QByteArray>
#include <QHash>
class Serializable;
// Number of columns in the table
#define VORDEMOD_COLUMNS 11
struct VORDemodSubChannelSettings {
int m_id; //!< Unique VOR identifier (from database)
int m_frequency; //!< Frequency the VOR is on
bool m_audioMute; //!< Mute the audio from this VOR
};
struct VORDemodSCSettings
{
Real m_squelch;
Real m_volume;
bool m_audioMute;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
Real m_identThreshold; //!< Linear SNR threshold for Morse demodulator
Real m_refThresholdDB; //!< Threshold in dB for valid VOR reference signal
Real m_varThresholdDB; //!< Threshold in dB for valid VOR variable signal
bool m_magDecAdjust; //!< Adjust for magnetic declination when drawing radials on the map
int m_columnIndexes[VORDEMOD_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[VORDEMOD_COLUMNS]; //!< Size of the coumns in the table
QHash<int, VORDemodSubChannelSettings *> m_subChannelSettings;
VORDemodSCSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_VORDEMODSCSETTINGS_H */

View File

@ -0,0 +1,446 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <complex.h>
#include "audio/audiooutputdevice.h"
#include "dsp/dspengine.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "util/morse.h"
#include "util/units.h"
#include "vordemodscsink.h"
#include "vordemodscreport.h"
VORDemodSCSink::VORDemodSCSink(const VORDemodSCSettings& settings, int subChannel,
MessageQueue *messageQueueToGUI) :
m_channelFrequencyOffset(0),
m_outOfBand(true),
m_channelSampleRate(VORDEMOD_CHANNEL_SAMPLE_RATE),
m_audioSampleRate(48000),
m_squelchCount(0),
m_squelchOpen(false),
m_squelchDelayLine(9600),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToGUI(messageQueueToGUI),
m_volumeAGC(0.003),
m_audioFifo(48000),
m_refPrev(0.0f),
m_movingAverageIdent(5000),
m_prevBit(0),
m_bitTime(0),
m_varGoertzel(30, VORDEMOD_CHANNEL_SAMPLE_RATE),
m_refGoertzel(30, VORDEMOD_CHANNEL_SAMPLE_RATE)
{
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_magsq = 0.0;
qDebug() << "Sink " << subChannel;
if (subChannel >= 0)
{
m_subChannelId = subChannel;
m_vorFrequencyHz = settings.m_subChannelSettings[subChannel]->m_frequency;
applySettings(settings, true);
}
}
VORDemodSCSink::~VORDemodSCSink()
{
}
void VORDemodSCSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
if (m_outOfBand)
return;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
if (m_audioBufferFill > 0)
{
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill) {
qDebug("VORDemodSCSink::feed: %u/%u tail samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
}
void VORDemodSCSink::processOneAudioSample(Complex &ci)
{
Real re = ci.real() / SDR_RX_SCALEF;
Real im = ci.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
m_squelchDelayLine.write(magsq);
if (m_magsq < m_squelchLevel)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
else
{
if (m_squelchCount < (unsigned int)m_audioSampleRate / 10) {
m_squelchCount++;
}
}
qint16 sample;
m_squelchOpen = (m_squelchCount >= (unsigned int)m_audioSampleRate / 20);
if (m_squelchOpen && !m_settings.m_audioMute && !m_settings.m_subChannelSettings.value(m_subChannelId)->m_audioMute)
{
Real demod;
{
demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20));
m_volumeAGC.feed(demod);
demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue();
}
demod = m_bandpass.filter(demod);
Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate);
sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume;
}
else
{
sample = 0;
}
m_audioBuffer[m_audioBufferFill].l = sample;
m_audioBuffer[m_audioBufferFill].r = sample;
++m_audioBufferFill;
if (m_audioBufferFill >= m_audioBuffer.size())
{
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill)
{
qDebug("VORDemodSCSink::processOneAudioSample: %u/%u audio samples written", res, m_audioBufferFill);
m_audioFifo.clear();
}
m_audioBufferFill = 0;
}
}
void VORDemodSCSink::processOneSample(Complex &ci)
{
Complex ca;
// Resample as audio
if (m_audioInterpolatorDistance < 1.0f) // interpolate
{
while (!m_audioInterpolator.interpolate(&m_audioInterpolatorDistanceRemain, ci, &ca))
{
processOneAudioSample(ca);
m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance;
}
}
else // decimate
{
if (m_audioInterpolator.decimate(&m_audioInterpolatorDistanceRemain, ci, &ca))
{
processOneAudioSample(ca);
m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance;
}
}
Real re = ci.real() / SDR_RX_SCALEF;
Real im = ci.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
// AM Demod
Real mag = std::sqrt(magsq);
// Calculate phase of 30Hz variable AM signal
double varPhase;
double varMag;
if (m_varGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
{
m_varGoertzel.goertzel(mag);
varPhase = Units::radiansToDegress(m_varGoertzel.phase());
varMag = m_varGoertzel.mag();
m_varGoertzel.reset();
}
else
m_varGoertzel.filter(mag);
Complex magc(mag, 0.0);
// Mix reference sub-carrier down to 0Hz
Complex fm0 = magc;
fm0 *= m_ncoRef.nextIQ();
// Filter other signals
Complex fmfilt = m_lowpassRef.filter(fm0);
// FM demod
Real phi = std::arg(std::conj(m_refPrev) * fmfilt);
m_refPrev = fmfilt;
// Calculate phase of 30Hz reference FM signal
if (m_refGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
{
m_refGoertzel.goertzel(phi);
float phaseDeg = Units::radiansToDegress(m_refGoertzel.phase());
double refMag = m_refGoertzel.mag();
int groupDelay = (301-1)/2;
float filterPhaseShift = 360.0*30.0*groupDelay/VORDEMOD_CHANNEL_SAMPLE_RATE;
float shiftedPhase = phaseDeg + filterPhaseShift;
// Calculate difference in phase, which is the radial
float phaseDifference = shiftedPhase - varPhase;
if (phaseDifference < 0.0)
phaseDifference += 360.0;
else if (phaseDifference >= 360.0)
phaseDifference -= 360.0;
// qDebug() << "Ref phase: " << phaseDeg << " var phase " << varPhase;
if (getMessageQueueToGUI())
{
VORDemodSCReport::MsgReportRadial *msg = VORDemodSCReport::MsgReportRadial::create(m_subChannelId, phaseDifference, refMag, varMag);
getMessageQueueToGUI()->push(msg);
}
m_refGoertzel.reset();
}
else
m_refGoertzel.filter(phi);
// Ident demod
// Remove ident sub-carrier offset
Complex c1 = magc;
c1 *= m_ncoIdent.nextIQ();
// Filter other signals
Complex c2 = std::abs(m_lowpassIdent.filter(c1));
// Filter noise with moving average (moving average preserves edges)
m_movingAverageIdent(c2.real());
Real mav = m_movingAverageIdent.asFloat();
// Caclulate noise floor
if (mav > m_identMaxs[m_binCnt])
m_identMaxs[m_binCnt] = mav;
m_binSampleCnt++;
if (m_binSampleCnt >= m_samplesPerDot10wpm/2)
{
// Calc minimum of maximums
m_identNoise = 1.0f;
for (int i = 0; i < m_identBins; i++)
{
m_identNoise = std::min(m_identNoise, m_identMaxs[i]);
}
m_binSampleCnt = 0;
m_binCnt++;
if (m_binCnt == m_identBins)
m_binCnt = 0;
m_identMaxs[m_binCnt] = 0.0f;
// Prevent divide by zero
if (m_identNoise == 0.0f)
m_identNoise = 1e-20f;
}
// CW demod
int bit = (mav / m_identNoise) >= m_settings.m_identThreshold;
if ((m_prevBit == 0) && (bit == 1))
{
if (m_bitTime > 7*m_samplesPerDot10wpm)
{
if (m_ident != "")
{
qDebug() << m_ident << " " << Morse::toString(m_ident);
if (getMessageQueueToGUI())
{
VORDemodSCReport::MsgReportIdent *msg = VORDemodSCReport::MsgReportIdent::create(m_subChannelId, m_ident);
getMessageQueueToGUI()->push(msg);
}
m_ident = "";
}
}
else if (m_bitTime > 2.5*m_samplesPerDot10wpm)
{
m_ident.append(" ");
}
m_bitTime = 0;
}
else if (bit == 1)
{
m_bitTime++;
}
else if ((m_prevBit == 1) && (bit == 0))
{
if (m_bitTime > 2*m_samplesPerDot10wpm)
{
m_ident.append("-");
}
else if (m_bitTime > 0.2*m_samplesPerDot10wpm)
{
m_ident.append(".");
}
m_bitTime = 0;
}
else
{
m_bitTime++;
if (m_bitTime > 10*m_samplesPerDot7wpm)
{
m_ident = m_ident.simplified();
if (m_ident != "")
{
qDebug() << m_ident << " " << Morse::toString(m_ident);
if (getMessageQueueToGUI())
{
VORDemodSCReport::MsgReportIdent *msg = VORDemodSCReport::MsgReportIdent::create(m_subChannelId, m_ident);
getMessageQueueToGUI()->push(msg);
}
m_ident = "";
}
m_bitTime = 0;
}
}
m_prevBit = bit;
}
void VORDemodSCSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "VORDemodSCSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, VORDEMOD_CHANNEL_BANDWIDTH);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) VORDEMOD_CHANNEL_SAMPLE_RATE;
m_samplesPerDot7wpm = VORDEMOD_CHANNEL_SAMPLE_RATE*60/(50*7);
m_samplesPerDot10wpm = VORDEMOD_CHANNEL_SAMPLE_RATE*60/(50*10);
m_ncoIdent.setFreq(-1020, VORDEMOD_CHANNEL_SAMPLE_RATE); // +-50Hz source offset allowed
m_ncoRef.setFreq(-9960, VORDEMOD_CHANNEL_SAMPLE_RATE);
m_lowpassIdent.create(301, VORDEMOD_CHANNEL_SAMPLE_RATE, 100.0f);
m_lowpassRef.create(301, VORDEMOD_CHANNEL_SAMPLE_RATE, 600.0f); // Max deviation is 480Hz
m_movingAverageIdent.resize(m_samplesPerDot10wpm/5); // Needs to be short enough for noise floor calculation
m_binSampleCnt = 0;
m_binCnt = 0;
m_identNoise = 0.0001f;
for (int i = 0; i < m_identBins; i++)
{
m_identMaxs[i] = 0.0f;
}
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void VORDemodSCSink::applySettings(const VORDemodSCSettings& settings, bool force)
{
qDebug() << "VORDemodSCSink::applySettings:"
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " force: " << force;
if ((m_settings.m_squelch != settings.m_squelch) || force) {
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
}
m_settings = settings;
}
void VORDemodSCSink::applyAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("VORDemodSCSink::applyAudioSampleRate: invalid sample rate: %d", sampleRate);
return;
}
qDebug("VORDemodSCSink::applyAudioSampleRate: sampleRate: %d m_channelSampleRate: %d", sampleRate, m_channelSampleRate);
// (ICAO Annex 10 3.3.6.3) - Optional voice audio is 300Hz to 3kHz
m_audioInterpolator.create(16, VORDEMOD_CHANNEL_SAMPLE_RATE, 3000.0f);
m_audioInterpolatorDistanceRemain = 0;
m_audioInterpolatorDistance = (Real) VORDEMOD_CHANNEL_SAMPLE_RATE / (Real) sampleRate;
m_bandpass.create(301, sampleRate, 300.0f, 3000.0f);
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/5);
m_volumeAGC.resizeNew(sampleRate/10, 0.003f);
m_audioSampleRate = sampleRate;
}

View File

@ -0,0 +1,156 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSCSINK_H
#define INCLUDE_VORDEMODSCSINK_H
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/agc.h"
#include "dsp/firfilter.h"
#include "dsp/goertzel.h"
#include "audio/audiofifo.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "util/messagequeue.h"
#include "vordemodscsettings.h"
#include <vector>
// Highest frequency is the FM subcarrier at up to ~11kHz
// However, old VORs can have 0.005% frequency offset, which is 6kHz
#define VORDEMOD_CHANNEL_BANDWIDTH 18000
// Sample rate needs to be at least twice the above
// Also need to consider impact frequency resolution of Goertzel filters
// May as well make it a common audio rate, to possibly avoid decimation
#define VORDEMOD_CHANNEL_SAMPLE_RATE 48000
class VORDemodSCSink : public ChannelSampleSink {
public:
VORDemodSCSink(const VORDemodSCSettings& settings, int subChannel,
MessageQueue *messageQueueToGUI);
~VORDemodSCSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const VORDemodSCSettings& settings, bool force = false);
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
void applyAudioSampleRate(int sampleRate);
int getAudioSampleRate() const { return m_audioSampleRate; }
double getMagSq() const { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
int m_subChannelId; // The id for the VOR this demod sink was created for
int m_vorFrequencyHz; // The VORs frequency
int m_frequencyOffset; // Different between sample source center frequeny and VOR center frequency
int m_channelFrequencyOffset;
bool m_outOfBand;
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
VORDemodSCSettings m_settings;
int m_channelSampleRate;
int m_audioSampleRate;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
Real m_squelchLevel;
uint32_t m_squelchCount;
bool m_squelchOpen;
DoubleBufferFIFO<Real> m_squelchDelayLine;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MessageQueue *m_messageQueueToGUI;
MovingAverageUtil<Real, double, 16> m_movingAverage;
SimpleAGC<4800> m_volumeAGC;
Bandpass<Real> m_bandpass;
Interpolator m_audioInterpolator;
Real m_audioInterpolatorDistance;
Real m_audioInterpolatorDistanceRemain;
AudioVector m_audioBuffer;
AudioFifo m_audioFifo;
uint32_t m_audioBufferFill;
NCO m_ncoIdent;
NCO m_ncoRef;
Lowpass<Complex> m_lowpassRef;
Lowpass<Complex> m_lowpassIdent;
Complex m_refPrev;
MovingAverageUtilVar<Real, double> m_movingAverageIdent;
static const int m_identBins = 10;
Real m_identMins[m_identBins];
Real m_identMin;
Real m_identMaxs[m_identBins];
Real m_identNoise;
int m_binSampleCnt;
int m_binCnt;
int m_samplesPerDot7wpm;
int m_samplesPerDot10wpm;
int m_prevBit;
int m_bitTime;
QString m_ident;
Goertzel m_varGoertzel;
Goertzel m_refGoertzel;
void processOneSample(Complex &ci);
void processOneAudioSample(Complex &ci);
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
};
#endif // INCLUDE_VORDEMODSCSINK_H

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "vordemodsc.h"
#include "vordemodscwebapiadapter.h"
VORDemodSCWebAPIAdapter::VORDemodSCWebAPIAdapter()
{}
VORDemodSCWebAPIAdapter::~VORDemodSCWebAPIAdapter()
{}
int VORDemodSCWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setVorDemodSettings(new SWGSDRangel::SWGVORDemodSettings());
response.getVorDemodSettings()->init();
VORDemodSC::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int VORDemodSCWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
VORDemodSC::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_VORDEMODSC_WEBAPIADAPTER_H
#define INCLUDE_VORDEMODSC_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "vordemodscsettings.h"
/**
* Standalone API adapter only for the settings
*/
class VORDemodSCWebAPIAdapter : public ChannelWebAPIAdapter {
public:
VORDemodSCWebAPIAdapter();
virtual ~VORDemodSCWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
VORDemodSCSettings m_settings;
};
#endif // INCLUDE_VORDEMODSC_WEBAPIADAPTER_H