1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-09-27 15:26:33 -04:00

Merge pull request #1232 from srcejon/v7_vor

VOR Demod and Localizer updates
This commit is contained in:
Edouard Griffiths 2022-05-04 13:34:30 +02:00 committed by GitHub
commit 85f7b1de47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 518 additions and 1213 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

View File

@ -257,6 +257,12 @@ void VORDemod::applySettings(const VORDemodSettings& settings, bool force)
}
if ((m_settings.m_navId != settings.m_navId) || force) {
reverseAPIKeys.append("navId");
// Reset state so we don't report old data for new NavId
m_radial = 0.0f;
m_refMag = -200.0f;
m_varMag = -200.0f;
m_morseIdent = "";
}
if ((m_settings.m_squelch != settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
@ -509,7 +515,6 @@ void VORDemod::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());

View File

@ -401,6 +401,21 @@ void VORDemodSCSink::applySettings(const VORDemodSettings& settings, bool force)
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
}
if (m_settings.m_navId != settings.m_navId)
{
// Reset state when navId changes, so we don't report old ident for new navId
m_binSampleCnt = 0;
m_binCnt = 0;
m_identNoise = 0.0001f;
for (int i = 0; i < m_identBins; i++)
{
m_identMaxs[i] = 0.0f;
}
m_ident = "";
m_refGoertzel.reset();
m_varGoertzel.reset();
}
m_settings = settings;
}

View File

@ -34,7 +34,6 @@ if(NOT SERVER_MODE)
set(vor_HEADERS
${vor_HEADERS}
vorlocalizergui.h
navaid.h
)
set(TARGET_NAME vorlocalizer)

View File

@ -1,368 +0,0 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "util/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();
QLocale cLocale(QLocale::C);
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 = cLocale.toFloat(latitudeString);
}
else if (idx == longitudeCol)
{
longitudeString = p;
longitudeLen = strlen(longitudeString)-1;
longitudeString[longitudeLen] = '\0';
longitude = cLocale.toFloat(longitudeString);
}
else if (idx == elevationCol)
{
elevationString = p;
elevationLen = strlen(elevationString)-1;
elevationString[elevationLen] = '\0';
elevation = cLocale.toFloat(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

@ -2,7 +2,7 @@
<h2>Introduction</h2>
This plugin can control and receive information from single channel VOR demodulators (see [VOR single channel demodulator](../../channelrx/demodvorsc/readme.md) for details) and collate information from multiple VOR demodulators in order to show your position on a map.
This plugin can control and receive information from VOR demodulators (see [VOR demodulator](../../channelrx/demodvor/readme.md) for details) and collate information from multiple VOR demodulators in order to show your position on a map.
<h2>Interface</h2>
@ -19,7 +19,7 @@ There are 3 sections in this interface:
<h3>1: Start/Stop plugin</h3>
This button starts or stops the plugin
This button starts or stops the plugin.
<h3>2: Download VOR Database</h3>
@ -35,7 +35,7 @@ Available VOR demodulator channels are allocated to service the selected VORs on
<h3>5: Round robin turn time progress</h3>
Shows the round robin turn time progress
Shows the round robin turn time progress.
<h3>6: Force averaging over round robin turn time</h3>
@ -55,10 +55,6 @@ Channels may be used in round robin turns if their number is not enough to cover
When there is more than one turn for a device valid radial directions are averaged and the resulting average is used during the round robin loop. Averaging also takes place for reference and variable signal levels.
<h3>9: Refresh VOR demodulators list and allocation</h3>
Use this button to (re)scan the available VOR demodulators in the SDRangel instance and (re)run the round robin allocation.
<h2>B: VOR Table</h2>
The VOR table displays information about selected VORs. To select or deselect a VOR, double click it on the map. The information displayed includes:
@ -67,7 +63,6 @@ The VOR table displays information about selected VORs. To select or deselect a
* Name - The name of the VOR. For example: 'LONDON'.
* Freq (MHz) - The center frequency the VOR transmits on in MHz. The frequency is highlighted in green when the VOR is serviced by a demodulator.
* Nav Id - This is the VOR unique identifier from the VOR database.
* 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.

View File

@ -47,258 +47,6 @@
#include "vorlocalizersettings.h"
#include "vorlocalizergui.h"
static const char *countryCodes[] = {
"ad",
"ae",
"af",
"ag",
"ai",
"al",
"am",
"an",
"ao",
"aq",
"ar",
"as",
"at",
"au",
"aw",
"ax",
"az",
"ba",
"bb",
"bd",
"be",
"bf",
"bg",
"bh",
"bi",
"bj",
"bl",
"bm",
"bn",
"bo",
"bq",
"br",
"bs",
"bt",
"bv",
"bw",
"by",
"bz",
"ca",
"cc",
"cd",
"cf",
"cg",
"ch",
"ci",
"ck",
"cl",
"cm",
"cn",
"co",
"cr",
"cu",
"cv",
"cw",
"cx",
"cy",
"cz",
"de",
"dj",
"dk",
"dm",
"do",
"dz",
"ec",
"ee",
"eg",
"eh",
"er",
"es",
"et",
"fi",
"fj",
"fk",
"fm",
"fo",
"fr",
"ga",
"gb",
"ge",
"gf",
"gg",
"gh",
"gi",
"gl",
"gm",
"gn",
"gp",
"gq",
"gr",
"gs",
"gt",
"gu",
"gw",
"gy",
"hk",
"hm",
"hn",
"hr",
"hu",
"id",
"ie",
"il",
"im",
"in",
"io",
"iq",
"ir",
"is",
"it",
"je",
"jm",
"jo",
"jp",
"ke",
"kg",
"kh",
"ki",
"km",
"kn",
"kp",
"kr",
"kw",
"ky",
"kz",
"la",
"lb",
"lc",
"li",
"lk",
"lr",
"ls",
"lt",
"lu",
"lv",
"ly",
"ma",
"mc",
"md",
"me",
"mf",
"mg",
"mh",
"mk",
"ml",
"mm",
"mn",
"mo",
"mp",
"mq",
"mr",
"ms",
"mt",
"mu",
"mv",
"mw",
"mx",
"my",
"mz",
"na",
"nc",
"ne",
"nf",
"ng",
"ni",
"nl",
"no",
"np",
"nr",
"nu",
"nz",
"om",
"pa",
"pe",
"pf",
"pg",
"ph",
"pk",
"pl",
"pm",
"pn",
"pr",
"ps",
"pt",
"pw",
"py",
"qa",
"re",
"ro",
"rs",
"ru",
"rw",
"sa",
"sb",
"sc",
"sd",
"se",
"sg",
"sh",
"si",
"sj",
"sk",
"sl",
"sm",
"sn",
"so",
"sr",
"ss",
"st",
"sv",
"sx",
"sy",
"sz",
"tc",
"td",
"tf",
"tg",
"th",
"tj",
"tk",
"tl",
"tm",
"tn",
"to",
"tr",
"tt",
"tv",
"tw",
"tz",
"ua",
"ug",
"um",
"us",
"uy",
"uz",
"va",
"vc",
"ve",
"vg",
"vi",
"vn",
"vu",
"wf",
"ws",
"ye",
"yt",
"za",
"zm",
"zw",
nullptr
};
// Lats and longs in decimal degrees. Distance in metres. Bearing in degrees.
// https://www.movable-type.co.uk/scripts/latlong.html
static void calcRadialEndPoint(float startLatitude, float startLongitude, float distance, float bearing, float &endLatitude, float &endLongitude)
@ -389,7 +137,6 @@ VORGUI::VORGUI(NavAid *navAid, VORLocalizerGUI *gui) :
// These are deleted by QTableWidget
m_nameItem = new QTableWidgetItem();
m_frequencyItem = new QTableWidgetItem();
m_navIdItem = new QTableWidgetItem();
m_radialItem = new QTableWidgetItem();
m_identItem = new QTableWidgetItem();
m_morseItem = new QTableWidgetItem();
@ -610,7 +357,6 @@ void VORLocalizerGUI::resizeTable()
ui->vorData->setRowCount(row + 1);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_NAME, new QTableWidgetItem("White Sulphur Springs"));
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_FREQUENCY, new QTableWidgetItem("Freq (MHz) "));
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_NAVID, new QTableWidgetItem("99999999"));
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_IDENT, new QTableWidgetItem("Ident "));
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_MORSE, new QTableWidgetItem(Morse::toSpacedUnicode(morse)));
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_RADIAL, new QTableWidgetItem("Radial (o) "));
@ -684,7 +430,6 @@ void VORLocalizerGUI::selectVOR(VORGUI *vorGUI, bool selected)
ui->vorData->setRowCount(row + 1);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_NAME, vorGUI->m_nameItem);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_FREQUENCY, vorGUI->m_frequencyItem);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_NAVID, vorGUI->m_navIdItem);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_IDENT, vorGUI->m_identItem);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_MORSE, vorGUI->m_morseItem);
ui->vorData->setItem(row, VORLocalizerSettings::VOR_COL_RADIAL, vorGUI->m_radialItem);
@ -702,7 +447,7 @@ void VORLocalizerGUI::selectVOR(VORGUI *vorGUI, bool selected)
// Add to settings to create corresponding demodulator
m_settings.m_subChannelSettings.insert(navId, VORLocalizerSubChannelSettings{
navId,
vorGUI->m_navAid->m_frequencykHz * 1000,
(int)(vorGUI->m_navAid->m_frequencykHz * 1000),
false
});
@ -725,23 +470,21 @@ void VORLocalizerGUI::selectVOR(VORGUI *vorGUI, bool selected)
void VORLocalizerGUI::updateVORs()
{
m_vorModel.removeAllVORs();
QHash<int, NavAid *>::iterator i = m_vors->begin();
AzEl azEl = m_azEl;
while (i != m_vors->end())
for (auto vor : m_vors)
{
NavAid *vor = i.value();
if (vor->m_type.contains("VOR")) // Exclude DMEs
{
// Calculate distance to VOR from My Position
azEl.setTarget(vor->m_latitude, vor->m_longitude, Units::feetToMetres(vor->m_elevation));
azEl.calculate();
// Calculate distance to VOR from My Position
azEl.setTarget(vor->m_latitude, vor->m_longitude, Units::feetToMetres(vor->m_elevation));
azEl.calculate();
// Only display VOR if in range
if (azEl.getDistance() <= 200000) {
m_vorModel.addVOR(vor);
// Only display VOR if in range
if (azEl.getDistance() <= 200000) {
m_vorModel.addVOR(vor);
}
}
++i;
}
}
@ -807,41 +550,46 @@ bool VORLocalizerGUI::handleMessage(const Message& message)
int subChannelId = report.getSubChannelId();
VORGUI *vorGUI = m_selectedVORs.value(subChannelId);
if (vorGUI)
{
// Display radial and signal magnitudes in table
// Display radial and signal magnitudes in table
Real varMagDB = std::round(20.0*std::log10(report.getVarMag()));
Real refMagDB = std::round(20.0*std::log10(report.getRefMag()));
Real varMagDB = std::round(20.0*std::log10(report.getVarMag()));
Real refMagDB = std::round(20.0*std::log10(report.getRefMag()));
bool validRadial = report.getValidRadial();
vorGUI->m_radialItem->setData(Qt::DisplayRole, std::round(report.getRadial()));
bool validRadial = report.getValidRadial();
vorGUI->m_radialItem->setData(Qt::DisplayRole, std::round(report.getRadial()));
vorGUI->m_navIdItem->setData(Qt::DisplayRole, subChannelId);
if (validRadial) {
vorGUI->m_radialItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_radialItem->setForeground(QBrush(Qt::red));
}
if (validRadial) {
vorGUI->m_radialItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_radialItem->setForeground(QBrush(Qt::red));
vorGUI->m_refMagItem->setData(Qt::DisplayRole, refMagDB);
if (report.getValidRefMag()) {
vorGUI->m_refMagItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_refMagItem->setForeground(QBrush(Qt::red));
}
vorGUI->m_varMagItem->setData(Qt::DisplayRole, varMagDB);
if (report.getValidVarMag()) {
vorGUI->m_varMagItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_varMagItem->setForeground(QBrush(Qt::red));
}
// Update radial on map
m_vorModel.setRadial(subChannelId, validRadial, report.getRadial());
}
vorGUI->m_refMagItem->setData(Qt::DisplayRole, refMagDB);
if (report.getValidRefMag()) {
vorGUI->m_refMagItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_refMagItem->setForeground(QBrush(Qt::red));
else
{
qDebug() << "VORLocalizerGUI::handleMessage: Got MsgReportRadial for non-existant subChannelId " << subChannelId;
}
vorGUI->m_varMagItem->setData(Qt::DisplayRole, varMagDB);
if (report.getValidVarMag()) {
vorGUI->m_varMagItem->setForeground(QBrush(Qt::white));
} else {
vorGUI->m_varMagItem->setForeground(QBrush(Qt::red));
}
// Update radial on map
m_vorModel.setRadial(subChannelId, validRadial, report.getRadial());
return true;
}
else if (VORLocalizerReport::MsgReportIdent::match(message))
@ -850,38 +598,45 @@ bool VORLocalizerGUI::handleMessage(const Message& message)
int subChannelId = report.getSubChannelId();
VORGUI *vorGUI = m_selectedVORs.value(subChannelId);
QString ident = report.getIdent();
// Convert Morse to a string
QString identString = Morse::toString(ident);
// Idents should only be two or three characters, so filter anything else
// other than TEST which indicates a VOR is under maintainance (may also be TST)
if (((identString.size() >= 2) && (identString.size() <= 3)) || (identString == "TEST"))
if (vorGUI)
{
vorGUI->m_rxIdentItem->setText(identString);
vorGUI->m_rxMorseItem->setText(Morse::toSpacedUnicode(ident));
if (vorGUI->m_navAid->m_ident == identString)
QString ident = report.getIdent();
// Convert Morse to a string
QString identString = Morse::toString(ident);
// Idents should only be two or three characters, so filter anything else
// other than TEST which indicates a VOR is under maintainance (may also be TST)
if (((identString.size() >= 2) && (identString.size() <= 3)) || (identString == "TEST"))
{
// Set colour to green if matching expected ident
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::green));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::green));
vorGUI->m_rxIdentItem->setText(identString);
vorGUI->m_rxMorseItem->setText(Morse::toSpacedUnicode(ident));
if (vorGUI->m_navAid->m_ident == identString)
{
// Set colour to green if matching expected ident
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::green));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::green));
}
else
{
// Set colour to green if not matching expected ident
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::red));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::red));
}
}
else
{
// Set colour to green if not matching expected ident
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::red));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::red));
// Set yellow to indicate we've filtered something (unless red)
if (vorGUI->m_rxIdentItem->foreground().color() != Qt::red)
{
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::yellow));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::yellow));
}
}
}
else
{
// Set yellow to indicate we've filtered something (unless red)
if (vorGUI->m_rxIdentItem->foreground().color() != Qt::red)
{
vorGUI->m_rxIdentItem->setForeground(QBrush(Qt::yellow));
vorGUI->m_rxMorseItem->setForeground(QBrush(Qt::yellow));
}
qDebug() << "VORLocalizerGUI::handleMessage: Got MsgReportIdent for non-existant subChannelId " << subChannelId;
}
return true;
@ -938,168 +693,17 @@ void VORLocalizerGUI::handleInputMessages()
}
}
qint64 VORLocalizerGUI::fileAgeInDays(QString filename)
{
QFile file(filename);
if (file.exists())
{
QDateTime modified = file.fileTime(QFileDevice::FileModificationTime);
if (modified.isValid()) {
return modified.daysTo(QDateTime::currentDateTime());
} else {
return -1;
}
}
return -1;
}
bool VORLocalizerGUI::confirmDownload(QString filename)
{
qint64 age = fileAgeInDays(filename);
if ((age == -1) || (age > 100))
{
return true;
}
else
{
QMessageBox::StandardButton reply;
if (age == 0) {
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
} else if (age == 1) {
reply = QMessageBox::question(this, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
} else {
reply = QMessageBox::question(this, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No);
}
return reply == QMessageBox::Yes;
}
}
QString VORLocalizerGUI::getDataDir()
{
// Get directory to store app data in
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
// First dir is writable
return locations[0];
}
QString VORLocalizerGUI::getOpenAIPVORDBFilename(int i)
{
if (countryCodes[i] != nullptr) {
return getDataDir() + "/" + countryCodes[i] + "_nav.aip";
} else {
return "";
}
}
QString VORLocalizerGUI::getOpenAIPVORDBURL(int i)
{
if (countryCodes[i] != nullptr) {
return QString(OPENAIP_NAVAIDS_URL).arg(countryCodes[i]);
} else {
return "";
}
}
QString VORLocalizerGUI::getVORDBFilename()
{
return getDataDir() + "/vorDatabase.csv";
}
void VORLocalizerGUI::updateDownloadProgress(qint64 bytesRead, qint64 totalBytes)
{
if (m_progressDialog)
{
m_progressDialog->setMaximum(totalBytes);
m_progressDialog->setValue(bytesRead);
}
}
void VORLocalizerGUI::downloadFinished(const QString& filename, bool success)
{
bool closeDialog = true;
if (success)
{
if (filename == getVORDBFilename())
{
m_vors = NavAid::readNavAidsDB(filename);
if (m_vors != nullptr) {
updateVORs();
}
}
else if (filename == getOpenAIPVORDBFilename(m_countryIndex))
{
m_countryIndex++;
if (countryCodes[m_countryIndex] != nullptr)
{
QString vorDBFile = getOpenAIPVORDBFilename(m_countryIndex);
QString urlString = getOpenAIPVORDBURL(m_countryIndex);
QUrl dbURL(urlString);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(urlString));
m_progressDialog->setValue(m_countryIndex);
m_dlm.download(dbURL, vorDBFile);
closeDialog = false;
}
else
{
readNavAids();
if (m_vors) {
updateVORs();
}
}
}
else
{
qDebug() << "VORLocalizerGUI::downloadFinished: Unexpected filename: " << filename;
}
}
else
{
qDebug() << "VORLocalizerGUI::downloadFinished: Failed: " << filename;
QMessageBox::warning(this, "Download failed", QString("Failed to download %1").arg(filename));
}
if (closeDialog && m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void VORLocalizerGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
VORLocalizer::MsgStartStop *message = VORLocalizer::MsgStartStop::create(checked);
m_vorLocalizer->getInputMessageQueue()->push(message);
}
}
void VORLocalizerGUI::on_getOurAirportsVORDB_clicked()
{
// Don't try to download while already in progress
if (m_progressDialog == nullptr)
{
QString vorDBFile = getVORDBFilename();
if (confirmDownload(vorDBFile))
if (checked)
{
// Download OurAirports navaid database to disk
QUrl dbURL(QString(OURAIRPORTS_NAVAIDS_URL));
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setMinimumDuration(500);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(OURAIRPORTS_NAVAIDS_URL));
QNetworkReply *reply = m_dlm.download(dbURL, vorDBFile);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(updateDownloadProgress(qint64,qint64)));
// Refresh channels in case device b/w has changed
channelsRefresh();
}
}
}
@ -1109,33 +713,51 @@ void VORLocalizerGUI::on_getOpenAIPVORDB_clicked()
// Don't try to download while already in progress
if (!m_progressDialog)
{
m_countryIndex = 0;
QString vorDBFile = getOpenAIPVORDBFilename(m_countryIndex);
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setMaximum(OpenAIP::m_countryCodes.size());
m_progressDialog->setCancelButton(nullptr);
if (confirmDownload(vorDBFile))
{
// Download OpenAIP XML to disk
QString urlString = getOpenAIPVORDBURL(m_countryIndex);
QUrl dbURL(urlString);
m_progressDialog = new QProgressDialog(this);
m_progressDialog->setCancelButton(nullptr);
m_progressDialog->setMinimumDuration(500);
m_progressDialog->setMaximum(sizeof(countryCodes)/sizeof(countryCodes[0]));
m_progressDialog->setValue(0);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(urlString));
m_dlm.download(dbURL, vorDBFile);
}
m_openAIP.downloadNavAids();
}
}
void VORLocalizerGUI::readNavAids()
{
m_vors = new QHash<int, NavAid *>();
m_vors = OpenAIP::readNavAids();
updateVORs();
}
for (int countryIndex = 0; countryCodes[countryIndex] != nullptr; countryIndex++)
void VORLocalizerGUI::downloadingURL(const QString& url)
{
if (m_progressDialog)
{
QString vorDBFile = getOpenAIPVORDBFilename(countryIndex);
NavAid::readNavAidsXML(m_vors, vorDBFile);
m_progressDialog->setLabelText(QString("Downloading %1.").arg(url));
m_progressDialog->setValue(m_progressDialog->value() + 1);
}
}
void VORLocalizerGUI::downloadError(const QString& error)
{
QMessageBox::critical(this, "VOR Localizer", error);
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
void VORLocalizerGUI::downloadNavAidsFinished()
{
if (m_progressDialog) {
m_progressDialog->setLabelText("Reading NAVAIDs.");
}
readNavAids();
if (m_progressDialog)
{
m_progressDialog->close();
delete m_progressDialog;
m_progressDialog = nullptr;
}
}
@ -1160,7 +782,7 @@ void VORLocalizerGUI::on_centerShift_valueChanged(int value)
applySettings();
}
void VORLocalizerGUI::on_channelsRefresh_clicked()
void VORLocalizerGUI::channelsRefresh()
{
if (m_doApplySettings)
{
@ -1231,7 +853,6 @@ VORLocalizerGUI::VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
m_tickCount(0),
m_progressDialog(nullptr),
m_vorModel(this),
m_vors(nullptr),
m_lastFeatureState(0),
m_rrSecondsCount(0)
{
@ -1250,7 +871,9 @@ VORLocalizerGUI::VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
m_muteIcon.addPixmap(QPixmap("://sound_on.png"), QIcon::Normal, QIcon::Off);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &VORLocalizerGUI::downloadFinished);
connect(&m_openAIP, &OpenAIP::downloadingURL, this, &VORLocalizerGUI::downloadingURL);
connect(&m_openAIP, &OpenAIP::downloadError, this, &VORLocalizerGUI::downloadError);
connect(&m_openAIP, &OpenAIP::downloadNavAidsFinished, this, &VORLocalizerGUI::downloadNavAidsFinished);
m_vorLocalizer = reinterpret_cast<VORLocalizer*>(feature);
m_vorLocalizer->setMessageQueueToGUI(getInputMessageQueue());
@ -1293,22 +916,7 @@ VORLocalizerGUI::VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
}
// Read in VOR information if it exists
bool useOurAirports = false;
if (useOurAirports)
{
m_vors = NavAid::readNavAidsDB(getVORDBFilename());
ui->getOpenAIPVORDB->setVisible(false);
}
else
{
readNavAids();
ui->getOurAirportsVORDB->setVisible(false);
}
if (m_vors) {
updateVORs();
}
readNavAids();
// Resize the table using dummy data
resizeTable();
@ -1338,14 +946,33 @@ VORLocalizerGUI::VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
ui->rrTurnTimeProgress->setValue(0);
ui->rrTurnTimeProgress->setToolTip(tr("Round robin turn time %1s").arg(0));
// Get updated when position changes
connect(&MainCore::instance()->getSettings(), &MainSettings::preferenceChanged, this, &VORLocalizerGUI::preferenceChanged);
displaySettings();
applySettings(true);
connect(&m_redrawMapTimer, &QTimer::timeout, this, &VORLocalizerGUI::redrawMap);
m_redrawMapTimer.setSingleShot(true);
ui->map->installEventFilter(this);
makeUIConnections();
// Update channel list when added/removed
connect(MainCore::instance(), &MainCore::channelAdded, this, &VORLocalizerGUI::channelsRefresh);
connect(MainCore::instance(), &MainCore::channelRemoved, this, &VORLocalizerGUI::channelsRefresh);
// Also replan when device changed (as bandwidth may change or may becomed fixed center freq)
connect(MainCore::instance(), &MainCore::deviceChanged, this, &VORLocalizerGUI::channelsRefresh);
// List already opened channels
channelsRefresh();
}
VORLocalizerGUI::~VORLocalizerGUI()
{
disconnect(&m_redrawMapTimer, &QTimer::timeout, this, &VORLocalizerGUI::redrawMap);
m_redrawMapTimer.stop();
delete ui;
qDeleteAll(m_vors);
}
void VORLocalizerGUI::blockApplySettings(bool block)
@ -1456,13 +1083,107 @@ void VORLocalizerGUI::tick()
}
}
void VORLocalizerGUI::preferenceChanged(int elementType)
{
Preferences::ElementType pref = (Preferences::ElementType)elementType;
if ((pref == Preferences::Latitude) || (pref == Preferences::Longitude) || (pref == Preferences::Altitude))
{
Real stationLatitude = MainCore::instance()->getSettings().getLatitude();
Real stationLongitude = MainCore::instance()->getSettings().getLongitude();
Real stationAltitude = MainCore::instance()->getSettings().getAltitude();
if ( (stationLatitude != m_azEl.getLocationSpherical().m_latitude)
|| (stationLongitude != m_azEl.getLocationSpherical().m_longitude)
|| (stationAltitude != m_azEl.getLocationSpherical().m_altitude))
{
m_azEl.setLocation(stationLatitude, stationLongitude, stationAltitude);
// Update distances and what is visible
updateVORs();
// Update icon position on Map
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild<QObject*>("map");
if (map != nullptr)
{
QObject *stationObject = map->findChild<QObject*>("station");
if(stationObject != NULL)
{
QGeoCoordinate coords = stationObject->property("coordinate").value<QGeoCoordinate>();
coords.setLatitude(stationLatitude);
coords.setLongitude(stationLongitude);
coords.setAltitude(stationAltitude);
stationObject->setProperty("coordinate", QVariant::fromValue(coords));
}
}
}
}
if (pref == Preferences::StationName)
{
// Update icon label on Map
QQuickItem *item = ui->map->rootObject();
QObject *map = item->findChild<QObject*>("map");
if (map != nullptr)
{
QObject *stationObject = map->findChild<QObject*>("station");
if(stationObject != NULL) {
stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName()));
}
}
}
}
void VORLocalizerGUI::redrawMap()
{
// An awful workaround for https://bugreports.qt.io/browse/QTBUG-100333
// Also used in ADS-B demod
QQuickItem *item = ui->map->rootObject();
if (item)
{
QObject *object = item->findChild<QObject*>("map");
if (object)
{
double zoom = object->property("zoomLevel").value<double>();
object->setProperty("zoomLevel", QVariant::fromValue(zoom+1));
object->setProperty("zoomLevel", QVariant::fromValue(zoom));
}
}
}
void VORLocalizerGUI::showEvent(QShowEvent *event)
{
if (!event->spontaneous())
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position when window is first displayed
m_redrawMapTimer.start(500);
}
}
bool VORLocalizerGUI::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->map)
{
if (event->type() == QEvent::Resize)
{
// Workaround for https://bugreports.qt.io/browse/QTBUG-100333
// MapQuickItems can be in wrong position after vertical resize
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
QSize oldSize = resizeEvent->oldSize();
QSize size = resizeEvent->size();
if (oldSize.height() != size.height()) {
redrawMap();
}
}
}
return false;
}
void VORLocalizerGUI::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &VORLocalizerGUI::on_startStop_toggled);
QObject::connect(ui->getOurAirportsVORDB, &QPushButton::clicked, this, &VORLocalizerGUI::on_getOurAirportsVORDB_clicked);
QObject::connect(ui->getOpenAIPVORDB, &QPushButton::clicked, this, &VORLocalizerGUI::on_getOpenAIPVORDB_clicked);
QObject::connect(ui->magDecAdjust, &ButtonSwitch::toggled, this, &VORLocalizerGUI::on_magDecAdjust_toggled);
QObject::connect(ui->rrTime, &QDial::valueChanged, this, &VORLocalizerGUI::on_rrTime_valueChanged);
QObject::connect(ui->centerShift, &QDial::valueChanged, this, &VORLocalizerGUI::on_centerShift_valueChanged);
QObject::connect(ui->channelsRefresh, &QPushButton::clicked, this, &VORLocalizerGUI::on_channelsRefresh_clicked);
}

View File

@ -37,10 +37,10 @@
#include "util/messagequeue.h"
#include "util/httpdownloadmanager.h"
#include "util/azel.h"
#include "util/openaip.h"
#include "settings/rollupstate.h"
#include "vorlocalizersettings.h"
#include "navaid.h"
class PluginAPI;
class FeatureUISet;
@ -64,7 +64,6 @@ public:
QTableWidgetItem *m_nameItem;
QTableWidgetItem *m_frequencyItem;
QTableWidgetItem *m_navIdItem;
QTableWidgetItem *m_identItem;
QTableWidgetItem *m_morseItem;
QTableWidgetItem *m_radialItem;
@ -239,15 +238,17 @@ private:
QMenu *menu; // Column select context menu
HttpDownloadManager m_dlm;
QProgressDialog *m_progressDialog;
OpenAIP m_openAIP;
int m_countryIndex;
VORModel m_vorModel;
QHash<int, NavAid *> *m_vors;
QList<NavAid *> m_vors;
QHash<int, VORGUI *> m_selectedVORs;
AzEl m_azEl; // Position of station
QIcon m_muteIcon;
QTimer m_statusTimer;
int m_lastFeatureState;
int m_rrSecondsCount;
QTimer m_redrawMapTimer;
explicit VORLocalizerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~VORLocalizerGUI();
@ -256,6 +257,7 @@ private:
void applySettings(bool force = false);
void displaySettings();
bool handleMessage(const Message& message);
void redrawMap();
void makeUIConnections();
void resizeTable();
@ -264,35 +266,31 @@ private:
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);
void updateChannelList();
private slots:
void on_startStop_toggled(bool checked);
void on_getOurAirportsVORDB_clicked();
void on_getOpenAIPVORDB_clicked();
void on_magDecAdjust_toggled(bool checked);
void on_rrTime_valueChanged(int value);
void on_centerShift_valueChanged(int value);
void on_channelsRefresh_clicked();
void channelsRefresh();
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 updateStatus();
void tick();
void downloadingURL(const QString& url);
void downloadError(const QString& error);
void downloadNavAidsFinished();
void preferenceChanged(int elementType);
virtual void showEvent(QShowEvent *event);
virtual bool eventFilter(QObject *obj, QEvent *event);
};
#endif // INCLUDE_VORLOCALIZERGUI_H

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>462</width>
<width>470</width>
<height>850</height>
</rect>
</property>
@ -40,7 +40,7 @@
<x>0</x>
<y>0</y>
<width>461</width>
<height>61</height>
<height>31</height>
</rect>
</property>
<property name="minimumSize">
@ -85,23 +85,6 @@
</property>
</widget>
</item>
<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">
@ -136,6 +119,13 @@
</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="rrTimeLabel">
<property name="text">
@ -180,7 +170,7 @@
</size>
</property>
<property name="toolTip">
<string>Sound volume (%)</string>
<string>Round robin turn time (s)</string>
</property>
<property name="text">
<string>20s</string>
@ -223,10 +213,17 @@ QToolTip{background-color: white; color: black;}</string>
</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="centerShiftLabel">
<property name="text">
<string>Sh</string>
<string>Δcf</string>
</property>
</widget>
</item>
@ -275,22 +272,12 @@ QToolTip{background-color: white; color: black;}</string>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="vorsLayout">
<item>
<widget class="QLabel" name="channelsLabel">
<property name="text">
@ -306,27 +293,7 @@ QToolTip{background-color: white; color: black;}</string>
</widget>
</item>
<item>
<widget class="QPushButton" name="channelsRefresh">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Refresh VOR channels available</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
@ -345,14 +312,14 @@ QToolTip{background-color: white; color: black;}</string>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>110</y>
<width>461</width>
<height>145</height>
<x>10</x>
<y>50</y>
<width>441</width>
<height>710</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -366,178 +333,146 @@ QToolTip{background-color: white; color: black;}</string>
<property name="windowTitle">
<string>VORs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>3</number>
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
<number>0</number>
</property>
<property name="rightMargin">
<number>3</number>
<number>0</number>
</property>
<property name="bottomMargin">
<number>3</number>
<number>0</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>Nav Id</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>258</y>
<width>461</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">
<widget class="QSplitter" name="splitter">
<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 name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTableWidget" name="vorData">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<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>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>
<widget class="QQuickWidget" name="map">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>4</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>
</widget>
</item>
</layout>
@ -562,9 +497,7 @@ QToolTip{background-color: white; color: black;}</string>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>getOurAirportsVORDB</tabstop>
<tabstop>vorData</tabstop>
<tabstop>map</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>

View File

@ -77,18 +77,17 @@ struct VORLocalizerSettings
int m_workspaceIndex;
QByteArray m_geometryBytes;
static const int VORDEMOD_COLUMNS = 11;
static const int VORDEMOD_COLUMNS = 10;
static const int VOR_COL_NAME = 0;
static const int VOR_COL_FREQUENCY = 1;
static const int VOR_COL_NAVID = 2;
static const int VOR_COL_IDENT = 3;
static const int VOR_COL_MORSE = 4;
static const int VOR_COL_RX_IDENT = 5;
static const int VOR_COL_RX_MORSE = 6;
static const int VOR_COL_RADIAL = 7;
static const int VOR_COL_REF_MAG = 8;
static const int VOR_COL_VAR_MAG = 9;
static const int VOR_COL_MUTE = 10;
static const int VOR_COL_IDENT = 2;
static const int VOR_COL_MORSE = 3;
static const int VOR_COL_RX_IDENT = 4;
static const int VOR_COL_RX_MORSE = 5;
static const int VOR_COL_RADIAL = 6;
static const int VOR_COL_REF_MAG = 7;
static const int VOR_COL_VAR_MAG = 8;
static const int VOR_COL_MUTE = 9;
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

View File

@ -24,7 +24,11 @@
#include "SWGErrorResponse.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
#include "dsp/devicesamplesource.h"
#include "dsp/devicesamplesink.h"
#include "channel/channelapi.h"
#include "channel/channelwebapiutils.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
#include "maincore.h"
@ -78,6 +82,7 @@ void VorLocalizerWorker::started()
m_rrTimer.start(m_settings.m_rrTime * 1000);
disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
}
void VorLocalizerWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
@ -205,6 +210,53 @@ void VorLocalizerWorker::updateHardware()
m_mutex.unlock();
}
quint64 VorLocalizerWorker::getDeviceCenterFrequency(int deviceIndex)
{
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
if (deviceIndex < deviceSets.size())
{
DeviceSet *deviceSet = deviceSets[deviceIndex];
if (deviceSet->m_deviceSourceEngine)
{
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
return source->getCenterFrequency();
}
else if (deviceSet->m_deviceSinkEngine)
{
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
return sink->getCenterFrequency();
}
}
return 0;
}
int VorLocalizerWorker::getDeviceSampleRate(int deviceIndex)
{
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
if (deviceIndex < deviceSets.size())
{
DeviceSet *deviceSet = deviceSets[deviceIndex];
if (deviceSet->m_deviceSourceEngine)
{
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
return source->getSampleRate();
}
else if (deviceSet->m_deviceSinkEngine)
{
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
return sink->getSampleRate();
}
}
return 0;
}
// Does this device have a center frequency setting (FileInput doesn't)
bool VorLocalizerWorker::hasCenterFrequencySetting(int deviceIndex)
{
double deviceFrequency;
return !ChannelWebAPIUtils::getCenterFrequency(deviceIndex, deviceFrequency);
}
void VorLocalizerWorker::removeVORChannel(int navId)
{
qDebug("VorLocalizerWorker::removeVORChannel: %d", navId);
@ -294,7 +346,12 @@ void VorLocalizerWorker::updateChannels()
RRTurnPlan turnPlan(deviceChannel);
int fMin = vorList.front().m_frequency;
int fMax = vorList.back().m_frequency;
int devFreq = (fMin + fMax) / 2;
int devFreq;
if (turnPlan.m_fixedCenterFrequency) {
devFreq = getDeviceCenterFrequency(turnPlan.m_device.m_deviceIndex);
} else {
devFreq = (fMin + fMax) / 2;
}
turnPlan.m_device.m_frequency = devFreq;
int iCh = 0;
@ -321,7 +378,6 @@ void VorLocalizerWorker::updateChannels()
++it;
}
}
iCh++;
}
@ -359,7 +415,12 @@ void VorLocalizerWorker::updateChannels()
RRTurnPlan turnPlan(deviceChannel);
int fMin = vorList.front().m_frequency;
int fMax = vorList.back().m_frequency;
int devFreq = (fMin + fMax) / 2;
int devFreq;
if (turnPlan.m_fixedCenterFrequency) {
devFreq = getDeviceCenterFrequency(turnPlan.m_device.m_deviceIndex);
} else {
devFreq = (fMin + fMax) / 2;
}
turnPlan.m_device.m_frequency = devFreq;
int iCh = 0;
@ -406,69 +467,6 @@ void VorLocalizerWorker::updateChannels()
rrNextTurn();
}
void VorLocalizerWorker::allocateChannel(ChannelAPI *channel, int vorFrequency, int vorNavId, int channelShift)
{
VORLocalizerSettings::AvailableChannel& availableChannel = m_availableChannels->operator[](channel);
qDebug() << "VorLocalizerWorker::allocateChannel:"
<< " vorNavId:" << vorNavId
<< " vorFrequency:" << vorFrequency
<< " channelShift:" << channelShift
<< " deviceIndex:" << availableChannel.m_deviceSetIndex
<< " channelIndex:" << availableChannel.m_channelIndex;
double deviceFrequency = vorFrequency - channelShift;
setDeviceFrequency(availableChannel.m_deviceSetIndex, deviceFrequency);
setChannelShift(availableChannel.m_deviceSetIndex, availableChannel.m_channelIndex, channelShift, vorNavId);
availableChannel.m_navId = vorNavId;
}
void VorLocalizerWorker::setDeviceFrequency(int deviceIndex, double targetFrequency)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
int httpRC;
// Get current device center frequency
httpRC = m_webAPIAdapterInterface->devicesetDeviceSettingsGet(
deviceIndex,
deviceSettingsResponse,
errorResponse
);
if (httpRC/100 != 2)
{
qWarning("VorLocalizerWorker::setDeviceFrequency: get device frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
}
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
// Update centerFrequency
WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", targetFrequency);
QStringList deviceSettingsKeys;
deviceSettingsKeys.append("centerFrequency");
deviceSettingsResponse.init();
deviceSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
httpRC = m_webAPIAdapterInterface->devicesetDeviceSettingsPutPatch(
deviceIndex,
false, // PATCH
deviceSettingsKeys,
deviceSettingsResponse,
errorResponse2
);
if (httpRC/100 == 2)
{
qDebug("VorLocalizerWorker::setDeviceFrequency: set device frequency %f OK", targetFrequency);
}
else
{
qWarning("VorLocalizerWorker::setDeviceFrequency: set device frequency error %d: %s",
httpRC, qPrintable(*errorResponse2.getMessage()));
}
}
void VorLocalizerWorker::setChannelShift(int deviceIndex, int channelIndex, double targetOffset, int vorNavId)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
@ -674,14 +672,16 @@ void VorLocalizerWorker::getChannelsByDevice(
for (; itr != availableChannels->end(); ++itr)
{
devicesChannelsMap[itr->m_deviceSetIndex].m_device.m_deviceIndex = itr->m_deviceSetIndex;
devicesChannelsMap[itr->m_deviceSetIndex].m_bandwidth = itr->m_basebandSampleRate;
devicesChannelsMap[itr->m_deviceSetIndex].m_bandwidth = getDeviceSampleRate(itr->m_deviceSetIndex); // Get b/w of device, not channel, as the latter may be decimated
devicesChannelsMap[itr->m_deviceSetIndex].m_channels.push_back(RRChannel{itr->m_channelAPI, itr->m_channelIndex, 0, -1});
}
QMap<int, RRTurnPlan>::const_iterator itm = devicesChannelsMap.begin();
QMap<int, RRTurnPlan>::iterator itm = devicesChannelsMap.begin();
devicesChannels.clear();
for (; itm != devicesChannelsMap.end(); ++itm) {
for (; itm != devicesChannelsMap.end(); ++itm)
{
itm->m_fixedCenterFrequency = hasCenterFrequencySetting(itm->m_device.m_deviceIndex);
devicesChannels.push_back(*itm);
}
@ -700,23 +700,32 @@ void VorLocalizerWorker::rrNextTurn()
unsigned int turnCount = m_rrTurnCounters[iDevPlan];
int deviceIndex = rrPlan[turnCount].m_device.m_deviceIndex;
int deviceFrequency = rrPlan[turnCount].m_device.m_frequency - m_settings.m_centerShift;
qDebug() << "VorLocalizerWorker::rrNextTurn: "
<< "turn:" << turnCount
<< "device:" << deviceIndex
<< "frequency:" << deviceFrequency - m_settings.m_centerShift;
setDeviceFrequency(deviceIndex, deviceFrequency);
if (!rrPlan[turnCount].m_fixedCenterFrequency) {
ChannelWebAPIUtils::setCenterFrequency(deviceIndex, deviceFrequency);
}
for (auto channel : rrPlan[turnCount].m_channels)
{
int shift = channel.m_frequencyShift;
if (!rrPlan[turnCount].m_fixedCenterFrequency) {
shift += m_settings.m_centerShift;
}
qDebug() << "VorLocalizerWorker::rrNextTurn: "
<< "device:" << deviceIndex
<< "channel:" << channel.m_channelIndex
<< "shift:" << channel.m_frequencyShift + m_settings.m_centerShift
<< "shift:" << shift
<< "navId:" << channel.m_navId;
setChannelShift(
deviceIndex,
channel.m_channelIndex,
channel.m_frequencyShift + m_settings.m_centerShift,
shift,
channel.m_navId
);
m_channelAllocations[channel.m_navId] = ChannelAllocation{

View File

@ -121,6 +121,7 @@ private:
RRDevice m_device;
int m_bandwidth;
std::vector<RRChannel> m_channels;
bool m_fixedCenterFrequency; // Devices such as FileInput that can't have center freq changed
RRTurnPlan() = default;
RRTurnPlan(const RRTurnPlan&) = default;
@ -157,10 +158,11 @@ private:
void removeVORChannel(int navId);
void addVORChannel(const VORLocalizerSubChannelSettings& subChannelSettings);
void updateChannels(); //!< (re)allocate channels to service VORs
void allocateChannel(ChannelAPI *channel, int vorFrequency, int vorNavId, int channelShift);
void setDeviceFrequency(int deviceIndex, double targetFrequency);
void setChannelShift(int deviceIndex, int channelIndex, double targetOffset, int vorNavId);
void setAudioMute(int vorNavId, bool audioMute);
static quint64 getDeviceCenterFrequency(int deviceIndex);
static int getDeviceSampleRate(int deviceIndex);
static bool hasCenterFrequencySetting(int deviceIndex);
static void generateIndexCombinations(int length, int subLength, std::vector<std::vector<int>>& indexCombinations);
static void getVORRanges(const QList<VORLocalizerSettings::VORChannel>& vors, int subLength, std::vector<VORRange>& vorRanges);
static void filterVORRanges(std::vector<VORRange>& vorRanges, int thresholdBW);

View File

@ -50,7 +50,6 @@ struct SDRBASE_API Airspace {
};
QString m_category; // A-G, GLIDING, DANGER, PROHIBITED, TMZ
int m_id;
QString m_country; // GB
QString m_name; // BIGGIN HILL ATZ 129.405 - TODO: Extract frequency so we can tune to it
AltLimit m_top; // Top of airspace
@ -139,11 +138,7 @@ struct SDRBASE_API Airspace {
while(xmlReader.readNextStartElement())
{
if (xmlReader.name() == QLatin1String("ID"))
{
airspace->m_id = xmlReader.readElementText().toInt();
}
else if (xmlReader.name() == QLatin1String("COUNTRY"))
if (xmlReader.name() == QLatin1String("COUNTRY"))
{
airspace->m_country = xmlReader.readElementText();
}
@ -237,7 +232,7 @@ struct SDRBASE_API Airspace {
};
struct SDRBASE_API NavAid {
int m_id; // Unique ID needed by VOR feature - Don't use value from database as that's 96-bit
QString m_ident; // 2 or 3 character ident
QString m_type; // NDB, VOR, VOR-DME or VORTAC
QString m_name;
@ -258,6 +253,7 @@ struct SDRBASE_API NavAid {
// OpenAIP XML file
static QList<NavAid *> readXML(const QString &filename)
{
int uniqueId = 1;
QList<NavAid *> navAidInfo;
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
@ -352,6 +348,7 @@ struct SDRBASE_API NavAid {
}
}
NavAid *navAid = new NavAid();
navAid->m_id = uniqueId++;
navAid->m_ident = id;
// Check idents conform to our filtering rules
if (navAid->m_ident.size() < 2) {