From 1975c1748c49de53478d085a683a78ab68002b37 Mon Sep 17 00:00:00 2001 From: srcejon Date: Wed, 4 Jun 2025 18:05:10 +0100 Subject: [PATCH] Add additional demod stats. Add analysis of Mode S frames in order to help filter out false positives. Remove Boost. Use Profiler instead. Fix crash if interrrupted before run. Decode full preamble for Mode S frames. Add support for additional Mode S downlink formats. Allow demod stats to be reset. Add timestamps for buffers, to ensure ordering of frames. Add additional data columns (Aircraft Type, Sideview, Track, Interrogator Code, TCAS, ACAS, RA, Max speed, Version, Length, Width, ADS-B/Mode S frame counts, radius, NACp, NACv, GVA, NIC, SIL, Stops). Add PCE (Preamble Chip Errors) settings for Mode S demod. Remove correlate full preamable setting. Send aircraft state to Map feature for display on PFD. Add support for airline route database. Use combined aircraft database from sdrangel.org rather than OSN database. Add stats table, with demod stats and breakdown of frame types received.. Add button to delete all aircraft from table. Add display of interrogator code coverage. Remove airport elevation setting as now calculated dynamically. Add QNH setting. Add coverage map. Add chart of frame rate and aircraft count. Add table/map orientation setting. Add display of aircraft position uncertainty. Add coloured flight paths with several palettes. Add setting to favour airline livery over aircraft type in 3D model matching. Only use 4 engine map icon for aircraft with 4 engines. Add specialised aircraft map icons (Eurofighter, Spitfire, F35, A400M, Apache, Chinook, Glider) Display aircraft route in labels. Better validate local/global aircraft positions. Add support for tc==31, aircraft operational status frames. Add decoding of Mode S df 0, 11, 16 frames. Add decoding of BDS 0,5 0,8, 0,9. --- plugins/channelrx/demodadsb/adsbdemod.cpp | 19 +- plugins/channelrx/demodadsb/adsbdemod.h | 15 + .../channelrx/demodadsb/adsbdemodbaseband.cpp | 6 + .../demodadsb/adsbdemoddisplaydialog.cpp | 26 +- .../demodadsb/adsbdemoddisplaydialog.ui | 70 +- plugins/channelrx/demodadsb/adsbdemodgui.cpp | 3902 ++++++++++++++--- plugins/channelrx/demodadsb/adsbdemodgui.h | 525 ++- plugins/channelrx/demodadsb/adsbdemodgui.ui | 2135 ++++++--- .../demodadsb/adsbdemodnotificationdialog.cpp | 3 +- .../channelrx/demodadsb/adsbdemodsettings.cpp | 199 +- .../channelrx/demodadsb/adsbdemodsettings.h | 153 +- plugins/channelrx/demodadsb/adsbdemodsink.cpp | 33 +- plugins/channelrx/demodadsb/adsbdemodsink.h | 8 +- .../demodadsb/adsbdemodsinkworker.cpp | 577 ++- .../channelrx/demodadsb/adsbdemodsinkworker.h | 22 +- plugins/channelrx/demodadsb/adsbdemodstats.h | 27 +- 16 files changed, 6089 insertions(+), 1631 deletions(-) diff --git a/plugins/channelrx/demodadsb/adsbdemod.cpp b/plugins/channelrx/demodadsb/adsbdemod.cpp index dee2d7eab..cf7fdc678 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.cpp +++ b/plugins/channelrx/demodadsb/adsbdemod.cpp @@ -17,10 +17,6 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#define BOOST_CHRONO_HEADER_ONLY -#include - -#include #include #include @@ -49,6 +45,7 @@ MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgConfigureADSBDemod, Message) MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgAircraftReport, Message) +MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgResetStats, Message) const char* const ADSBDemod::m_channelIdURI = "sdrangel.channel.adsbdemod"; const char* const ADSBDemod::m_channelId = "ADSBDemod"; @@ -194,6 +191,13 @@ bool ADSBDemod::handleMessage(const Message& cmd) m_aircraftReport = msg.getReport(); return true; } + else if (MsgResetStats::match(cmd)) + { + MsgResetStats& msg = (MsgResetStats&) cmd; + MsgResetStats* rep = new MsgResetStats(msg); + m_basebandSink->getInputMessageQueue()->push(rep); + return true; + } else { return false; @@ -337,9 +341,6 @@ void ADSBDemod::webapiUpdateChannelSettings( if (channelSettingsKeys.contains("samplesPerBit")) { settings.m_samplesPerBit = response.getAdsbDemodSettings()->getSamplesPerBit(); } - if (channelSettingsKeys.contains("correlateFullPreamble")) { - settings.m_correlateFullPreamble = response.getAdsbDemodSettings()->getCorrelateFullPreamble() != 0; - } if (channelSettingsKeys.contains("demodModeS")) { settings.m_demodModeS = response.getAdsbDemodSettings()->getDemodModeS() != 0; } @@ -458,7 +459,6 @@ void ADSBDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res response.getAdsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); response.getAdsbDemodSettings()->setCorrelationThreshold(settings.m_correlationThreshold); response.getAdsbDemodSettings()->setSamplesPerBit(settings.m_samplesPerBit); - response.getAdsbDemodSettings()->setCorrelateFullPreamble(settings.m_correlateFullPreamble ? 1 : 0); response.getAdsbDemodSettings()->setDemodModeS(settings.m_demodModeS ? 1 : 0); response.getAdsbDemodSettings()->setInterpolatorPhaseSteps(settings.m_interpolatorPhaseSteps); response.getAdsbDemodSettings()->setInterpolatorTapsPerPhase(settings.m_interpolatorTapsPerPhase); @@ -587,9 +587,6 @@ void ADSBDemod::webapiReverseSendSettings(const QList& channelSettingsK if (channelSettingsKeys.contains("samplesPerBit") || force) { swgADSBDemodSettings->setSamplesPerBit(settings.m_samplesPerBit); } - if (channelSettingsKeys.contains("correlateFullPreamble") || force) { - swgADSBDemodSettings->setCorrelateFullPreamble(settings.m_correlateFullPreamble ? 1 : 0); - } if (channelSettingsKeys.contains("demodModeS") || force) { swgADSBDemodSettings->setDemodModeS(settings.m_demodModeS ? 1 : 0); } diff --git a/plugins/channelrx/demodadsb/adsbdemod.h b/plugins/channelrx/demodadsb/adsbdemod.h index 41b82030f..313771eb6 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.h +++ b/plugins/channelrx/demodadsb/adsbdemod.h @@ -92,6 +92,21 @@ public: { } }; + class MsgResetStats : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgResetStats* create() + { + return new MsgResetStats(); + } + + private: + MsgResetStats() : + Message() + { } + }; + ADSBDemod(DeviceAPI *deviceAPI); virtual ~ADSBDemod(); virtual void destroy() { delete this; } diff --git a/plugins/channelrx/demodadsb/adsbdemodbaseband.cpp b/plugins/channelrx/demodadsb/adsbdemodbaseband.cpp index cc586310f..bce27b428 100644 --- a/plugins/channelrx/demodadsb/adsbdemodbaseband.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodbaseband.cpp @@ -23,6 +23,7 @@ #include "dsp/downchannelizer.h" #include "adsbdemodbaseband.h" +#include "adsbdemod.h" #include "adsb.h" MESSAGE_CLASS_DEFINITION(ADSBDemodBaseband::MsgConfigureADSBDemodBaseband, Message) @@ -132,6 +133,11 @@ bool ADSBDemodBaseband::handleMessage(const Message& cmd) return true; } + else if (ADSBDemod::MsgResetStats::match(cmd)) + { + m_sink.resetStats(); + return true; + } else { return false; diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp index 44129f423..829f41b49 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp @@ -50,7 +50,6 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWid ui->airportSize->setCurrentIndex((int)settings->m_airportMinimumSize); ui->heliports->setChecked(settings->m_displayHeliports); ui->units->setCurrentIndex((int)settings->m_siUnits); - ui->displayStats->setChecked(settings->m_displayDemodStats); ui->autoResizeTableColumns->setChecked(settings->m_autoResizeTableColumns); ui->aviationstackAPIKey->setText(settings->m_aviationstackAPIKey); ui->checkWXAPIKey->setText(settings->m_checkWXAPIKey); @@ -71,8 +70,12 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWid ui->atcCallsigns->setChecked(settings->m_atcCallsigns); ui->photos->setChecked(settings->m_displayPhotos); ui->verboseModelMatching->setChecked(settings->m_verboseModelMatching); - ui->airfieldElevation->setValue(settings->m_airfieldElevation); + ui->favourLivery->setChecked(settings->m_favourLivery); ui->transitionAltitude->setValue(settings->m_transitionAlt); + for (auto i = ADSBDemodSettings::m_palettes.cbegin(), end = ADSBDemodSettings::m_palettes.cend(); i != end; ++i) { + ui->flightPathPalette->addItem(i.key()); + } + ui->flightPathPalette->setCurrentText(settings->m_flightPathPaletteName); } ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog() @@ -107,16 +110,11 @@ void ADSBDemodDisplayDialog::accept() m_settings->m_displayHeliports = ui->heliports->isChecked(); m_settingsKeys.append("displayHeliports"); } - if (m_settings->m_siUnits != ui->units->currentIndex() == 0 ? false : true) + if (m_settings->m_siUnits != (ui->units->currentIndex() == 0 ? false : true)) { m_settings->m_siUnits = ui->units->currentIndex() == 0 ? false : true; m_settingsKeys.append("siUnits"); } - if (m_settings->m_displayDemodStats != ui->displayStats->isChecked()) - { - m_settings->m_displayDemodStats = ui->displayStats->isChecked(); - m_settingsKeys.append("displayDemodStats"); - } if (m_settings->m_autoResizeTableColumns != ui->autoResizeTableColumns->isChecked()) { m_settings->m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked(); @@ -180,10 +178,10 @@ void ADSBDemodDisplayDialog::accept() m_settings->m_verboseModelMatching = ui->verboseModelMatching->isChecked(); m_settingsKeys.append("verboseModelMatching"); } - if (m_settings->m_airfieldElevation != ui->airfieldElevation->value()) + if (m_settings->m_favourLivery != ui->favourLivery->isChecked()) { - m_settings->m_airfieldElevation = ui->airfieldElevation->value(); - m_settingsKeys.append("airfieldElevation"); + m_settings->m_favourLivery = ui->favourLivery->isChecked(); + m_settingsKeys.append("favourLivery"); } if (m_settings->m_transitionAlt != ui->transitionAltitude->value()) { @@ -200,6 +198,12 @@ void ADSBDemodDisplayDialog::accept() m_settings->m_tableFontSize = m_fontSize; m_settingsKeys.append("tableFontSize"); } + if (m_settings->m_flightPathPaletteName != ui->flightPathPalette->currentText()) + { + m_settings->m_flightPathPaletteName = ui->flightPathPalette->currentText(); + m_settingsKeys.append("flightPathPaletteName"); + m_settings->applyPalette(); + } QDialog::accept(); } diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui index faab888db..a3303c4c4 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui @@ -7,7 +7,7 @@ 0 0 417 - 467 + 505 @@ -124,22 +124,16 @@ - + - Display demodulator statistics + Log 3D model matching information - - - - 0 - 0 - - + - Display demodulator statistics + Log information about how aircraft are matched to 3D models @@ -147,16 +141,16 @@ - + - Log 3D model matching information + Favour airline livery over aircraft type - + - Log information about how aircraft are matched to 3D models + Favour airline livery over aircraft type for 3D models @@ -192,36 +186,13 @@ - - - Airfield barometric altitude (ft) - - - - - - - Barometric altitude reported by aircraft when on airfield surface - - - -10000 - - - 30000 - - - 10 - - - - Transition altitude (ft) - + Transition altitude in feet @@ -232,6 +203,9 @@ 20000 + + 6000 + @@ -598,6 +572,20 @@ + + + + Colour palette to use for aircraft flight paths + + + + + + + Flight path palette + + + @@ -607,10 +595,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index 462124b47..ed8a48b17 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -18,7 +18,6 @@ #include -#include "device/deviceuiset.h" #include #include #include @@ -39,6 +38,7 @@ #endif #include "ui_adsbdemodgui.h" +#include "device/deviceuiset.h" #include "device/deviceapi.h" #include "dsp/devicesamplesource.h" #include "channel/channelwebapiutils.h" @@ -49,12 +49,14 @@ #include "util/db.h" #include "util/units.h" #include "util/morse.h" +#include "util/profiler.h" #include "gui/basicchannelsettingsdialog.h" #include "gui/crightclickenabler.h" #include "gui/clickablelabel.h" #include "gui/tabletapandhold.h" #include "gui/dialpopup.h" #include "gui/dialogpositioner.h" +#include "gui/checklist.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" @@ -132,6 +134,77 @@ const QString ADSBDemodGUI::m_fomSources[] = { "Invalid", "INS", "GNSS", "DME/DME", "DME/VOR", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved" }; +const QString ADSBDemodGUI::m_nacvStrings[] = { + ">= 10 m/s", "< 10 m/s", "< 3 m/s", "< 1 m/s", "< 0.3 m/s", "Reserved", "Reserved", "Reserved" +}; + +static const unsigned char colors[3*64] = { + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x55, + 0x00, 0x00, 0xaa, + 0x00, 0x00, 0xff, + 0x00, 0x55, 0x00, + 0x00, 0x55, 0x55, + 0x00, 0x55, 0xaa, + 0x00, 0x55, 0xff, + 0x00, 0xaa, 0x00, + 0x00, 0xaa, 0x55, + 0x00, 0xaa, 0xaa, + 0x00, 0xaa, 0xff, + 0x00, 0xff, 0x00, + 0x00, 0xff, 0x55, + 0x00, 0xff, 0xaa, + 0x00, 0xff, 0xff, + 0x55, 0x00, 0x00, + 0x55, 0x00, 0x55, + 0x55, 0x00, 0xaa, + 0x55, 0x00, 0xff, + 0x55, 0x55, 0x00, + 0x55, 0x55, 0x55, + 0x55, 0x55, 0xaa, + 0x55, 0x55, 0xff, + 0x55, 0xaa, 0x00, + 0x55, 0xaa, 0x55, + 0x55, 0xaa, 0xaa, + 0x55, 0xaa, 0xff, + 0x55, 0xff, 0x00, + 0x55, 0xff, 0x55, + 0x55, 0xff, 0xaa, + 0x55, 0xff, 0xff, + 0xaa, 0x00, 0x00, + 0xaa, 0x00, 0x55, + 0xaa, 0x00, 0xaa, + 0xaa, 0x00, 0xff, + 0xaa, 0x55, 0x00, + 0xaa, 0x55, 0x55, + 0xaa, 0x55, 0xaa, + 0xaa, 0x55, 0xff, + 0xaa, 0xaa, 0x00, + 0xaa, 0xaa, 0x55, + 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xff, + 0xaa, 0xff, 0x00, + 0xaa, 0xff, 0x55, + 0xaa, 0xff, 0xaa, + 0xaa, 0xff, 0xff, + 0xff, 0x00, 0x00, + 0xff, 0x00, 0x55, + 0xff, 0x00, 0xaa, + 0xff, 0x00, 0xff, + 0xff, 0x55, 0x00, + 0xff, 0x55, 0x55, + 0xff, 0x55, 0xaa, + 0xff, 0x55, 0xff, + 0xff, 0xaa, 0x00, + 0xff, 0xaa, 0x55, + 0xff, 0xaa, 0xaa, + 0xff, 0xaa, 0xff, + 0xff, 0xff, 0x00, + 0xff, 0xff, 0x55, + 0xff, 0xff, 0xaa, + 0xff, 0xff, 0xff, +}; + ADSBDemodGUI* ADSBDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { ADSBDemodGUI* gui = new ADSBDemodGUI(pluginAPI, deviceUISet, rxChannel); @@ -213,22 +286,45 @@ static Real modulus(double x, double y) QString Aircraft::getImage() const { + if (m_aircraftInfo && !m_aircraftInfo->m_type.isEmpty()) + { + // https://www.icao.int/publications/doc8643/pages/search.aspx + static const QStringList fourEngineTypes = {"A388", "B741", "B742", "B743", "B744", "B748", "B74R", "B74S", "BLCF", "A342", "A343", "A345", "A346", "C17", "K35R"}; + + if (fourEngineTypes.contains(m_aircraftInfo->m_type)) { + return QString("aircraft_heavy_4engine.png"); + } else if (m_aircraftInfo->m_type == "EUFI") { + return QString("eurofighter.png"); + } else if (m_aircraftInfo->m_type == "VF35") { + return QString("f35.png"); + } else if ((m_aircraftInfo->m_type == "SPIT") || (m_aircraftInfo->m_type == "HURI")) { + return QString("spitfire.png"); + } else if ((m_aircraftInfo->m_type == "A400") || (m_aircraftInfo->m_type == "C30J")) { + return QString("a400m.png"); + } else if (m_aircraftInfo->m_type == "H64") { + return QString("apache.png"); + } else if (m_aircraftInfo->m_type == "H47") { + return QString("chinook.png"); + } + } + if (m_emitterCategory.length() > 0) { - if (!m_emitterCategory.compare("Heavy")) { - return QString("aircraft_4engine.png"); // Can also be 777, 787 - } else if (!m_emitterCategory.compare("Large")) { - return QString("aircraft_2engine.png"); - } else if (!m_emitterCategory.compare("Small")) { - return QString("aircraft_2enginesmall.png"); + if (!m_emitterCategory.compare("Heavy")) { // 777/787/A350 and 747/A380 - 4 engine handled above by type + return QString("aircraft_heavy_2engine.png"); + } else if (!m_emitterCategory.compare("Large")) { // A320/737 + return QString("aircraft_large.png"); + } else if (!m_emitterCategory.compare("Small")) { // Private jets + return QString("aircraft_small.png"); } else if (!m_emitterCategory.compare("Rotorcraft")) { return QString("aircraft_helicopter.png"); } else if (!m_emitterCategory.compare("High performance")) { return QString("aircraft_fighter.png"); } else if (!m_emitterCategory.compare("Light") - || !m_emitterCategory.compare("Ultralight") - || !m_emitterCategory.compare("Glider/sailplane")) { + || !m_emitterCategory.compare("Ultralight")) { return QString("aircraft_light.png"); + } else if (!m_emitterCategory.compare("Glider/sailplane")) { + return QString("aircraft_glider.png"); } else if (!m_emitterCategory.compare("Space vehicle")) { return QString("aircraft_space.png"); } else if (!m_emitterCategory.compare("UAV")) { @@ -237,12 +333,12 @@ QString Aircraft::getImage() const || !m_emitterCategory.compare("Service vehicle")) { return QString("truck.png"); } else { - return QString("aircraft_2engine.png"); + return QString("aircraft_large.png"); } } else { - return QString("aircraft_2engine.png"); + return QString("aircraft_large.png"); } } @@ -251,7 +347,11 @@ QString Aircraft::getText(const ADSBDemodSettings *settings, bool all) const QStringList list; if (m_showAll || all) { - if (!m_flagIconURL.isEmpty() && !m_airlineIconURL.isEmpty()) + if (!m_flagIconURL.isEmpty() && !m_sideviewIconURL.isEmpty()) + { + list.append(QString("
").arg(m_sideviewIconURL).arg(m_flagIconURL)); + } + else if (!m_flagIconURL.isEmpty() && !m_airlineIconURL.isEmpty()) { list.append(QString("
").arg(m_airlineIconURL).arg(m_flagIconURL)); } @@ -259,6 +359,8 @@ QString Aircraft::getText(const ADSBDemodSettings *settings, bool all) const { if (!m_flagIconURL.isEmpty()) { list.append(QString("").arg(m_flagIconURL)); + } else if (!m_sideviewIconURL.isEmpty()) { + list.append(QString("").arg(m_sideviewIconURL)); } else if (!m_airlineIconURL.isEmpty()) { list.append(QString("").arg(m_airlineIconURL)); } @@ -380,13 +482,14 @@ QString Aircraft::getText(const ADSBDemodSettings *settings, bool all) const } else { - list.append(getLabel(settings)); + QDateTime labelDateTime; + list.append(getLabel(settings, labelDateTime)); } return list.join("
"); } // See https://nats.aero/blog/2017/07/drone-disruption-gatwick/ for example of UK ATC display -QString Aircraft::getLabel(const ADSBDemodSettings *settings) const +QString Aircraft::getLabel(const ADSBDemodSettings *settings, QDateTime& dateTime) const { QString id; if (!m_callsign.isEmpty()) @@ -406,6 +509,19 @@ QString Aircraft::getLabel(const ADSBDemodSettings *settings) const strings.append(id); if (settings->m_atcLabels) { + // Route + QString dep = m_depItem->text(); + QString arr = m_arrItem->text(); + QString stops = m_stopsItem->text(); + if (!dep.isEmpty() && !arr.isEmpty()) + { + if (stops.isEmpty()) { + strings.append(QString("%1-%2").arg(dep).arg(arr)); + } else { + strings.append(QString("%1-%2-%3").arg(dep).arg(stops).arg(arr)); + } + } + if (m_altitudeValid) { QStringList row1; @@ -425,16 +541,28 @@ QString Aircraft::getLabel(const ADSBDemodSettings *settings) const row1.append(QString::number(selfl)); } strings.append(row1.join(" ")); + dateTime = m_altitudeDateTime; } + QStringList row2; // Display speed - if (m_groundspeedValid) { + if (m_groundspeedValid) + { row2.append(QString("G%2").arg(m_groundspeed)); - } else if (m_indicatedAirspeedValid) { + } + else if (m_indicatedAirspeedValid) + { row2.append(QString("I%1").arg(m_indicatedAirspeed)); + if (!dateTime.isValid() || (m_indicatedAirspeedDateTime > dateTime)) { + dateTime = m_indicatedAirspeedDateTime; + } } // Aircraft type - if (m_aircraftInfo && !m_aircraftInfo->m_model.isEmpty()) + if (m_aircraftInfo && !m_aircraftInfo->m_type.isEmpty()) + { + row2.append(m_aircraftInfo->m_type); + } + else if (m_aircraftInfo && !m_aircraftInfo->m_model.isEmpty()) { QString name = m_aircraftInfo->m_model; int idx; @@ -454,6 +582,250 @@ QString Aircraft::getLabel(const ADSBDemodSettings *settings) const return strings.join("
"); } +void Aircraft::addCoordinate(const QDateTime& dateTime, AircraftModel *model) +{ + int color = std::min(7, m_altitude / 5000); + + bool showFullPath = (model->m_settings->m_flightPaths && m_isHighlighted) || model->m_settings->m_allFlightPaths; + bool showATCPath = model->m_settings->m_atcLabels && !showFullPath; + + QGeoCoordinate coord(m_latitude, m_longitude, m_altitude); + if (m_coordinates.isEmpty()) + { + QVariantList list; + list.push_back(QVariant::fromValue(coord)); + m_coordinates.push_back(list); + m_coordinateColors.push_back(color); + if (showFullPath) { + model->aircraftCoordsAdded(this); + } + } + else + { + if (color == m_lastColor) + { + QVariantList& list = m_coordinates.back(); + list.push_back(QVariant::fromValue(coord)); + if (showFullPath) { + model->aircraftCoordsUpdated(this); + } + } + else + { + QVariantList& prevList = m_coordinates.back(); + prevList.push_back(QVariant::fromValue(coord)); + m_coordinateDateTimes.push_back(dateTime); + if (showFullPath) { + model->aircraftCoordsUpdated(this); + } + + QVariantList list; + list.push_back(QVariant::fromValue(coord)); + m_coordinates.push_back(list); + m_coordinateColors.push_back(color); + if (showFullPath) { + model->aircraftCoordsAdded(this); + } + } + } + + if (m_recentCoordinates.isEmpty()) + { + QVariantList list; + list.push_back(QVariant::fromValue(coord)); + m_recentCoordinates.push_back(list); + m_recentCoordinateColors.push_back(color); + if (showATCPath) { + model->aircraftCoordsAdded(this); + } + } + else + { + if (color == m_lastColor) + { + QVariantList& list = m_recentCoordinates.back(); + list.push_back(QVariant::fromValue(coord)); + if (showATCPath) { + model->aircraftCoordsUpdated(this); + } + } + else + { + QVariantList& prevList = m_recentCoordinates.back(); + prevList.push_back(QVariant::fromValue(coord)); + if (showATCPath) { + model->aircraftCoordsUpdated(this); + } + + QVariantList list; + list.push_back(QVariant::fromValue(coord)); + m_recentCoordinates.push_back(list); + m_recentCoordinateColors.push_back(color); + if (showATCPath) { + model->aircraftCoordsAdded(this); + } + } + } + m_coordinateDateTimes.push_back(dateTime); + m_lastColor = color; + + // Prune recent list so only last 25 seconds of coordinates + QDateTime cutoff = QDateTime::currentDateTime().addSecs(-25); + int keepCount = 0; + for (int i = m_coordinateDateTimes.size() - 1; i >= 0; i--) + { + if (m_coordinateDateTimes[i] < cutoff) { + break; + } + keepCount++; + } + bool removed = false; + for (int i = m_recentCoordinates.size() - 1; i >= 0; i--) + { + int size = (int) m_recentCoordinates[i].size(); + + if (keepCount <= 0) + { + m_recentCoordinates.remove(0, i + 1); + m_recentCoordinateColors.remove(0, i + 1); + removed = true; + break; + } + else + { + if (size > keepCount) + { + int remove = size - keepCount + 1; + m_recentCoordinates[i].remove(0, remove); + removed = true; + keepCount -= remove; + } + else + { + keepCount -= size; + } + } + } + if (showATCPath && removed) { + model->aircraftCoordsRemoved(this); + } +} + +void Aircraft::clearCoordinates(AircraftModel *model) +{ + m_coordinates.clear(); + m_coordinateColors.clear(); + m_recentCoordinates.clear(); + m_recentCoordinateColors.clear(); + m_coordinateDateTimes.clear(); + model->clearCoords(this); +} + +void AircraftPathModel::add() +{ + beginInsertRows(QModelIndex(), m_paths, m_paths); + m_paths++; + endInsertRows(); +} + +void AircraftPathModel::updateLast() +{ + QModelIndex idx = index(m_paths - 1); + emit dataChanged(idx, idx); +} + +void AircraftPathModel::clear() +{ + if (m_paths > 0) + { + beginRemoveRows(QModelIndex(), 0, m_paths - 1); + m_paths = 0; + endRemoveRows(); + } +} + +void AircraftPathModel::removed() +{ + beginResetModel(); + // Coordinates are never removed from m_coordinates + m_paths = m_aircraft->m_recentCoordinates.size(); + endResetModel(); +} + +void AircraftPathModel::settingsUpdated() +{ + if (!m_aircraftModel->m_settings) { + return; + } + + bool showFullPath = (m_aircraftModel->m_settings->m_flightPaths && m_aircraft->m_isHighlighted) || m_aircraftModel->m_settings->m_allFlightPaths; + bool showATCPath = m_aircraftModel->m_settings->m_atcLabels && !showFullPath; + + if (showFullPath) + { + if (!m_showFullPath) + { + m_showFullPath = showFullPath; + m_showATCPath = false; + beginResetModel(); + m_paths = m_aircraft->m_coordinates.size(); + endResetModel(); + } + } + else if (showATCPath) + { + if (!m_showATCPath) + { + m_showFullPath = false; + m_showATCPath = showATCPath; + beginResetModel(); + m_paths = m_aircraft->m_recentCoordinates.size(); + endResetModel(); + } + } + else if (m_showFullPath || m_showATCPath) + { + m_showFullPath = false; + m_showATCPath = false; + beginResetModel(); + m_paths = 0; + endResetModel(); + } +} + +QVariant AircraftPathModel::data(const QModelIndex &index, int role) const +{ + int row = index.row(); + if ((row < 0) || (row >= m_paths)) { + return QVariant(); + } + /*qDebug() << "AircraftPathModel::data: row" << row << "m_paths" << m_paths << "m_showFullPath" << m_showFullPath << "m_showATCPath" << m_showATCPath + << "m_aircraft->m_coordinates.size()" << m_aircraft->m_coordinates.size() << "m_aircraft->m_recentCoordinates" << m_aircraft->m_recentCoordinates.size() + << "m_aircraft->m_coordinateColors.size()" << m_aircraft->m_coordinateColors.size() << "m_aircraft->m_recentCoordinateColors" << m_aircraft->m_recentCoordinateColors.size();*/ + + if (role == AircraftPathModel::coordinatesRole) + { + if (m_showFullPath) { + return m_aircraft->m_coordinates[row]; + } else if (m_showATCPath) { + return m_aircraft->m_recentCoordinates[row]; + } else { + return QVariantList(); + } + } + else if (role == AircraftPathModel::colorRole) + { + if (m_showFullPath) { + return m_aircraftModel->m_settings->m_flightPathPalette[m_aircraft->m_coordinateColors[row]]; + } else if (m_showATCPath) { + return m_aircraftModel->m_settings->m_flightPathPalette[m_aircraft->m_recentCoordinateColors[row]]; + } else { + return QVariant(); + } + } + return QVariant(); +} + QVariant AircraftModel::data(const QModelIndex &index, int role) const { int row = index.row(); @@ -471,7 +843,11 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const else if (role == AircraftModel::headingRole) { // What rotation to draw the aircraft icon at - return QVariant::fromValue(m_aircrafts[row]->m_heading); + if (m_aircrafts[row]->m_trackValid) { + return QVariant::fromValue(m_aircrafts[row]->m_track); + } else { + return QVariant::fromValue(m_aircrafts[row]->m_heading); + } } else if (role == AircraftModel::adsbDataRole) { @@ -495,37 +871,16 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const else return QVariant::fromValue(QColor("lightblue")); } - else if (role == AircraftModel::aircraftPathRole) - { - if ((m_flightPaths && m_aircrafts[row]->m_isHighlighted) || m_allFlightPaths) - { - return m_aircrafts[row]->m_coordinates; - } - else if (m_settings->m_atcLabels) - { - // Display last 20 seconds of coordinates - QDateTime cutoff = QDateTime::currentDateTime().addSecs(-20); - QVariantList coordinates; - for (int i = m_aircrafts[row]->m_coordinateDateTimes.size() - 1; i >= 0; i--) - { - if (m_aircrafts[row]->m_coordinateDateTimes[i] < cutoff) { - break; - } - coordinates.push_front(m_aircrafts[row]->m_coordinates[i]); - } - return coordinates; - } - else - { - return QVariantList(); - } - } else if (role == AircraftModel::showAllRole) return QVariant::fromValue(m_aircrafts[row]->m_showAll); else if (role == AircraftModel::highlightedRole) return QVariant::fromValue(m_aircrafts[row]->m_isHighlighted); else if (role == AircraftModel::targetRole) return QVariant::fromValue(m_aircrafts[row]->m_isTarget); + else if (role == AircraftModel::radiusRole) + return QVariant::fromValue(m_aircrafts[row]->m_radius); + else if (role == AircraftModel::aircraftPathModelRole) + return QVariant::fromValue(m_pathModels[row]); return QVariant(); } @@ -725,9 +1080,12 @@ QVariant AirspaceModel::data(const QModelIndex &index, int role) const // Airspace name and altitudes QString details; details.append(m_airspaces[row]->m_name); - details.append(QString("\n%1 - %2") - .arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_bottom)) - .arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_top))); + if (m_airspaces[row]->m_bottom.m_alt != -1) + { + details.append(QString("\n%1 - %2") + .arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_bottom)) + .arg(m_airspaces[row]->getAlt(&m_airspaces[row]->m_top))); + } return QVariant::fromValue(details); } else if (role == AirspaceModel::positionRole) @@ -741,7 +1099,11 @@ QVariant AirspaceModel::data(const QModelIndex &index, int role) const } else if (role == AirspaceModel::airspaceBorderColorRole) { - if ((m_airspaces[row]->m_category == "D") + if (m_airspaces[row]->m_name == "Coverage Map Low") { + return QVariant::fromValue(QColor(0x00, 0xff, 0x00, 0x00)); + } else if (m_airspaces[row]->m_name == "Coverage Map High") { + return QVariant::fromValue(QColor(0xff, 0xff, 0x00, 0x00)); + } else if ((m_airspaces[row]->m_category == "D") || (m_airspaces[row]->m_category == "G") || (m_airspaces[row]->m_category == "CTR")) { return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x00)); @@ -751,7 +1113,16 @@ QVariant AirspaceModel::data(const QModelIndex &index, int role) const } else if (role == AirspaceModel::airspaceFillColorRole) { - if ((m_airspaces[row]->m_category == "D") + if (m_airspaces[row]->m_name.startsWith("IC")) { + int ic = m_airspaces[row]->m_name.sliced(3).toInt(); + int i = (ic & 0x3f) * 3; + + return QVariant::fromValue(QColor(colors[i], colors[i+1], colors[i+2], 0x40)); + } else if (m_airspaces[row]->m_name == "Coverage Map Low") { + return QVariant::fromValue(QColor(0x00, 0xff, 0x00, 0x40)); + } else if (m_airspaces[row]->m_name == "Coverage Map High") { + return QVariant::fromValue(QColor(0xff, 0xff, 0x00, 0x40)); + } else if ((m_airspaces[row]->m_category == "D") || (m_airspaces[row]->m_category == "G") || (m_airspaces[row]->m_category == "CTR")) { return QVariant::fromValue(QColor(0x00, 0x00, 0xff, 0x10)); @@ -906,54 +1277,6 @@ bool ADSBDemodGUI::setFrequency(qint64 targetFrequencyHz) return false; } -// Called when we have both lat & long -void ADSBDemodGUI::updatePosition(Aircraft *aircraft) -{ - if (!aircraft->m_positionValid) - { - aircraft->m_positionValid = true; - // Now we have a position, add a plane to the map - QGeoCoordinate coords; - coords.setLatitude(aircraft->m_latitude); - coords.setLongitude(aircraft->m_longitude); - m_aircraftModel.addAircraft(aircraft); - } - // Calculate range, azimuth and elevation to aircraft from station - m_azEl.setTarget(aircraft->m_latitude, aircraft->m_longitude, Units::feetToMetres(aircraft->m_altitude)); - m_azEl.calculate(); - aircraft->m_range = m_azEl.getDistance(); - aircraft->m_azimuth = m_azEl.getAzimuth(); - aircraft->m_elevation = m_azEl.getElevation(); - aircraft->m_rangeItem->setText(QString::number(aircraft->m_range/1000.0, 'f', 1)); - aircraft->m_azElItem->setText(QString("%1/%2").arg(std::round(aircraft->m_azimuth)).arg(std::round(aircraft->m_elevation))); - if (aircraft == m_trackAircraft) { - m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation, aircraft->m_range); - } -} - -// Called when we have lat & long from local decode and we need to check if it is in a valid range (<180nm/333km) -// or 1/4 of that for surface positions -bool ADSBDemodGUI::updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition) -{ - // Calculate range to aircraft from station - m_azEl.setTarget(latitude, longitude, Units::feetToMetres(aircraft->m_altitude)); - m_azEl.calculate(); - - // Don't use the full 333km, as there may be some error in station position - if (m_azEl.getDistance() < (surfacePosition ? 80000 : 320000)) - { - aircraft->m_latitude = latitude; - aircraft->m_longitude = longitude; - updatePosition(aircraft); - return true; - } - else - { - //qDebug() << "Local position out of range - calculated distance: " << m_azEl.getDistance(); - return false; - } -} - void ADSBDemodGUI::clearFromMap(const QString& name) { QList mapPipes; @@ -978,16 +1301,25 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QList 0) { - // Adjust altitude by airfield barometric elevation, so aircraft appears to - // take-off/land at correct point on runway + // Barometric altitude reported by ADS-B is relative to pressure of 1013.25 + // Correct using QNH setting to get altitude relative to sea level int altitudeFt = aircraft->m_altitude; - if (!aircraft->m_onSurface && !aircraft->m_altitudeGNSS) { - altitudeFt -= m_settings.m_airfieldElevation; + if (aircraft->m_onSurfaceValid && aircraft->m_onSurface) { + altitudeFt = 0; // Ignore Mode S altitude + } else if (!aircraft->m_altitudeGNSS) { + altitudeFt += (m_settings.m_qnh - 1013.25) * 30; // Adjust by 30ft per hPa } float altitudeM = Units::feetToMetres(altitudeFt); + // On take-off, m_onSurface may go false before aircraft actually takes off + // and due to inaccuracy in baro / QHN, it can appear to move down the runway in the air + // So keep altitude at 0 until it starts to go above first airbourne value + if (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude <= aircraft->m_runwayAltitude)) { + altitudeM = 0; + } + for (const auto& pipe : mapPipes) { MessageQueue *messageQueue = qobject_cast(pipe->m_element); @@ -997,10 +1329,16 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetLongitude(aircraft->m_longitude); swgMapItem->setAltitude(altitudeM); swgMapItem->setPositionDateTime(new QString(aircraft->m_positionDateTime.toString(Qt::ISODateWithMs))); + swgMapItem->setAltitudeDateTime(new QString(aircraft->m_altitudeDateTime.toString(Qt::ISODateWithMs))); swgMapItem->setFixedPosition(false); - swgMapItem->setAvailableUntil(new QString(aircraft->m_positionDateTime.addSecs(60).toString(Qt::ISODateWithMs))); + swgMapItem->setAvailableUntil(new QString(aircraft->m_positionDateTime.addSecs(61).toString(Qt::ISODateWithMs))); // Changed from 60 to 61 due to https://github.com/CesiumGS/cesium/issues/12426 swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage()))); - swgMapItem->setImageRotation(aircraft->m_heading); + // Use track, in preference to heading, as we typically receive far fewer heading frames + if (aircraft->m_trackValid) { + swgMapItem->setImageRotation(aircraft->m_track); + } else { + swgMapItem->setImageRotation(aircraft->m_heading); + } swgMapItem->setText(new QString(aircraft->getText(&m_settings, true))); if (!aircraft->m_aircraft3DModel.isEmpty()) { @@ -1009,12 +1347,16 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetModel(new QString(aircraft->m_aircraftCat3DModel)); } - swgMapItem->setLabel(new QString(aircraft->getLabel(&m_settings))); + QDateTime labelDateTime; + swgMapItem->setLabel(new QString(aircraft->getLabel(&m_settings, labelDateTime))); + if (labelDateTime.isValid()) { + swgMapItem->setLabelDateTime(new QString(labelDateTime.toString(Qt::ISODateWithMs))); + } - if (aircraft->m_headingValid) + if (aircraft->m_trackValid) { swgMapItem->setOrientation(1); - swgMapItem->setHeading(aircraft->m_heading); + swgMapItem->setHeading(aircraft->m_track); swgMapItem->setPitch(aircraft->m_pitchEst); swgMapItem->setRoll(aircraft->m_rollEst); swgMapItem->setOrientationDateTime(new QString(aircraft->m_positionDateTime.toString(Qt::ISODateWithMs))); @@ -1030,6 +1372,157 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetAltitudeReference(3); // CLIP_TO_GROUND so aircraft don't go under runway swgMapItem->setAnimations(animations); // Does this need to be duplicated? + SWGSDRangel::SWGMapAircraftState *aircraftState = new SWGSDRangel::SWGMapAircraftState(); + if (!aircraft->m_callsign.isEmpty()) { + aircraftState->setCallsign(new QString(aircraft->m_callsign)); + } + if (aircraft->m_aircraftInfo) { + aircraftState->setAircraftType(new QString(aircraft->m_aircraftInfo->m_type)); + } + if (aircraft->m_onSurfaceValid) { + aircraftState->setOnSurface(aircraft->m_onSurface); + } else { + aircraftState->setOnSurface(-1); + } + if (aircraft->m_indicatedAirspeedValid) + { + aircraftState->setAirspeed(aircraft->m_indicatedAirspeed); + aircraftState->setAirspeedDateTime(new QString(aircraft->m_indicatedAirspeedDateTime.toString(Qt::ISODateWithMs))); + } + else + { + aircraftState->setAirspeed(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_trueAirspeedValid) { + aircraftState->setTrueAirspeed(aircraft->m_trueAirspeed); + } else { + aircraftState->setTrueAirspeed(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_groundspeedValid) { + aircraftState->setGroundspeed(aircraft->m_groundspeed); + } else { + aircraftState->setGroundspeed(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_machValid) { + aircraftState->setMach(aircraft->m_mach); + } else { + aircraftState->setMach(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_pressureAltitudeValid) + { + aircraftState->setAltitude(aircraft->m_pressureAltitude); + aircraftState->setAltitudeDateTime(new QString(aircraft->m_altitudeDateTime.toString(Qt::ISODateWithMs))); + } + else + { + aircraftState->setAltitude(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_baroValid) { + aircraftState->setQnh(aircraft->m_baro); + } else { + aircraftState->setQnh(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_verticalRateValid) { + aircraftState->setVerticalSpeed(aircraft->m_verticalRate); + } else { + aircraftState->setVerticalSpeed(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_headingValid) { + aircraftState->setHeading(aircraft->m_heading); + } else { + aircraftState->setHeading(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_trackValid) { + aircraftState->setTrack(aircraft->m_track); + } else { + aircraftState->setTrack(std::numeric_limits::quiet_NaN()); + } + //aircraftState->setSelectedAirspeed(aircraft->m_selSpeed); // Selected speed not available in ADS-B / Mode-S + if (aircraft->m_selAltitudeValid) { + aircraftState->setSelectedAltitude(roundTo50Feet(aircraft->m_selAltitude)); + } else { + aircraftState->setSelectedAltitude(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_selHeadingValid) { + aircraftState->setSelectedHeading(aircraft->m_selHeading); + } else { + aircraftState->setSelectedHeading(std::numeric_limits::quiet_NaN()); + } + if (aircraft->m_autopilotValid) { + aircraftState->setAutopilot((int) aircraft->m_autopilot); + } else { + aircraftState->setAutopilot(-1); + } + if (aircraft->m_vnavModeValid && aircraft->m_vnavMode) { + aircraftState->setVerticalMode(1); + } else if (aircraft->m_altHoldModeValid && aircraft->m_altHoldMode) { + aircraftState->setVerticalMode(2); + } else if (aircraft->m_approachModeValid && aircraft->m_approachMode) { + aircraftState->setVerticalMode(3); + } else { + aircraftState->setVerticalMode(0); + } + if (aircraft->m_lnavModeValid && aircraft->m_lnavMode) { + aircraftState->setLateralMode(1); + } else if (aircraft->m_approachModeValid && aircraft->m_approachMode) { + aircraftState->setLateralMode(2); + } else { + aircraftState->setLateralMode(0); + } + + // When RA Only, m_tcasOperational appears to be false + QString acasCapability = aircraft->m_acasItem->text(); + if (acasCapability == "") { + aircraftState->setTcasMode(0); // Unknown + } else if (acasCapability == "No ACAS") { + aircraftState->setTcasMode(1); // Off + } else if (acasCapability == "No RA") { + aircraftState->setTcasMode(2); // TA Only + } else { + aircraftState->setTcasMode(3); // TA/RA + } + + // Windspeed/direction are rarely available, so use calculated headwind as backup + QVariant windSpeed = aircraft->m_windSpeedItem->data(Qt::DisplayRole); + QVariant windDirection = aircraft->m_windDirItem->data(Qt::DisplayRole); + if (!windSpeed.isNull() && !windDirection.isNull()) + { + aircraftState->setWindSpeed(windSpeed.toFloat()); + aircraftState->setWindDirection(windDirection.toFloat()); + } + else + { + QVariant headwind = aircraft->m_headwindItem->data(Qt::DisplayRole); + if (!headwind.isNull() && aircraft->m_headingValid) + { + aircraftState->setWindSpeed(headwind.toFloat()); + aircraftState->setWindDirection(aircraft->m_heading); + } + else + { + aircraftState->setWindSpeed(std::numeric_limits::quiet_NaN()); + aircraftState->setWindDirection(std::numeric_limits::quiet_NaN()); + } + } + + // Static air temperature rarely available, so use estimated as backup + QVariant staticAirTemp = aircraft->m_staticAirTempItem->data(Qt::DisplayRole); + if (!staticAirTemp.isNull()) + { + aircraftState->setStaticAirTemperature(staticAirTemp.toFloat()); + } + else + { + QVariant estAirTemp = aircraft->m_estAirTempItem->data(Qt::DisplayRole); + if (!estAirTemp.isNull()) { + aircraftState->setStaticAirTemperature(estAirTemp.toFloat()); + } else { + aircraftState->setStaticAirTemperature(std::numeric_limits::quiet_NaN()); + } + } + + swgMapItem->setAircraftState(aircraftState); + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem); messageQueue->push(msg); } @@ -1062,9 +1555,12 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) ui->adsbData->setItem(row, ADSB_COL_ATC_CALLSIGN, aircraft->m_atcCallsignItem); ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, aircraft->m_callsignItem); ui->adsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem); + ui->adsbData->setItem(row, ADSB_COL_TYPE, aircraft->m_typeItem); + ui->adsbData->setItem(row, ADSB_COL_SIDEVIEW, aircraft->m_sideviewItem); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, aircraft->m_airlineItem); ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, aircraft->m_altitudeItem); ui->adsbData->setItem(row, ADSB_COL_HEADING, aircraft->m_headingItem); + ui->adsbData->setItem(row, ADSB_COL_TRACK, aircraft->m_trackItem); ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, aircraft->m_verticalRateItem); ui->adsbData->setItem(row, ADSB_COL_RANGE, aircraft->m_rangeItem); ui->adsbData->setItem(row, ADSB_COL_AZEL, aircraft->m_azElItem); @@ -1073,19 +1569,34 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) ui->adsbData->setItem(row, ADSB_COL_CATEGORY, aircraft->m_emitterCategoryItem); ui->adsbData->setItem(row, ADSB_COL_STATUS, aircraft->m_statusItem); ui->adsbData->setItem(row, ADSB_COL_SQUAWK, aircraft->m_squawkItem); + ui->adsbData->setItem(row, ADSB_COL_IDENT, aircraft->m_identItem); ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, aircraft->m_registrationItem); ui->adsbData->setItem(row, ADSB_COL_COUNTRY, aircraft->m_countryItem); ui->adsbData->setItem(row, ADSB_COL_REGISTERED, aircraft->m_registeredItem); ui->adsbData->setItem(row, ADSB_COL_MANUFACTURER, aircraft->m_manufacturerNameItem); ui->adsbData->setItem(row, ADSB_COL_OWNER, aircraft->m_ownerItem); ui->adsbData->setItem(row, ADSB_COL_OPERATOR_ICAO, aircraft->m_operatorICAOItem); + ui->adsbData->setItem(row, ADSB_COL_IC, aircraft->m_interogatorCodeItem); ui->adsbData->setItem(row, ADSB_COL_TIME, aircraft->m_timeItem); - ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, aircraft->m_adsbFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, aircraft->m_totalFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_ADSB_FRAMECOUNT, aircraft->m_adsbFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_MODES_FRAMECOUNT, aircraft->m_modesFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_NON_TRANSPONDER, aircraft->m_nonTransponderItem); + ui->adsbData->setItem(row, ADSB_COL_TIS_B_FRAMECOUNT, aircraft->m_tisBFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_ADSR_FRAMECOUNT, aircraft->m_adsrFrameCountItem); + ui->adsbData->setItem(row, ADSB_COL_RADIUS, aircraft->m_radiusItem); + ui->adsbData->setItem(row, ADSB_COL_NACP, aircraft->m_nacpItem); + ui->adsbData->setItem(row, ADSB_COL_NACV, aircraft->m_nacvItem); + ui->adsbData->setItem(row, ADSB_COL_GVA, aircraft->m_gvaItem); + ui->adsbData->setItem(row, ADSB_COL_NIC, aircraft->m_nicItem); + ui->adsbData->setItem(row, ADSB_COL_NIC_BARO, aircraft->m_nicBaroItem); + ui->adsbData->setItem(row, ADSB_COL_SIL, aircraft->m_silItem); ui->adsbData->setItem(row, ADSB_COL_CORRELATION, aircraft->m_correlationItem); ui->adsbData->setItem(row, ADSB_COL_RSSI, aircraft->m_rssiItem); ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, aircraft->m_flightStatusItem); ui->adsbData->setItem(row, ADSB_COL_DEP, aircraft->m_depItem); ui->adsbData->setItem(row, ADSB_COL_ARR, aircraft->m_arrItem); + ui->adsbData->setItem(row, ADSB_COL_STOPS, aircraft->m_stopsItem); ui->adsbData->setItem(row, ADSB_COL_STD, aircraft->m_stdItem); ui->adsbData->setItem(row, ADSB_COL_ETD, aircraft->m_etdItem); ui->adsbData->setItem(row, ADSB_COL_ATD, aircraft->m_atdItem); @@ -1098,6 +1609,13 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) ui->adsbData->setItem(row, ADSB_COL_AP, aircraft->m_apItem); ui->adsbData->setItem(row, ADSB_COL_V_MODE, aircraft->m_vModeItem); ui->adsbData->setItem(row, ADSB_COL_L_MODE, aircraft->m_lModeItem); + ui->adsbData->setItem(row, ADSB_COL_TCAS, aircraft->m_tcasItem); + ui->adsbData->setItem(row, ADSB_COL_ACAS, aircraft->m_acasItem); + ui->adsbData->setItem(row, ADSB_COL_RA, aircraft->m_raItem); + ui->adsbData->setItem(row, ADSB_COL_MAX_SPEED, aircraft->m_maxSpeedItem); + ui->adsbData->setItem(row, ADSB_COL_VERSION, aircraft->m_versionItem); + ui->adsbData->setItem(row, ADSB_COL_LENGTH, aircraft->m_lengthItem); + ui->adsbData->setItem(row, ADSB_COL_WIDTH, aircraft->m_widthItem); ui->adsbData->setItem(row, ADSB_COL_ROLL, aircraft->m_rollItem); ui->adsbData->setItem(row, ADSB_COL_GROUND_SPEED, aircraft->m_groundspeedItem); ui->adsbData->setItem(row, ADSB_COL_TURNRATE, aircraft->m_turnRateItem); @@ -1111,7 +1629,6 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) ui->adsbData->setItem(row, ADSB_COL_STATIC_PRESSURE, aircraft->m_staticPressureItem); ui->adsbData->setItem(row, ADSB_COL_STATIC_AIR_TEMP, aircraft->m_staticAirTempItem); ui->adsbData->setItem(row, ADSB_COL_HUMIDITY, aircraft->m_humidityItem); - ui->adsbData->setItem(row, ADSB_COL_TIS_B, aircraft->m_tisBItem); // Look aircraft up in database if (m_aircraftInfo != nullptr) { @@ -1119,6 +1636,7 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) { aircraft->m_aircraftInfo = m_aircraftInfo->value(icao); aircraft->m_modelItem->setText(aircraft->m_aircraftInfo->m_model); + aircraft->m_typeItem->setText(aircraft->m_aircraftInfo->m_type); aircraft->m_registrationItem->setText(aircraft->m_aircraftInfo->m_registration); aircraft->m_manufacturerNameItem->setText(aircraft->m_aircraftInfo->m_manufacturerName); aircraft->m_ownerItem->setText(aircraft->m_aircraftInfo->m_owner); @@ -1128,7 +1646,7 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) QIcon *icon = nullptr; if (aircraft->m_aircraftInfo->m_operatorICAO.size() > 0) { - aircraft->m_airlineIconURL = AircraftInformation::getFlagIconURL(aircraft->m_aircraftInfo->m_operatorICAO); + aircraft->m_airlineIconURL = AircraftInformation::resourcePathToURL(AircraftInformation::getAirlineIconPath(aircraft->m_aircraftInfo->m_operatorICAO)); icon = AircraftInformation::getAirlineIcon(aircraft->m_aircraftInfo->m_operatorICAO); if (icon != nullptr) { @@ -1143,16 +1661,21 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) else aircraft->m_airlineItem->setText(aircraft->m_aircraftInfo->m_owner); } + // Try loading a sideview + icon = AircraftInformation::getSideviewIcon(aircraft->m_aircraftInfo->m_registration, aircraft->m_aircraftInfo->m_operatorICAO, aircraft->m_aircraftInfo->m_type); + if (icon) + { + aircraft->m_sideviewItem->setSizeHint(QSize(85, 20)); + aircraft->m_sideviewItem->setIcon(*icon); + aircraft->m_sideviewIconURL = AircraftInformation::resourcePathToURL(AircraftInformation::getSideviewIconPath(aircraft->m_aircraftInfo->m_registration, aircraft->m_aircraftInfo->m_operatorICAO, aircraft->m_aircraftInfo->m_type)); + } // Try loading a flag based on registration if (aircraft->m_aircraftInfo->m_registration.size() > 0) { QString flag = aircraft->m_aircraftInfo->getFlag(); if (flag != "") { - aircraft->m_flagIconURL = AircraftInformation::getFlagIconPath(flag); - if (aircraft->m_flagIconURL.startsWith(':')) { - aircraft->m_flagIconURL = "qrc://" + aircraft->m_flagIconURL.mid(1); - } + aircraft->m_flagIconURL = AircraftInformation::resourcePathToURL(AircraftInformation::getFlagIconPath(flag)); icon = AircraftInformation::getFlagIcon(flag); if (icon != nullptr) { @@ -1189,6 +1712,21 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) return aircraft; } +void ADSBDemodGUI::setCallsign(Aircraft *aircraft, const QString& callsign) +{ + aircraft->m_callsign = callsign; + aircraft->m_callsignItem->setText(aircraft->m_callsign); + if (m_routeInfo->contains(aircraft->m_callsign)) + { + AircraftRouteInformation *route = m_routeInfo->value(aircraft->m_callsign); + aircraft->m_depItem->setText(route->m_dep); + aircraft->m_arrItem->setText(route->m_arr); + aircraft->m_stopsItem->setText(route->m_stops); + } + atcCallsign(aircraft); + callsignToFlight(aircraft); +} + void ADSBDemodGUI::atcCallsign(Aircraft *aircraft) { QString icao = aircraft->m_callsign.left(3); @@ -1201,7 +1739,7 @@ void ADSBDemodGUI::atcCallsign(Aircraft *aircraft) { // Airline logo QIcon *icon = nullptr; - aircraft->m_airlineIconURL = AircraftInformation::getFlagIconURL(airline->m_icao); + aircraft->m_airlineIconURL = AircraftInformation::resourcePathToURL(AircraftInformation::getAirlineIconPath(airline->m_icao)); icon = AircraftInformation::getAirlineIcon(airline->m_icao); if (icon != nullptr) { @@ -1214,10 +1752,7 @@ void ADSBDemodGUI::atcCallsign(Aircraft *aircraft) } // Flag QString flag = airline->m_country.toLower().replace(" ", "_"); - aircraft->m_flagIconURL = AircraftInformation::getFlagIconPath(flag); - if (aircraft->m_flagIconURL.startsWith(':')) { - aircraft->m_flagIconURL = "qrc://" + aircraft->m_flagIconURL.mid(1); - } + aircraft->m_flagIconURL = AircraftInformation::resourcePathToURL(AircraftInformation::getFlagIconPath(flag)); icon = AircraftInformation::getFlagIcon(flag); if (icon != nullptr) { @@ -1292,6 +1827,184 @@ bool ADSBDemodGUI::calcAirTemp(Aircraft *aircraft) } } +void ADSBDemodGUI::resetStats() +{ + m_correlationAvg.reset(); + m_correlationOnesAvg.reset(); + m_rangeFails = 0; + m_altFails = 0; + m_frameRateTime = QDateTime::currentDateTime(); + m_adsbFrameRateCount = 0; + m_modesFrameRateCount = 0; + m_totalBytes = 0; + m_maxRangeStat = 0.0f; + m_maxAltitudeStat = 0.0f; + m_maxRateState = 0.0f; + for (int i = 0; i < 32; i++) + { + m_dfStats[i] = 0; + m_tcStats[i] = 0; + } + for (int i = 0; i < ui->statsTable->rowCount(); i++) { + ui->statsTable->item(i, 0)->setData(Qt::DisplayRole, 0); + } + + ADSBDemod::MsgResetStats* message = ADSBDemod::MsgResetStats::create(); + m_adsbDemod->getInputMessageQueue()->push(message); +} + +void ADSBDemodGUI::updateDFStats(int df) +{ + switch (df) + { + case 0: + ui->statsTable->item(DF0, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 4: + ui->statsTable->item(DF4, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 5: + ui->statsTable->item(DF5, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 11: + ui->statsTable->item(DF11, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 16: + ui->statsTable->item(DF16, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 17: + ui->statsTable->item(DF17, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 18: + ui->statsTable->item(DF18, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 19: + ui->statsTable->item(DF19, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 20: + case 21: + ui->statsTable->item(DF20_21, 0)->setData(Qt::DisplayRole, m_dfStats[20] + m_dfStats[21]); + break; + case 22: + ui->statsTable->item(DF22, 0)->setData(Qt::DisplayRole, m_dfStats[df]); + break; + case 24: + case 25: + case 26: + case 27: + ui->statsTable->item(DF24, 0)->setData(Qt::DisplayRole, m_dfStats[24] + m_dfStats[25] + m_dfStats[26] + m_dfStats[27]); + break; + default: + break; + } +} + +bool ADSBDemodGUI::updateTCStats(int tc, int row, int low, int high) +{ + if ((tc >= low) && (tc <= high)) + { + qint64 sum = 0; + + for (int i = low; i <= high; i++) { + sum += m_tcStats[i]; + } + ui->statsTable->item(row, 0)->setData(Qt::DisplayRole, sum); + return true; + } + else + { + return false; + } +} + +void Aircraft::setOnSurface(const QDateTime& dateTime) +{ + // There are a few airports that are below 0 MSL + // https://en.wikipedia.org/wiki/List_of_lowest_airports + // So we could set altitude to a negative value here, which should + // then get clipped to actual terrain elevation in 3D map + // But then PFD altimeter reads negative elsewhere + // Really, the altimeter would typically read airport elevation when on the surface + m_altitude = 0; + m_altitudeValid = true; + m_altitudeGNSS = false; + m_altitudeItem->setData(Qt::DisplayRole, "Surface"); + m_altitudeDateTime = dateTime; +} + +void Aircraft::setAltitude(int altitudeFt, bool gnss, const QDateTime& dateTime, const ADSBDemodSettings& settings) +{ + m_altitude = altitudeFt; + m_altitudeValid = true; + + if (!gnss) + { + m_pressureAltitude = altitudeFt; + m_pressureAltitudeValid = true; + } + else + { + m_pressureAltitudeValid = false; + } + + m_altitudeGNSS = gnss; + m_altitudeItem->setData(Qt::DisplayRole, settings.m_siUnits ? Units::feetToIntegerMetres(m_altitude) : m_altitude); + m_altitudeDateTime = dateTime; +} + +void Aircraft::setVerticalRate(int verticalRate, const ADSBDemodSettings& settings) +{ + m_verticalRate = verticalRate; + m_verticalRateValid = true; + if (settings.m_siUnits) { + m_verticalRateItem->setData(Qt::DisplayRole, Units::feetPerMinToIntegerMetresPerSecond(verticalRate)); + } else { + m_verticalRateItem->setData(Qt::DisplayRole, m_verticalRate); + } +} + +void Aircraft::setGroundspeed(float groundspeed, const ADSBDemodSettings& settings) +{ + m_groundspeed = groundspeed; + m_groundspeedValid = true; + m_groundspeedItem->setData(Qt::DisplayRole, settings.m_siUnits ? Units::knotsToIntegerKPH(m_groundspeed) : (int)std::round(m_groundspeed)); + // FIXME: dateTime? +} + +void Aircraft::setTrueAirspeed(int airspeed, const ADSBDemodSettings& settings) +{ + m_trueAirspeed = airspeed; + m_trueAirspeedValid = true; + m_trueAirspeedItem->setData(Qt::DisplayRole, settings.m_siUnits ? Units::knotsToIntegerKPH(m_trueAirspeed) : m_trueAirspeed); +} + +void Aircraft::setIndicatedAirspeed(int airspeed, const QDateTime& dateTime, const ADSBDemodSettings& settings) +{ + m_indicatedAirspeed = airspeed; + m_indicatedAirspeedValid = true; + m_indicatedAirspeedItem->setData(Qt::DisplayRole, settings.m_siUnits ? Units::knotsToIntegerKPH(m_indicatedAirspeed) : m_indicatedAirspeed); + m_indicatedAirspeedDateTime = dateTime; +} + +void Aircraft::setTrack(float track, const QDateTime& dateTime) +{ + m_track = track; + m_trackValid = true; + m_trackDateTime = dateTime; + m_trackItem->setData(Qt::DisplayRole, std::round(m_track)); + m_orientationDateTime = dateTime; +} + +void Aircraft::setHeading(float heading, const QDateTime& dateTime) +{ + m_heading = heading; + m_headingValid = true; + m_headingDateTime = dateTime; + m_headingItem->setData(Qt::DisplayRole, std::round(m_heading)); + m_orientationDateTime = dateTime; + m_trackWhenHeadingSet = m_track; +} + void ADSBDemodGUI::handleADSB( const QByteArray data, const QDateTime dateTime, @@ -1300,111 +2013,79 @@ void ADSBDemodGUI::handleADSB( unsigned crc, bool updateModel) { + PROFILER_START(); + bool newAircraft = false; bool updatedCallsign = false; bool resetAnimation = false; - int df = (data[0] >> 3) & ADS_B_DF_MASK; // Downlink format - int ca = data[0] & 0x7; // Capability + const int df = (data[0] >> 3) & ADS_B_DF_MASK; // Downlink format + const int ca = data[0] & 0x7; // Capability (or Control Field for TIS-B) unsigned icao; Aircraft *aircraft; - if ((df == 4) || (df == 5) || (df == 20) || (df == 21)) - { - // Extract ICAO from parity - int bytes = data.length(); - unsigned parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1]; - icao = (parity ^ crc) & 0xffffff; - if (m_aircraft.contains(icao)) - { - //qDebug() << "Mode-S from known aircraft - DF " << df << " ICAO " << Qt::hex << icao; - aircraft = getAircraft(icao, newAircraft); - } - else - { - // Ignore if not from a known aircraft, as its likely not to be a valid packet - //qDebug() << "Skipping Mode-S from unknown aircraft - DF " << df << " ICAO " << Qt::hex << icao; - return; - } - } - else + if ((df == 11) || (df == 17) || (df == 18)) { icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); // ICAO aircraft address aircraft = getAircraft(icao, newAircraft); } - int tc = (data[4] >> 3) & 0x1f; // Type code - - aircraft->m_time = dateTime; - QTime time = dateTime.time(); - aircraft->m_timeItem->setText(QString("%1:%2:%3").arg(time.hour(), 2, 10, QLatin1Char('0')).arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0'))); - aircraft->m_adsbFrameCount++; - aircraft->m_adsbFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount); - if (df == 18) + else { - aircraft->m_tisBFrameCount++; - aircraft->m_tisBItem->setData(Qt::DisplayRole, aircraft->m_tisBFrameCount); + // Extract ICAO from parity + int bytes = data.length(); + unsigned parity = ((data[bytes-3] & 0xff) << 16) | ((data[bytes-2] & 0xff) << 8) | (data[bytes-1] & 0xff); + icao = (parity ^ crc) & 0xffffff; + if (icao == 0) + { + // Appears to be the case often for DF=5 + qDebug() << "ADSBDemodGUI::handleADSB: iaco of 0 - df" << df; + return; + } + aircraft = getAircraft(icao, newAircraft); } - if (correlation < aircraft->m_minCorrelation) - aircraft->m_minCorrelation = correlation; - if (correlation > aircraft->m_maxCorrelation) - aircraft->m_maxCorrelation = correlation; - m_correlationAvg(correlation); - aircraft->m_correlationAvg(correlation); - aircraft->m_correlation = aircraft->m_correlationAvg.instantAverage(); - aircraft->m_correlationItem->setText(QString("%1/%2/%3") - .arg(CalcDb::dbPower(aircraft->m_minCorrelation), 3, 'f', 1) - .arg(CalcDb::dbPower(aircraft->m_correlation), 3, 'f', 1) - .arg(CalcDb::dbPower(aircraft->m_maxCorrelation), 3, 'f', 1)); - m_correlationOnesAvg(correlationOnes); - aircraft->m_rssiItem->setText(QString("%1") - .arg(CalcDb::dbPower(m_correlationOnesAvg.instantAverage()), 3, 'f', 1)); + /*if (aircraft->m_icaoHex != "43ea47") { + return; + }*/ - // ADS-B, non-transponder ADS-B or TIS-B rebroadcast of ADS-B (ADS-R) - if ((df == 17) || ((df == 18) && (ca != 4))) + // ADS-B, non-transponder ADS-B, TIS-B, or rebroadcast of ADS-B (ADS-R) + if ((df == 17) || ((df == 18) && (ca != 3) && (ca != 4))) { + const int tc = (data[4] >> 3) & 0x1f; // Type code + unsigned surveillanceStatus = 0; + + m_tcStats[tc]++; + if (updateTCStats(tc, TC_0, 0, 0)) { + } else if (updateTCStats(tc, TC_1_4, 1, 4)) { + } else if (updateTCStats(tc, TC_5_8, 5, 8)) { + } else if (updateTCStats(tc, TC_9_18, 9, 18)) { + } else if (updateTCStats(tc, TC_19, 19, 19)) { + } else if (updateTCStats(tc, TC_20_22, 20, 22)) { + } else if (updateTCStats(tc, TC_RESERVED, 23, 23)) { + } else if (updateTCStats(tc, TC_24, 24, 24)) { + } else if (updateTCStats(tc, TC_RESERVED, 25, 27)) { + } else if (updateTCStats(tc, TC_28, 28, 28)) { + } else if (updateTCStats(tc, TC_29, 29, 29)) { + } else if (updateTCStats(tc, TC_RESERVED, 30, 30)) { + } else if (updateTCStats(tc, TC_31, 31, 31)) { + } if ((tc >= 1) && ((tc <= 4))) { // Aircraft identification - BDS 0,8 + QString emitterCategory, callsign; + + decodeID(data, emitterCategory, callsign); + int ec = data[4] & 0x7; // Emitter category QString prevEmitterCategory = aircraft->m_emitterCategory; - if (tc == 4) { - aircraft->m_emitterCategory = m_categorySetA[ec]; - } else if (tc == 3) { - aircraft->m_emitterCategory = m_categorySetB[ec]; - } else if (tc == 2) { - aircraft->m_emitterCategory = m_categorySetC[ec]; - } else { - aircraft->m_emitterCategory = QStringLiteral("Reserved"); - } + aircraft->m_emitterCategory = emitterCategory; aircraft->m_emitterCategoryItem->setText(aircraft->m_emitterCategory); - // Flight/callsign - Extract 8 6-bit characters from 6 8-bit bytes, MSB first - unsigned char c[8]; - char callsign[9]; - c[0] = (data[5] >> 2) & 0x3f; // 6 - c[1] = ((data[5] & 0x3) << 4) | ((data[6] & 0xf0) >> 4); // 2+4 - c[2] = ((data[6] & 0xf) << 2) | ((data[7] & 0xc0) >> 6); // 4+2 - c[3] = (data[7] & 0x3f); // 6 - c[4] = (data[8] >> 2) & 0x3f; - c[5] = ((data[8] & 0x3) << 4) | ((data[9] & 0xf0) >> 4); - c[6] = ((data[9] & 0xf) << 2) | ((data[10] & 0xc0) >> 6); - c[7] = (data[10] & 0x3f); - // Map to ASCII - for (int i = 0; i < 8; i++) - callsign[i] = m_idMap[c[i]]; - callsign[8] = '\0'; - QString callsignTrimmed = QString(callsign).trimmed(); - - updatedCallsign = aircraft->m_callsign != callsignTrimmed; - if (updatedCallsign) - { - aircraft->m_callsign = callsignTrimmed; - aircraft->m_callsignItem->setText(aircraft->m_callsign); - atcCallsign(aircraft); - callsignToFlight(aircraft); + updatedCallsign = aircraft->m_callsign != callsign; + if (updatedCallsign) { + setCallsign(aircraft, callsign); } // Select 3D model based on category, if we don't already have one based on ICAO @@ -1420,10 +2101,11 @@ void ADSBDemodGUI::handleADSB( resetAnimation = true; } } - else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22))) + else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22)) || (tc == 0)) { bool wasOnSurface = aircraft->m_onSurface; aircraft->m_onSurface = (tc >= 5) && (tc <= 8); + aircraft->m_onSurfaceValid = true; if (wasOnSurface != aircraft->m_onSurface) { @@ -1435,14 +2117,7 @@ void ADSBDemodGUI::handleADSB( if (aircraft->m_onSurface) { // Surface position - BDS 0,6 - - // There are a few airports that are below 0 MSL - // https://en.wikipedia.org/wiki/List_of_lowest_airports - // So we set altitude to a negative value here, which should - // then get clipped to actual terrain elevation in 3D map - aircraft->m_altitudeValid = true; - aircraft->m_altitude = -200; - aircraft->m_altitudeItem->setData(Qt::DisplayRole, "Surface"); + aircraft->setOnSurface(dateTime); int movement = ((data[4] & 0x7) << 4) | ((data[5] >> 4) & 0xf); if (movement == 0) @@ -1454,9 +2129,7 @@ void ADSBDemodGUI::handleADSB( else if (movement == 1) { // Aircraft stopped - aircraft->m_groundspeedValid = true; - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, 0); - aircraft->m_groundspeed = 0.0; + aircraft->setGroundspeed(0, m_settings); } else if ((movement >= 2) && (movement <= 123)) { @@ -1498,184 +2171,140 @@ void ADSBDemodGUI::handleADSB( step = 5.0f; adjust = 109; } - aircraft->m_groundspeed = base + (movement - adjust) * step; - aircraft->m_groundspeedValid = true; - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_groundspeed) : (int)std::round(aircraft->m_groundspeed)); + aircraft->setGroundspeed(base + (movement - adjust) * step, m_settings); } else if (movement == 124) { - aircraft->m_groundspeedValid = true; - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? 324 : 175); // Actually greater than this + aircraft->setGroundspeed(175, m_settings); // Actually greater than this } int groundTrackStatus = (data[5] >> 3) & 1; int groundTrackValue = ((data[5] & 0x7) << 4) | ((data[6] >> 4) & 0xf); if (groundTrackStatus) { - aircraft->m_heading = groundTrackValue * 360.0/128.0; - aircraft->m_headingValid = true; - aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); + float groundTrackValueFloat = groundTrackValue * 360.0/128.0; + clearOldHeading(aircraft, dateTime, groundTrackValueFloat); + aircraft->setTrack(groundTrackValueFloat, dateTime); } } - else if (((tc >= 9) && (tc <= 18)) || ((tc >= 20) && (tc <= 22))) + else if (((tc >= 9) && (tc <= 18)) || ((tc >= 20) && (tc <= 22)) || (tc == 0)) { // Airborne position (9-18 baro, 20-22 GNSS) - int alt = ((data[5] & 0xff) << 4) | ((data[6] >> 4) & 0xf); // Altitude - int q = (alt & 0x10) != 0; - int n = ((alt >> 1) & 0x7f0) | (alt & 0xf); // Remove Q-bit - int alt_ft; - if (q == 1) + + surveillanceStatus = (data[4] >> 1) & 0x3; + + // ADS-B: Version 0 is single antenna flag, version 2 is NIC supplement-B + // TSI-B: ICAO/Mode A flag + if (aircraft->m_adsbVersionValid && (aircraft->m_adsbVersion == 2) && (df == 17)) { - alt_ft = n * ((alt & 0x10) ? 25 : 100) - 1000; - } - else - { - alt_ft = gillhamToFeet(n); + aircraft->m_nicSupplementB = data[4] & 1; + aircraft->m_nicSupplementBValid = true; } - aircraft->m_altitude = alt_ft; - aircraft->m_altitudeValid = alt != 0; - aircraft->m_altitudeGNSS = ((tc >= 20) && (tc <= 22)); - // setData rather than setText so it sorts numerically - aircraft->m_altitudeItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::feetToIntegerMetres(aircraft->m_altitude) : aircraft->m_altitude); - - // Assume runway elevation is at first reported airboune altitude - if (wasOnSurface) + int altFt; + bool valid = decodeAltitude(data, altFt); + if (valid) { - aircraft->m_runwayAltitude = aircraft->m_altitude; - aircraft->m_runwayAltitudeValid = true; - } - } + aircraft->setAltitude(altFt, (tc >= 20) && (tc <= 22), dateTime, m_settings); - int f = (data[6] >> 2) & 1; // CPR odd/even frame - should alternate every 0.2s - int lat_cpr = ((data[6] & 3) << 15) | ((data[7] & 0xff) << 7) | ((data[8] >> 1) & 0x7f); - int lon_cpr = ((data[8] & 1) << 16) | ((data[9] & 0xff) << 8) | (data[10] & 0xff); - - aircraft->m_cprValid[f] = true; - aircraft->m_cprLat[f] = lat_cpr/131072.0f; - aircraft->m_cprLong[f] = lon_cpr/131072.0f; - aircraft->m_cprTime[f] = dateTime; - - // CPR decoding - // Refer to Technical Provisions for Mode S Services and Extended Squitter - Appendix C2.6 - // See also: https://mode-s.org/decode/adsb/airborne-position.html - // For global decoding, we need both odd and even frames - // We also need to check that both frames aren't greater than 10s apart in time (C.2.6.7), otherwise position may be out by ~10deg - // I've reduced this to 8.5s, as problems have been seen where times are just 9s apart. This may be because - // our timestamps aren't accurate, as the times are generated when packets are decoded on buffered data. - // We could compare global + local methods to see if the positions are sensible - if (aircraft->m_cprValid[0] && aircraft->m_cprValid[1] - && (std::abs(aircraft->m_cprTime[0].toMSecsSinceEpoch() - aircraft->m_cprTime[1].toMSecsSinceEpoch()) <= 8500) - && !aircraft->m_onSurface) - { - // Global decode using odd and even frames (C.2.6) - - // Calculate latitude - const double dLatEven = 360.0/60.0; - const double dLatOdd = 360.0/59.0; - double latEven, latOdd; - double latitude, longitude; - int ni, m; - - int j = std::floor(59.0f*aircraft->m_cprLat[0] - 60.0f*aircraft->m_cprLat[1] + 0.5); - latEven = dLatEven * (modulus(j, 60) + aircraft->m_cprLat[0]); - // Southern hemisphere is in range 270-360, so adjust to -90-0 - if (latEven >= 270.0f) - latEven -= 360.0f; - latOdd = dLatOdd * (modulus(j, 59) + aircraft->m_cprLat[1]); - if (latOdd >= 270.0f) - latOdd -= 360.0f; - if (aircraft->m_cprTime[0] >= aircraft->m_cprTime[1]) - latitude = latEven; - else - latitude = latOdd; - if ((latitude <= 90.0) && (latitude >= -90.0)) - { - // Check if both frames in same latitude zone - int latEvenNL = cprNL(latEven); - int latOddNL = cprNL(latOdd); - if (latEvenNL == latOddNL) + // Assume runway elevation is at first reported airboune altitude + if (wasOnSurface) { - // Calculate longitude - if (!f) - { - ni = cprN(latEven, 0); - m = std::floor(aircraft->m_cprLong[0] * (latEvenNL - 1) - aircraft->m_cprLong[1] * latEvenNL + 0.5f); - longitude = (360.0f/ni) * (modulus(m, ni) + aircraft->m_cprLong[0]); - } - else - { - ni = cprN(latOdd, 1); - m = std::floor(aircraft->m_cprLong[0] * (latOddNL - 1) - aircraft->m_cprLong[1] * latOddNL + 0.5f); - longitude = (360.0f/ni) * (modulus(m, ni) + aircraft->m_cprLong[1]); - } - if (longitude > 180.0f) - longitude -= 360.0f; - aircraft->m_latitude = latitude; - aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude); - aircraft->m_longitude = longitude; - aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude); - aircraft->m_positionDateTime = dateTime; - QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); - aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); - aircraft->m_coordinateDateTimes.push_back(dateTime); - updatePosition(aircraft); + aircraft->m_runwayAltitude = aircraft->m_altitude; + aircraft->m_runwayAltitudeValid = true; } } - else - { - qDebug() << "ADSBDemodGUI::handleADSB: Invalid latitude " << latitude << " for " << QString("%1").arg(aircraft->m_icaoHex) - << " m_cprLat[0] " << aircraft->m_cprLat[0] - << " m_cprLat[1] " << aircraft->m_cprLat[1]; - aircraft->m_cprValid[0] = false; - aircraft->m_cprValid[1] = false; - } } - else + + if (tc != 0) { - // Local decode using a single aircraft position + location of receiver - // Only valid if airborne within 180nm/333km (C.2.6.4) or 45nm for surface + int f; + double latCpr, lonCpr; + unsigned t = (data[2] >> 3) & 1; // Not valid for TIS-B + bool globalValid = false; + double globalLatitude, globalLongitude; + bool localValid = false; + double localLatitude, localLongitude; - // Calculate latitude - const double maxDeg = aircraft->m_onSurface ? 90.0 : 360.0; - const double dLatEven = maxDeg/60.0; - const double dLatOdd = maxDeg/59.0; - double dLat = f ? dLatOdd : dLatEven; - double latitude, longitude; + decodeCpr(data, f, latCpr, lonCpr); - int j = std::floor(m_azEl.getLocationSpherical().m_latitude/dLat) + std::floor(modulus(m_azEl.getLocationSpherical().m_latitude, dLat)/dLat - aircraft->m_cprLat[f] + 0.5); - latitude = dLat * (j + aircraft->m_cprLat[f]); + aircraft->m_cprValid[f] = true; + aircraft->m_cprLat[f] = latCpr; + aircraft->m_cprLong[f] = lonCpr; + aircraft->m_cprTime[f] = dateTime; - // Calculate longitude - double dLong; - int latNL = cprNL(latitude); - if (f == 0) + // CPR decoding + // Refer to Technical Provisions for Mode S Services and Extended Squitter - Appendix C2.6 + // See also: https://mode-s.org/decode/adsb/airborne-position.html + // For global decoding, we need both odd and even frames + // We also need to check that both frames aren't greater than 10s (airborne) or 50s (surface) apart in time (C.2.6.7), otherwise position may be out by ~10deg + // I've reduced this to 8.5s, as problems have been seen where times are just 9s apart + const int maxTimeDiff = aircraft->m_onSurface ? 48500 : 8500; + if (aircraft->m_cprValid[0] && aircraft->m_cprValid[1] + && (std::abs(aircraft->m_cprTime[0].toMSecsSinceEpoch() - aircraft->m_cprTime[1].toMSecsSinceEpoch()) <= maxTimeDiff) + && !aircraft->m_onSurface) { - if (latNL > 0) - dLong = maxDeg / latNL; + // Global decode using odd and even frames (C.2.6) + globalValid = decodeGlobalPosition(f, aircraft->m_cprLat, aircraft->m_cprLong, aircraft->m_cprTime, globalLatitude, globalLongitude, true); + if (!globalValid) + { + aircraft->m_cprValid[0] = false; + aircraft->m_cprValid[1] = false; + } else - dLong = maxDeg; + { + if (!aircraft->m_globalPosition) + { + aircraft->m_globalPosition = true; + double latDiff = abs(globalLatitude - aircraft->m_latitude); + double lonDiff = abs(globalLongitude - aircraft->m_longitude); + double maxLatDiff = 50000/111000.0; + double maxLonDiff = cos(Units::degreesToRadians(globalLatitude)) * maxLatDiff; + if ((latDiff > maxLatDiff) || (lonDiff > maxLonDiff)) + { + qDebug() << "Aircraft global position a long way from local - deleting track" << aircraft->m_icaoHex << globalLatitude << aircraft->m_latitude << globalLongitude << aircraft->m_longitude; + aircraft->clearCoordinates(&m_aircraftModel); + } + } + } } - else - { - if ((latNL - 1) > 0) - dLong = maxDeg / (latNL - 1); - else - dLong = maxDeg; - } - int m = std::floor(m_azEl.getLocationSpherical().m_longitude/dLong) + std::floor(modulus(m_azEl.getLocationSpherical().m_longitude, dLong)/dLong - aircraft->m_cprLong[f] + 0.5); - longitude = dLong * (m + aircraft->m_cprLong[f]); - if (updateLocalPosition(aircraft, latitude, longitude, aircraft->m_onSurface)) + // Local decode using a single aircraft position + location of receiver or previous global position + localValid = decodeLocalPosition(f, aircraft->m_cprLat[f], aircraft->m_cprLong[f], aircraft->m_onSurface, dateTime, aircraft, localLatitude, localLongitude, true); + + if (aircraft->m_globalPosition && !localValid) { - aircraft->m_latitude = latitude; - aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude); - aircraft->m_longitude = longitude; - aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude); - aircraft->m_positionDateTime = dateTime; - QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); - aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); - aircraft->m_coordinateDateTimes.push_back(dateTime); + qDebug() << "Aircraft local position not valid" << aircraft->m_icaoHex << localLatitude << aircraft->m_latitude << localLongitude << aircraft->m_longitude; + aircraft->m_globalPosition = false; + } + + bool positionsInconsistent = false; + if (globalValid && localValid) + { + double latDiff = abs(globalLatitude - localLatitude); + double lonDiff = abs(globalLongitude - localLongitude); + double maxLatDiff = 5.1/111000.0; + double maxLonDiff = cos(Units::degreesToRadians(globalLatitude)) * maxLatDiff; + positionsInconsistent = (latDiff > maxLatDiff) || (lonDiff > maxLonDiff); + if (positionsInconsistent) + { + qDebug() << "positionsInconsistent" << aircraft->m_icaoHex << globalLatitude << localLatitude << globalLongitude << localLongitude; + aircraft->m_cprValid[0] = false; + aircraft->m_cprValid[1] = false; + } + } + if (!positionsInconsistent) + { + if (globalValid) + { + updateAircraftPosition(aircraft, globalLatitude, globalLongitude, dateTime); + aircraft->addCoordinate(dateTime, &m_aircraftModel); + } + else if (localValid) + { + updateAircraftPosition(aircraft, localLatitude, localLongitude, dateTime); + aircraft->addCoordinate(dateTime, &m_aircraftModel); + } } } } @@ -1683,83 +2312,49 @@ void ADSBDemodGUI::handleADSB( { // Airborne velocity - BDS 0,9 int st = data[4] & 0x7; // Subtype + + if (df == 17) + { + int nacv = ((data[5] >> 3) & 0x3); // Navigation accuracy for velocity + + aircraft->m_nacvItem->setData(Qt::DisplayRole, m_nacvStrings[nacv]); + } + if ((st == 1) || (st == 2)) { // Ground speed - int s_ew = (data[5] >> 2) & 1; // East-west velocity sign - int v_ew = ((data[5] & 0x3) << 8) | (data[6] & 0xff); // East-west velocity - int s_ns = (data[7] >> 7) & 1; // North-south velocity sign - int v_ns = ((data[7] & 0x7f) << 3) | ((data[8] >> 5) & 0x7); // North-south velocity + float v, h; - int v_we; - int v_sn; - float v; - float h; + decodeGroundspeed(data, v, h); - if (s_ew) { - v_we = -1 * (v_ew - 1); - } else { - v_we = v_ew - 1; - } - if (s_ns) { - v_sn = -1 * (v_ns - 1); - } else { - v_sn = v_ns - 1; - } - v = std::round(std::sqrt(v_we*v_we + v_sn*v_sn)); - h = std::atan2(v_we, v_sn) * 360.0/(2.0*M_PI); - if (h < 0.0) { - h += 360.0; - } - - aircraft->m_heading = h; // This is actually track, rather than heading - aircraft->m_headingValid = true; - aircraft->m_headingDateTime = dateTime; - aircraft->m_groundspeed = v; - aircraft->m_groundspeedValid = true; - aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); - aircraft->m_groundspeedItem->setData(Qt::DisplayRole,m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_groundspeed) : aircraft->m_groundspeed); - aircraft->m_orientationDateTime = dateTime; + clearOldHeading(aircraft, dateTime, h); + aircraft->setTrack(h, dateTime); + aircraft->setGroundspeed(v, m_settings); } else { // Airspeed (only likely to get this if an aircraft is unable to determine it's position) - int s_hdg = (data[5] >> 2) & 1; // Heading status - int hdg = ((data[5] & 0x3) << 8) | (data[6] & 0xff); // Heading - if (s_hdg) - { - aircraft->m_heading = hdg/1024.0f*360.0f; - aircraft->m_headingValid = true; - aircraft->m_headingDateTime = dateTime; - aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); - aircraft->m_orientationDateTime = dateTime; + bool tas; + int as; + bool hdgValid; + float hdg; + + decodeAirspeed(data, tas, as, hdgValid, hdg); + + if (hdgValid) { + aircraft->setHeading(hdg, dateTime); } - int as_t = (data[7] >> 7) & 1; // Airspeed type - int as = ((data[7] & 0x7f) << 3) | ((data[8] >> 5) & 0x7); // Airspeed - - if (as_t == 1) - { - aircraft->m_trueAirspeed = as; - aircraft->m_trueAirspeedValid = true; - aircraft->m_trueAirspeedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_trueAirspeed) : aircraft->m_trueAirspeed); - } - else - { - aircraft->m_indicatedAirspeed = as; - aircraft->m_indicatedAirspeedValid = true; - aircraft->m_indicatedAirspeedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_indicatedAirspeed) : aircraft->m_indicatedAirspeed); + if (tas) { + aircraft->setTrueAirspeed(as, m_settings); + } else { + aircraft->setIndicatedAirspeed(as, dateTime, m_settings); } } - //int vr_source = (data[8] >> 4) & 1; // Source of vertical rate GNSS=0 Baro=1 - int s_vr = (data[8] >> 3) & 1; // Vertical rate sign - int vr = ((data[8] & 0x7) << 6) | ((data[9] >> 2) & 0x3f); // Vertical rate - aircraft->m_verticalRate = (vr-1)*64*(s_vr?-1:1); - aircraft->m_verticalRateValid = true; - if (m_settings.m_siUnits) - aircraft->m_verticalRateItem->setData(Qt::DisplayRole, Units::feetPerMinToIntegerMetresPerSecond(aircraft->m_verticalRate)); - else - aircraft->m_verticalRateItem->setData(Qt::DisplayRole, aircraft->m_verticalRate); + + int verticalRate; + decodeVerticalRate(data, verticalRate); + aircraft->setVerticalRate(verticalRate, m_settings); } else if (tc == 28) { @@ -1805,6 +2400,7 @@ void ADSBDemodGUI::handleADSB( aircraft->m_baro = baro; aircraft->m_baroValid = true; aircraft->m_baroItem->setData(Qt::DisplayRole, std::round(aircraft->m_baro)); + updateQNH(aircraft, baro); } bool selHeadingValid = (data[7] >> 2) & 0x1; if (selHeadingValid) @@ -1827,10 +2423,29 @@ void ADSBDemodGUI::handleADSB( bool vnavMode = (data[10] >> 7) & 0x1; bool altHoldMode = (data[10] >> 6) & 0x1; bool approachMode = (data[10] >> 4) & 0x1; + bool tcasOperational = (data[10] >> 3) & 0x1; bool lnavMode = (data[10] >> 2) & 0x1; + aircraft->m_autopilot = autoPilot; + aircraft->m_autopilotValid = true; aircraft->m_apItem->setText(autoPilot ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + aircraft->m_vnavMode = vnavMode; + aircraft->m_vnavModeValid = true; + aircraft->m_altHoldMode = altHoldMode; + aircraft->m_altHoldModeValid = true; + aircraft->m_approachMode = approachMode; + aircraft->m_approachModeValid = true; + aircraft->m_tcasOperational = tcasOperational; + aircraft->m_tcasOperationalValid = true; + aircraft->m_lnavMode = lnavMode; + aircraft->m_lnavModeValid = true; + + if (aircraft->m_tcasItem->text() != "RA") { + aircraft->m_tcasItem->setText(tcasOperational ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + } + + // Should only have one of these active QString vMode = ""; if (vnavMode) { vMode = vMode + "VNAV "; @@ -1856,9 +2471,320 @@ void ADSBDemodGUI::handleADSB( } } + else if (tc == 24) + { + // Surface system status + // Get quite a few from heathrow data + } else if (tc == 31) { - // Aircraft operation status + // Aircraft operational status + int st = data[4] & 0x7; + + int adsbVersion = (data[9] >> 5) & 0x7; + + aircraft->m_adsbVersion = adsbVersion; + aircraft->m_adsbVersionValid = true; + aircraft->m_versionItem->setData(Qt::DisplayRole, aircraft->m_adsbVersion); + + if (adsbVersion == 2) + { + bool nicSupplementA = (data[9] >> 4) & 0x1; + int nacp = data[9] & 0xf; + int sil = (data[10] >> 4) & 0x3; + bool hrd = (data[10] >> 2) & 0x1; // Whether headings are magnetic (false) or true North (true) + bool silSuppliment = (data[10] >> 1) & 0x1; // 0 per hour / 1 per sample + + aircraft->m_nicSupplementA = nicSupplementA; + aircraft->m_nicSupplementAValid = true; + + static const QStringList nacpStrings = { + ">= 10 NM", "< 10 NM", "< 4 NM", "< 2 NM", "< 1NM", "< 0.5NM", "< 0.3 NM", "< 0.1 NM", + "< 0.05 NM", "< 30 m", "< 10 m", "< 3 m", "Reserved", "Reserved", "Reserved", "Reserved" + }; + static const QStringList silStrings = { + "> 1e-3", "<= 1e-3", "<= 1e-5", "<= 1e-7" + }; + static const QStringList silSupplimentStrings = { + "ph", "ps" + }; + QString silString = QString("%1 %2").arg(silStrings[sil]).arg(silSupplimentStrings[silSuppliment]); + + aircraft->m_nacpItem->setData(Qt::DisplayRole, nacpStrings[nacp]); + aircraft->m_silItem->setData(Qt::DisplayRole, silString); + + if (st == 0) + { + // Airborne + unsigned capacityClassCode = ((data[5] & 0xff) << 8) | (data[6] & 0xff); + bool tcasOperational = (capacityClassCode >> 13) & 1; + bool adsbIn = (capacityClassCode >> 12) & 1; + + aircraft->m_tcasOperational = tcasOperational; + aircraft->m_tcasOperationalValid = true; + + int gva = (data[10] >> 6) & 0x3; + bool nicBaro = (data[10] >> 3) & 0x1; + + static const QStringList gvaStrings = { + "> 150 m", "<= 150 m", "<= 45 m", "Reserved" + }; + + aircraft->m_gvaItem->setData(Qt::DisplayRole, gvaStrings[gva]); + aircraft->m_nicBaroItem->setText(nicBaro ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + } + else if (st == 1) + { + // Surface + unsigned capacityClassCode = ((data[5] & 0xff) << 4) | ((data[6] >> 4) & 0xf); + unsigned lengthWidthCode = data[6] & 0xf; + int nacv = (capacityClassCode >> 1) & 0x7; + bool nicSupplementC = capacityClassCode & 1; + + aircraft->m_nicSupplementC = nicSupplementC; + aircraft->m_nicSupplementCValid = true; + + aircraft->m_nacvItem->setData(Qt::DisplayRole, m_nacvStrings[nacv]); + + switch (lengthWidthCode) + { + case 0: + aircraft->m_lengthItem->setData(Qt::DisplayRole, ""); + aircraft->m_widthItem->setData(Qt::DisplayRole, ""); + break; + case 1: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 15); + aircraft->m_widthItem->setData(Qt::DisplayRole, 23); + break; + case 2: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 25); + aircraft->m_widthItem->setData(Qt::DisplayRole, 28.5); + break; + case 3: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 25); + aircraft->m_widthItem->setData(Qt::DisplayRole, 34); + break; + case 4: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 35); + aircraft->m_widthItem->setData(Qt::DisplayRole, 33); + break; + case 5: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 35); + aircraft->m_widthItem->setData(Qt::DisplayRole, 38); + break; + case 6: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 45); + aircraft->m_widthItem->setData(Qt::DisplayRole, 39.5); + break; + case 7: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 45); + aircraft->m_widthItem->setData(Qt::DisplayRole, 45); + break; + case 8: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 55); + aircraft->m_widthItem->setData(Qt::DisplayRole, 45); + break; + case 9: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 55); + aircraft->m_widthItem->setData(Qt::DisplayRole, 52); + break; + case 10: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 65); + aircraft->m_widthItem->setData(Qt::DisplayRole, 59.5); + break; + case 11: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 65); + aircraft->m_widthItem->setData(Qt::DisplayRole, 67); + break; + case 12: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 75); + aircraft->m_widthItem->setData(Qt::DisplayRole, 72.5); + break; + case 13: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 75); + aircraft->m_widthItem->setData(Qt::DisplayRole, 80); + break; + case 14: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 85); + aircraft->m_widthItem->setData(Qt::DisplayRole, 80); + break; + case 15: + aircraft->m_lengthItem->setData(Qt::DisplayRole, 85); + aircraft->m_widthItem->setData(Qt::DisplayRole, 90); + break; + } + } + else + { + // Reserved sub-type + } + + unsigned operationalMode = ((data[7] & 0xff) << 8) | (data[8] & 0xff); + bool tcasActive = (operationalMode >> 13) & 1; + bool identActive = (operationalMode >> 12) & 1; + + if (tcasActive) + { + aircraft->m_tcasItem->setForeground(QBrush(Qt::red)); + aircraft->m_tcasItem->setText("RA"); + } + else if (aircraft->m_tcasOperationalValid) + { + aircraft->m_tcasItem->setForeground(QBrush()); + aircraft->m_tcasItem->setText(aircraft->m_tcasOperational ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + } + else + { + aircraft->m_tcasItem->setText(""); + } + aircraft->m_identItem->setText(identActive ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + + } + } + else + { + // 23, 25, 26, 27, 30 reserved + //qDebug() << "ADSBDemodGUI: Unsupported tc" << tc << aircraft->m_icaoHex << aircraft->m_adsbFrameCount; + } + + // Horizontal containment radius limit (Rc) and Navigation integrity category (NIC) + if (tc == 0) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "?"); + aircraft->m_radius = -1.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 0); + } + else if (tc == 5) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 7.5 m"); + aircraft->m_radius = 7.5f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 11); + } + else if (tc == 6) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 25 m"); + aircraft->m_radius = 25.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 10); + } + else if ((tc == 7) && aircraft->m_nicSupplementAValid && aircraft->m_nicSupplementA) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 75 m"); + aircraft->m_radius = 75.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 9); + } + else if (tc == 7) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.1 NM"); + aircraft->m_radius = 185.2f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 8); + } + else if (tc == 8) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, ">= 0.1 NM"); + aircraft->m_radius = 185.2f; // ? + aircraft->m_nicItem->setData(Qt::DisplayRole, 0); + } + else if (tc == 9) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 7.5 m"); + aircraft->m_radius = 7.5f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 11); + } + else if (tc == 10) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 25 m"); + aircraft->m_radius = 25.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 10); + } + else if ((tc == 11) && aircraft->m_nicSupplementAValid && aircraft->m_nicSupplementA && aircraft->m_nicSupplementBValid && aircraft->m_nicSupplementB) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 75 m"); + aircraft->m_radius = 75.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 9); + } + else if (tc == 11) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.1 NM"); + aircraft->m_radius = 185.2f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 8); + } + else if (tc == 12) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.2 NM"); + aircraft->m_radius = 370.4f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 7); + } + else if ((tc == 13) && aircraft->m_nicSupplementAValid && !aircraft->m_nicSupplementA && aircraft->m_nicSupplementBValid && aircraft->m_nicSupplementB) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.3 m"); + aircraft->m_radius = 1111.2f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 6); + } + else if ((tc == 13) && aircraft->m_nicSupplementAValid && aircraft->m_nicSupplementA && aircraft->m_nicSupplementBValid && aircraft->m_nicSupplementB) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.6 m"); + aircraft->m_radius = 1111.2f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 6); + } + else if (tc == 13) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 0.5 NM"); + aircraft->m_radius = 926.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 6); + } + else if (tc == 14) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 1 NM"); + aircraft->m_radius = 1852.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 5); + } + else if (tc == 15) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 2 NM"); + aircraft->m_radius = 3704.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 4); + } + else if ((tc == 16) && aircraft->m_nicSupplementAValid && aircraft->m_nicSupplementA && aircraft->m_nicSupplementBValid && aircraft->m_nicSupplementB) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 4 m"); + aircraft->m_radius = 7408.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 3); + } + else if (tc == 16) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 8 NM"); + aircraft->m_radius = 14816.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 2); + } + else if (tc == 17) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 20 NM"); + aircraft->m_radius = 37040.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 1); + } + else if (tc == 18) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, ">= 20 NM"); + aircraft->m_radius = 37040.0f; // ? + aircraft->m_nicItem->setData(Qt::DisplayRole, 0); + } + else if (tc == 20) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 7.5 m"); + aircraft->m_radius = 7.5f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 11); + } + else if (tc == 21) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, "< 25 m"); + aircraft->m_radius = 25.0f; + aircraft->m_nicItem->setData(Qt::DisplayRole, 10); + } + else if (tc == 22) + { + aircraft->m_radiusItem->setData(Qt::DisplayRole, ">= 25 m"); + aircraft->m_radius = 25.0f; // ? + aircraft->m_nicItem->setData(Qt::DisplayRole, 0); } // Update aircraft in map @@ -1888,18 +2814,271 @@ void ADSBDemodGUI::handleADSB( } else if (df == 18) { - // TIS-B that doesn't match ADS-B formats, such as TIS-B management + // TIS-B that doesn't match ADS-B formats, such as TIS-B management or Coarse TIB-B position (TODO) qDebug() << "TIS B message cf=" << ca << " icao: " << QString::number(icao, 16); } else if ((df == 4) || (df == 5)) { - decodeModeS(data, df, aircraft); + decodeModeS(data, dateTime, df, aircraft); } else if ((df == 20) || (df == 21)) { - decodeModeS(data, df, aircraft); + decodeModeS(data, dateTime, df, aircraft); decodeCommB(data, dateTime, df, aircraft, updatedCallsign); } + else if (df == 11) + { + // All call reply + // Extract 6-bit IC (Interegator Code (II or SI)) which is the address of the Mode-S ground radar + int bytes = data.length(); + unsigned parity = ((data[bytes-3] & 0xff) << 16) | ((data[bytes-2] & 0xff) << 8) | (data[bytes-1] & 0xff); + unsigned ca = data[0] & 0x7; + unsigned d = (parity ^ crc); + unsigned ii = d & 0xf; + unsigned cl = (d >> 4) & 0x7; + bool siCode = cl > 0; + + if ((ca >= 1) && (ca <= 3)) { + return; + } + + if (cl <= 4) + { + unsigned si = ii + (16 * (cl - 1)); + unsigned ic = siCode ? si : ii; + + aircraft->m_interogatorCodeItem->setData(Qt::DisplayRole, ic); + if (ic > 0) { + m_interogators[ic - 1].update(ic, aircraft, &m_airspaceModel, ui->ic, m_settings.m_displayIC[ic - 1]); + } + } + else + { + // This can happen quite frequency when running with a low correlation threshold + // So the IC map probably not very reliable in that case + //qDebug() << "ADSBDemodGUI: DF11 cl out of range" << Qt::hex << cl << d << parity << crc << aircraft->m_icaoHex << aircraft->m_adsbFrameCount; + return; + } + } + else if (df == 0) + { + // Short air-to-air ACAS (Airborne Collision Avoidance System) + bool onSurface = (data[0] >> 2) & 1; + unsigned crosslinkCapability = (data[0] >> 1) & 1; + unsigned spare1 = data[0] & 1; + unsigned sensitivityLevel = (data[1] >> 5) & 0x7; + unsigned spare2 = (data[1] >> 3) & 0x3; + unsigned replyInfo = ((data[1] & 0x7) << 1) | ((data[2] >> 7) & 1); + unsigned spare3 = (data[2] >> 5) & 0x3; + + if ((spare1 != 0) || (spare2 != 0) || (spare3 != 0)) { + qDebug() << "df == 0, SPARE SET TO 1" << aircraft->m_icaoHex; + return; + } + + QString acasCapability; + switch (replyInfo) + { + case 0: + acasCapability = "No ACAS"; + break; + case 2: + acasCapability = "No RA"; + break; + case 3: + acasCapability = "V"; + break; + case 4: + acasCapability = "V+H"; + break; + } + if (!acasCapability.isEmpty()) { + aircraft->m_acasItem->setText(acasCapability); + } + + QString maxSpeed; + switch (replyInfo) + { + case 8: + maxSpeed = "No data"; + break; + case 9: + maxSpeed = "<75"; + break; + case 10: + maxSpeed = "75-150"; + break; + case 11: + maxSpeed = "150-300"; + break; + case 12: + maxSpeed = "300-600"; + break; + case 13: + maxSpeed = "600-1200"; + break; + case 14: + maxSpeed = ">1200"; + break; + } + if (!maxSpeed.isEmpty()) { + aircraft->m_maxSpeedItem->setText(maxSpeed); + } + + decodeModeSAltitude(data, dateTime, aircraft); + } + else if (df == 16) + { + // Long air-to-air ACAS (Airborne Collision Avoidance System) + + decodeModeSAltitude(data, dateTime, aircraft); + + unsigned dv = data[4] & 0xff; + unsigned replyInfo = ((data[1] & 0x7) << 1) | ((data[2] >> 7) & 1); + + QString acasCapability; + switch (replyInfo) + { + case 0: + acasCapability = "No ACAS"; + break; + case 2: + acasCapability = "No RA"; + break; + case 3: + acasCapability = "V"; + break; + case 4: + acasCapability = "V+H"; + break; + } + + if (dv == 0x30) + { + // TODO: Merge in decodeCommB? + unsigned activeRAs = ((data[5] & 0xff) << 6) | ((data[6] >> 2) & 0x3f); + unsigned racs = ((data[6] & 0x3) << 2) | ((data[7] >> 6) & 0x3); + bool raTerminated = (data[7] >> 5) & 1; + bool multipleThreats = (data[7] >> 4) & 1; + unsigned threatType = (data[7] >> 2) & 0x3; + + QString threats; + bool msb = (activeRAs >> 13) & 0x1; + if (!msb) + { + if (!multipleThreats) { + threats = "No threats"; + } else { + threats = "Multiple threats"; + } + } + else + { + threats = "One threat"; + } + + QStringList racStrings; + if (racs & 1) { + racStrings.append("Do not turn right"); + } + if (racs & 2) { + racStrings.append("Do not turn left"); + } + if (racs & 4) { + racStrings.append("Do not pass above"); + } + if (racs & 8) { + racStrings.append("Do not pass below"); + } + + QString threatData; + if (threatType == 1) + { + unsigned threatICAO = ((data[7] & 0x3) << 22) | ((data[8] & 0xff) << 14) | ((data[9] & 0xff) << 6) | ((data[10] >> 2) & 0x3f); + threatData = QString::number(threatICAO, 16); + } + + if (raTerminated) + { + aircraft->m_raItem->setText("RA terminated"); + } + else + { + QStringList s; + s.append(threats); + s.append(racStrings.join("")); + s.append(threatData); + aircraft->m_raItem->setText(s.join(" ")); + } + } + else + { + decodeCommB(data, dateTime, df, aircraft, updatedCallsign); + } + } + else + { + qDebug() << "ADSBDemodGUI: Unsupported df" << df; + } + + // Update stats + m_dfStats[df]++; + updateDFStats(df); + if ((df == 17) || (df == 18)) { + m_adsbFrameRateCount++; + } else { + m_modesFrameRateCount++; + } + m_totalBytes += data.size(); + + aircraft->m_rxTime = dateTime; + aircraft->m_updateTime = QDateTime::currentDateTime(); + QTime time = dateTime.time(); + aircraft->m_timeItem->setText(QString("%1:%2:%3").arg(time.hour(), 2, 10, QLatin1Char('0')).arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0'))); + if (df == 17) + { + aircraft->m_adsbFrameCount++; + aircraft->m_adsbFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount); + } + else if (df == 18) + { + if (ca == 0) + { + aircraft->m_nonTransponderFrameCount++; + aircraft->m_nonTransponderItem->setData(Qt::DisplayRole, aircraft->m_nonTransponderFrameCount); + } + else if (ca == 6) + { + aircraft->m_adsrFrameCount++; + aircraft->m_adsrFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsrFrameCount); + } + else + { + aircraft->m_tisBFrameCount++; + aircraft->m_tisBFrameCountItem->setData(Qt::DisplayRole, aircraft->m_tisBFrameCount); + } + } + else + { + aircraft->m_modesFrameCount++; + aircraft->m_modesFrameCountItem->setData(Qt::DisplayRole, aircraft->m_modesFrameCount); + } + aircraft->m_totalFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount + aircraft->m_tisBFrameCount + aircraft->m_modesFrameCount); + + if (correlation < aircraft->m_minCorrelation) + aircraft->m_minCorrelation = correlation; + if (correlation > aircraft->m_maxCorrelation) + aircraft->m_maxCorrelation = correlation; + m_correlationAvg(correlation); + aircraft->m_correlationAvg(correlation); + aircraft->m_correlation = aircraft->m_correlationAvg.instantAverage(); + aircraft->m_correlationItem->setText(QString("%1/%2/%3") + .arg(CalcDb::dbPower(aircraft->m_minCorrelation), 3, 'f', 1) + .arg(CalcDb::dbPower(aircraft->m_correlation), 3, 'f', 1) + .arg(CalcDb::dbPower(aircraft->m_maxCorrelation), 3, 'f', 1)); + m_correlationOnesAvg(correlationOnes); + aircraft->m_rssiItem->setText(QString("%1") + .arg(CalcDb::dbPower(m_correlationOnesAvg.instantAverage()), 3, 'f', 1)); // Check to see if we need to emit a notification about this aircraft checkDynamicNotification(aircraft); @@ -1908,9 +3087,482 @@ void ADSBDemodGUI::handleADSB( if ((aircraft == m_highlightAircraft) && (newAircraft || updatedCallsign)) { updatePhotoText(aircraft); } + + PROFILER_STOP("ADS-B decode"); } -void ADSBDemodGUI::decodeModeS(const QByteArray data, int df, Aircraft *aircraft) +void ADSBDemodGUI::Interogator::update(int ic, Aircraft *aircraft, AirspaceModel *airspaceModel, CheckList *checkList, bool display) +{ + if (aircraft->m_positionValid) + { + if (m_valid) + { + bool changed = false; + + if (aircraft->m_latitude < m_minLatitude) + { + m_minLatitude = aircraft->m_latitude; + changed = true; + } + if (aircraft->m_latitude > m_maxLatitude) + { + m_maxLatitude = aircraft->m_latitude; + changed = true; + } + if (aircraft->m_longitude < m_minLongitude) + { + m_minLongitude = aircraft->m_longitude; + changed = true; + } + if (aircraft->m_longitude > m_maxLongitude) + { + m_maxLongitude = aircraft->m_longitude; + changed = true; + } + if (changed) + { + calcPoly(); + if (display) { + airspaceModel->airspaceUpdated(&m_airspace); + } + } + } + else + { + const double minSize = 0.02; + QBrush brush(QColor(colors[ic*3], colors[ic*3+1], colors[ic*3+2], 0xa0)); + + m_valid = true; + m_minLatitude = aircraft->m_latitude - minSize; + m_maxLatitude = aircraft->m_latitude + minSize; + m_minLongitude = aircraft->m_longitude - minSize; + m_maxLongitude = aircraft->m_longitude + minSize; + m_airspace.m_name = QString("IC %1").arg(ic); + m_airspace.m_bottom.m_alt = -1; + m_airspace.m_top.m_alt = -1; + calcPoly(); + if (display) + { + airspaceModel->addAirspace(&m_airspace); + checkList->addCheckItem(QString::number(ic), ic, Qt::Checked)->setBackground(brush); + } + else + { + checkList->addCheckItem(QString::number(ic), ic, Qt::Unchecked)->setBackground(brush); + } + checkList->setSortRole(Qt::UserRole + 1); // Sort via data rather than label + checkList->model()->sort(0, Qt::AscendingOrder); + } + } +} + +void ADSBDemodGUI::Interogator::calcPoly() +{ + double w = (m_maxLongitude - m_minLongitude) / 2.0; + double h = (m_maxLatitude - m_minLatitude) / 2.0; + double x = m_minLongitude + w; + double y = m_minLatitude + h; + m_airspace.m_center.setX(x); + m_airspace.m_center.setY(y); + m_airspace.m_position = m_airspace.m_center; + + const int s = 15; + m_airspace.m_polygon.resize(360/s+1); + + for (int d = 0, i = 0; d <= 360; d += s, i++) + { + double a = Units::degreesToRadians((double) d); + double r1 = cos(a) * w; + double r2 = sin(a) * h; + + m_airspace.m_polygon[i] = QPointF(x + r1, y + r2); + } +} + +// Clear heading if much older than latest track figure or too different +void ADSBDemodGUI::clearOldHeading(Aircraft *aircraft, const QDateTime& dateTime, float newTrack) +{ + if (aircraft->m_headingValid + && (aircraft->m_heading != newTrack) + && ( (std::abs(newTrack - aircraft->m_trackWhenHeadingSet) >= 5) + || (aircraft->m_headingDateTime.secsTo(dateTime) >= 10) + ) + ) + { + aircraft->m_headingValid = false; + aircraft->m_headingItem->setData(Qt::DisplayRole, ""); + } +} + +void ADSBDemodGUI::decodeID(const QByteArray& data, QString& emitterCategory, QString& callsign) +{ + // Aircraft identification - BDS 0,8 + unsigned tc = ((data[4] >> 3) & 0x1f); // Type code + int ec = data[4] & 0x7; // Emitter category + + if (tc == 4) { + emitterCategory = m_categorySetA[ec]; + } else if (tc == 3) { + emitterCategory = m_categorySetB[ec]; + } else if (tc == 2) { + emitterCategory = m_categorySetC[ec]; + } else { + emitterCategory = QStringLiteral("Reserved"); + } + + // Flight/callsign - Extract 8 6-bit characters from 6 8-bit bytes, MSB first + unsigned char c[8]; + c[0] = (data[5] >> 2) & 0x3f; // 6 + c[1] = ((data[5] & 0x3) << 4) | ((data[6] & 0xf0) >> 4); // 2+4 + c[2] = ((data[6] & 0xf) << 2) | ((data[7] & 0xc0) >> 6); // 4+2 + c[3] = (data[7] & 0x3f); // 6 + c[4] = (data[8] >> 2) & 0x3f; + c[5] = ((data[8] & 0x3) << 4) | ((data[9] & 0xf0) >> 4); + c[6] = ((data[9] & 0xf) << 2) | ((data[10] & 0xc0) >> 6); + c[7] = (data[10] & 0x3f); + // Map to ASCII + char callsignASCII[9]; + for (int i = 0; i < 8; i++) + callsignASCII[i] = m_idMap[c[i]]; + callsignASCII[8] = '\0'; + callsign = QString(callsignASCII).trimmed(); +} + +void ADSBDemodGUI::decodeGroundspeed(const QByteArray& data, float& v, float& h) +{ + int s_ew = (data[5] >> 2) & 1; // East-west velocity sign + int v_ew = ((data[5] & 0x3) << 8) | (data[6] & 0xff); // East-west velocity + int s_ns = (data[7] >> 7) & 1; // North-south velocity sign + int v_ns = ((data[7] & 0x7f) << 3) | ((data[8] >> 5) & 0x7); // North-south velocity + + int v_we; + int v_sn; + + if (s_ew) { + v_we = -1 * (v_ew - 1); + } else { + v_we = v_ew - 1; + } + if (s_ns) { + v_sn = -1 * (v_ns - 1); + } else { + v_sn = v_ns - 1; + } + v = std::round(std::sqrt(v_we*v_we + v_sn*v_sn)); + h = std::atan2(v_we, v_sn) * 360.0/(2.0*M_PI); + if (h < 0.0) { + h += 360.0; + } +} + +void ADSBDemodGUI::decodeAirspeed(const QByteArray& data, bool& tas, int& as, bool& hdgValid, float& hdg) +{ + hdgValid = (data[5] >> 2) & 1; // Heading status + int hdgFix = ((data[5] & 0x3) << 8) | (data[6] & 0xff); // Heading + hdg = hdgFix / 1024.0f * 360.0f; + + tas = (data[7] >> 7) & 1; // Airspeed type (true or indicated) + as = ((data[7] & 0x7f) << 3) | ((data[8] >> 5) & 0x7); // Airspeed +} + +void ADSBDemodGUI::decodeVerticalRate(const QByteArray& data, int& verticalRate) +{ + int s_vr = (data[8] >> 3) & 1; // Vertical rate sign + int vr = ((data[8] & 0x7) << 6) | ((data[9] >> 2) & 0x3f); // Vertical rate + verticalRate = (vr-1)*64*(s_vr?-1:1); +} + +// Called when we have both lat & long +void ADSBDemodGUI::updateAircraftPosition(Aircraft *aircraft, double latitude, double longitude, const QDateTime& dateTime) +{ + // Calculate range, azimuth and elevation to aircraft from station + m_azEl.setTarget(latitude, longitude, aircraft->m_altitudeValid ? Units::feetToMetres(aircraft->m_altitude) : 0); + m_azEl.calculate(); + + aircraft->m_latitude = latitude; + aircraft->m_longitude = longitude; + aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude); + aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude); + aircraft->m_positionDateTime = dateTime; + + if (!aircraft->m_positionValid) + { + aircraft->m_positionValid = true; + // Now we have a position, add a plane to the map + m_aircraftModel.addAircraft(aircraft); + } + + aircraft->m_range = m_azEl.getDistance(); + aircraft->m_azimuth = m_azEl.getAzimuth(); + aircraft->m_elevation = m_azEl.getElevation(); + aircraft->m_rangeItem->setText(QString::number(aircraft->m_range/1000.0, 'f', 1)); + aircraft->m_azElItem->setText(QString("%1/%2").arg(std::round(aircraft->m_azimuth)).arg(std::round(aircraft->m_elevation))); + if (aircraft == m_trackAircraft) { + m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation, aircraft->m_range); + } + if (m_azEl.getDistance() > m_maxRangeStat) + { + m_maxRangeStat = m_azEl.getDistance(); + ui->statsTable->item(MAX_RANGE, 0)->setData(Qt::DisplayRole, m_maxRangeStat / 1000.0); + } + if (aircraft->m_altitudeValid && (aircraft->m_altitude > m_maxAltitudeStat)) + { + m_maxAltitudeStat = aircraft->m_altitude; + ui->statsTable->item(MAX_ALTITUDE, 0)->setData(Qt::DisplayRole, m_maxAltitudeStat); + } + if (aircraft->m_altitudeValid) { + updateCoverageMap(m_azEl.getAzimuth(), m_azEl.getElevation(), m_azEl.getDistance(), aircraft->m_altitude); + } +} + +bool ADSBDemodGUI::validateGlobalPosition(double latitude, double longitude, bool countFailure) +{ + // Log files could be from anywhere, so allow any position + if (m_loadingData) { + return true; + } + + // Calculate range to aircraft from station + m_azEl.setTarget(latitude, longitude, 0); + m_azEl.calculate(); + + // Reasonableness test + if (m_azEl.getDistance() < 600000) + { + return true; + } + else if (countFailure) + { + m_rangeFails++; + ui->statsTable->item(RANGE_FAILS, 0)->setData(Qt::DisplayRole, m_rangeFails); + } +} + +// Called when we have lat & long from local decode and we need to check if it is in a valid range (<180nm/333km airborne or 45nm/83km for surface) +bool ADSBDemodGUI::validateLocalPosition(double latitude, double longitude, bool surfacePosition, bool countFailure) +{ + // Calculate range to aircraft from station + m_azEl.setTarget(latitude, longitude, 0); + m_azEl.calculate(); + + // Don't use the full 333km, as there may be some error in station position + if (m_azEl.getDistance() < (surfacePosition ? 80000 : 320000)) + { + return true; + } + else if (countFailure) + { + m_rangeFails++; + ui->statsTable->item(RANGE_FAILS, 0)->setData(Qt::DisplayRole, m_rangeFails); + } +} + +bool ADSBDemodGUI::decodeGlobalPosition(int f, const double cprLat[2], const double cprLong[2], const QDateTime cprTime[2], double& latitude, double& longitude, bool countFailure) +{ + // Calculate latitude + const double dLatEven = 360.0/60.0; + const double dLatOdd = 360.0/59.0; + double latEven, latOdd; + int ni, m; + + int j = std::floor(59.0f*cprLat[0] - 60.0f*cprLat[1] + 0.5); + latEven = dLatEven * (modulus(j, 60) + cprLat[0]); + // Southern hemisphere is in range 270-360, so adjust to -90-0 + if (latEven >= 270.0f) { + latEven -= 360.0f; + } + latOdd = dLatOdd * (modulus(j, 59) + cprLat[1]); + if (latOdd >= 270.0f) { + latOdd -= 360.0f; + } + if (cprTime[0] >= cprTime[1]) { + latitude = latEven; + } else { + latitude = latOdd; + } + if ((latitude <= 90.0) && (latitude >= -90.0)) + { + // Check if both frames in same latitude zone + int latEvenNL = cprNL(latEven); + int latOddNL = cprNL(latOdd); + + if (latEvenNL == latOddNL) + { + // Calculate longitude + if (!f) + { + ni = cprN(latEven, 0); + m = std::floor(cprLong[0] * (latEvenNL - 1) - cprLong[1] * latEvenNL + 0.5f); + longitude = (360.0f/ni) * (modulus(m, ni) + cprLong[0]); + } + else + { + ni = cprN(latOdd, 1); + m = std::floor(cprLong[0] * (latOddNL - 1) - cprLong[1] * latOddNL + 0.5f); + longitude = (360.0f/ni) * (modulus(m, ni) + cprLong[1]); + } + if (longitude > 180.0f) { + longitude -= 360.0f; + } + return validateGlobalPosition(latitude, longitude, countFailure); + } + else + { + return false; + } + } + else + { + return false; + } +} + +// Only valid if airborne within 180nm/333km (C.2.6.4) or 45nm for surface +bool ADSBDemodGUI::decodeLocalPosition(int f, double cprLat, double cprLong, bool onSurface, const QDateTime& dateTime, const Aircraft *aircraft, double& latitude, double& longitude, bool countFailure) +{ + double refLatitude; + double refLongitude; + + if (aircraft->m_globalPosition) + { + refLatitude = aircraft->m_latitude; + refLongitude = aircraft->m_longitude; + } + else + { + refLatitude = m_azEl.getLocationSpherical().m_latitude; + refLongitude = m_azEl.getLocationSpherical().m_longitude; + } + + // Calculate latitude + const double maxDeg = onSurface ? 90.0 : 360.0; + const double dLatEven = maxDeg/60.0; + const double dLatOdd = maxDeg/59.0; + double dLat = f ? dLatOdd : dLatEven; + + int j = std::floor(refLatitude/dLat) + std::floor(modulus(refLatitude, dLat)/dLat - cprLat + 0.5); + latitude = dLat * (j + cprLat); + + // Calculate longitude + double dLong; + int latNL = cprNL(latitude); + if (f == 0) + { + if (latNL > 0) { + dLong = maxDeg / latNL; + } else { + dLong = maxDeg; + } + } + else + { + if ((latNL - 1) > 0) { + dLong = maxDeg / (latNL - 1); + } else { + dLong = maxDeg; + } + } + int m = std::floor(refLongitude/dLong) + std::floor(modulus(refLongitude, dLong)/dLong - cprLong + 0.5); + longitude = dLong * (m + cprLong); + + if (aircraft->m_globalPosition) + { + // Reasonableness spec is 2.5nm / 30 seconds - we're less stringent + double latDiff = abs(refLatitude - latitude); + double lonDiff = abs(refLongitude - longitude); + double maxLatDiff = 20000/111000.0; + double maxLonDiff = cos(Units::degreesToRadians(latitude)) * maxLatDiff; + if ((latDiff > maxLatDiff) || (lonDiff > maxLonDiff)) { + if (countFailure) { + //qDebug() << "FAIL" << latDiff << maxLatDiff << lonDiff << maxLonDiff; + } + } + return !((latDiff > maxLatDiff) || (lonDiff > maxLonDiff)); + } + else + { + // Check position is within range of antenna + return validateLocalPosition(latitude, longitude, onSurface, countFailure); + } +} + +void ADSBDemodGUI::decodeCpr(const QByteArray& data, int& f, double& latCpr, double& lonCpr) const +{ + f = (data[6] >> 2) & 1; // CPR odd/even frame - should alternate every 0.2s + int latCprFix = ((data[6] & 3) << 15) | ((data[7] & 0xff) << 7) | ((data[8] >> 1) & 0x7f); + int lonCprFix = ((data[8] & 1) << 16) | ((data[9] & 0xff) << 8) | (data[10] & 0xff); + + latCpr = latCprFix / 131072.0; + lonCpr = lonCprFix / 131072.0; +} + +// This is relative to pressure of 1013.25 - it isn't corrected according to aircraft baro setting - so we can appear underground +bool ADSBDemodGUI::decodeAltitude(const QByteArray& data, int& altFt) const +{ + int alt = ((data[5] & 0xff) << 4) | ((data[6] >> 4) & 0xf); // Altitude + if (alt == 0) { + return false; + } + int q = (alt & 0x10) != 0; + int n = ((alt >> 1) & 0x7f0) | (alt & 0xf); // Remove Q-bit + + if (q == 1) { + altFt = n * ((alt & 0x10) ? 25 : 100) - 1000; + } else { + altFt = gillhamToFeet(n); + } + return true; +} + +// Mode S pressure altitude reported in 100ft or 25ft increments +// Note that we can get Mode-S altitude when still on the ground +void ADSBDemodGUI::decodeModeSAltitude(const QByteArray& data, const QDateTime dateTime, Aircraft *aircraft) +{ + int altitude = 0; // Altitude in feet + int altitudeCode = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); + + if (altitudeCode & 0x40) // M bit indicates metres + { + int altitudeMetres = ((altitudeCode & 0x1f80) >> 1) | (altitudeCode & 0x3f); + altitude = Units::metresToFeet(altitudeMetres); + } + else + { + // Remove M and Q bits + int altitudeFix = ((altitudeCode & 0x1f80) >> 2) | ((altitudeCode & 0x20) >> 1) | (altitudeCode & 0xf); + + // Convert to feet + if (altitudeCode & 0x10) { + altitude = altitudeFix * 25 - 1000; + } else { + altitude = gillhamToFeet(altitudeFix); + } + } + + // We can frequently get false decodes of df 4 frames, so check altitude is reasonable, based on previous value + // If greater than an unlikely climb rate, ignore, as we don't want the aircraft jumping around the 3D map + bool altUnlikely = false; + if (aircraft->m_altitudeValid) + { + int altDiff = abs(aircraft->m_altitude - altitude); + if (altDiff > 500) + { + qint64 msecs = aircraft->m_altitudeDateTime.msecsTo(dateTime); + float climbRate = altDiff / (msecs / (1000.0f * 60.0f)); + if (climbRate > 6000.0f) + { + altUnlikely = true; + m_altFails++; + ui->statsTable->item(ALT_FAILS, 0)->setData(Qt::DisplayRole, m_altFails); + } + } + } + if (!altUnlikely) { + aircraft->setAltitude(altitude, false, dateTime, m_settings); + } +} + +void ADSBDemodGUI::decodeModeS(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft) { bool wasOnSurface = aircraft->m_onSurface; bool takenOff = false; @@ -1920,10 +3572,12 @@ void ADSBDemodGUI::decodeModeS(const QByteArray data, int df, Aircraft *aircraft { takenOff = wasOnSurface; aircraft->m_onSurface = false; + aircraft->m_onSurfaceValid = true; } else if ((flightStatus == 1) || (flightStatus == 3)) { aircraft->m_onSurface = true; + aircraft->m_onSurfaceValid = true; } if (wasOnSurface != aircraft->m_onSurface) { @@ -1931,37 +3585,12 @@ void ADSBDemodGUI::decodeModeS(const QByteArray data, int df, Aircraft *aircraft aircraft->m_cprValid[0] = false; aircraft->m_cprValid[1] = false; } - //qDebug() << "Flight Status " << m_flightStatuses[flightStatus]; - - int altitude = 0; // Altitude in feet if ((df == 4) || (df == 20)) { - int altitudeCode = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); - if (altitudeCode & 0x40) // M bit indicates metres - { - int altitudeMetres = ((altitudeCode & 0x1f80) >> 1) | (altitudeCode & 0x3f); - altitude = Units::metresToFeet(altitudeMetres); - } - else - { - // Remove M and Q bits - int altitudeFix = ((altitudeCode & 0x1f80) >> 2) | ((altitudeCode & 0x20) >> 1) | (altitudeCode & 0xf); + decodeModeSAltitude(data, dateTime, aircraft); - // Convert to feet - if (altitudeCode & 0x10) { - altitude = altitudeFix * 25 - 1000; - } else { - altitude = gillhamToFeet(altitudeFix); - } - } - - aircraft->m_altitude = altitude; - aircraft->m_altitudeValid = true; - aircraft->m_altitudeGNSS = false; - aircraft->m_altitudeItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::feetToIntegerMetres(aircraft->m_altitude) : aircraft->m_altitude); - - // Assume runway elevation is at first reported airboune altitude + // Assume runway elevation is at first reported airboune altitude if (takenOff) { aircraft->m_runwayAltitude = aircraft->m_altitude; @@ -1986,6 +3615,26 @@ void ADSBDemodGUI::decodeModeS(const QByteArray data, int df, Aircraft *aircraft } } +static bool isSpeedAndHeadingInconsitent(float speed1, float heading1, float speed2, float heading2) +{ + return (abs(speed1 - speed2) > 50) || (abs(heading1 - heading2) > 45); +} + +static bool isVerticalRateInconsistent(int verticalRate1, int verticalRate2) +{ + return abs(verticalRate1 - verticalRate2) > 1500; +} + +static bool isAltitudeInconsistent(int altitude1, int altitude2) +{ + return abs(altitude1 - altitude2) > 1500; // 30s at climb rate of 3kft/m +} + +static bool isPositionInconsistent(double latitude1, double longitude1, double latitude2, double longitude2) +{ + return (abs(latitude1 - latitude2) > 0.2f) || (abs(longitude1 - longitude2) > 0.3f); // 1 deg lat is ~70 miles. 500mph is ~8miles per minute +} + void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft, bool &updatedCallsign) { // We only see downlink messages, so do not know the data format, so have to decode all possibilities @@ -2009,9 +3658,90 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, const int maxAlt = 46000; // Maximum expected altitude for commercial jet const float maxHeadingDiff = 20.0f; // Maximum difference in heading/track + // BDS 0,5 - Extended squitter airborne position (tc=11 typically seen in DF16) + + int altitude_0_5; + bool altitudeValid_0_5 = decodeAltitude(data, altitude_0_5); + // Require an altitude having been received by other means, otherwise too high chance of it being incorrect + bool altitudeInconsistent_0_5 = !altitudeValid_0_5 || (aircraft->m_altitudeValid && isAltitudeInconsistent(altitude_0_5, aircraft->m_altitude)); + + const unsigned tc = ((data[4] >> 3) & 0x1f); + bool tcInconsistent = !((tc == 0) || ((tc >= 5) && (tc <= 8)) || ((tc >= 9) && (tc << 18)) || ((tc >= 20) && (tc <= 22))); // Only position type codes + + int f; + double latCpr, lonCpr; + decodeCpr(data, f, latCpr, lonCpr); + bool cprValid[2] = {f ? aircraft->m_cprValid[0] : true, f ? true : aircraft->m_cprValid[1]}; + double cprLat[2] = {f ? aircraft->m_cprLat[0] : latCpr, f ? latCpr : aircraft->m_cprLat[1]}; + double cprLon[2] = {f ? aircraft->m_cprLong[0] : lonCpr, f ? lonCpr : aircraft->m_cprLong[1]}; + QDateTime cprTime[2] = {f ? aircraft->m_cprTime[0] : dateTime, f ? dateTime : aircraft->m_cprTime[1]}; + double latitude_0_5, longitude_0_5; + double positionValid_0_5 = false; + if (cprValid[0] && cprValid[1] + && (std::abs(cprTime[0].toMSecsSinceEpoch() - cprTime[1].toMSecsSinceEpoch()) <= 8500) + && !aircraft->m_onSurface) + { + if (decodeGlobalPosition(f, cprLat, cprLon, cprTime, latitude_0_5, longitude_0_5, false)) { + positionValid_0_5 = true; + } + } + else + { + if (decodeLocalPosition(f, cprLat[f], cprLon[f], aircraft->m_onSurface, dateTime, aircraft, latitude_0_5, longitude_0_5, false)) { + positionValid_0_5 = true; + } + } + // Require a position having been received by other means, otherwise too high chance of it being incorrect + bool positionInconsistent_0_5 = !positionValid_0_5 || (aircraft->m_positionValid && isPositionInconsistent(aircraft->m_latitude, aircraft->m_longitude, latitude_0_5, longitude_0_5)); + + bool bds_0_5 = ((tc >= 9) && (tc <= 18)) && !positionInconsistent_0_5 && !altitudeInconsistent_0_5 && !tcInconsistent; + + // BDS 0,8 - Aircraft identification and category + + QString emitterCategory, callsign_0_8; + + decodeID(data, emitterCategory, callsign_0_8); + + bool emitterCategoryInconsistent = (emitterCategory == "Reserved") || (emitterCategory != aircraft->m_emitterCategory); + bool callsignInconsistent_0_8 = callsign_0_8.isEmpty() || callsign_0_8.contains('#'); // Callsign can change between flights + + bool bds_0_8 = ((tc >= 1) && (tc <= 4)) && !emitterCategoryInconsistent && !callsignInconsistent_0_8; + + // BDS 0,9 - Airborne Velocity + + int st = data[4] & 0x7; // Subtype + bool groundspeedSubType = (st == 1) || (st == 2); + float groundspeed_0_9, track; + bool airspeedType_0_9; + int airspeed; + bool headingValid; + float heading; + if (groundspeedSubType) + { + // Ground speed + decodeGroundspeed(data, groundspeed_0_9, track); + } + else + { + // Airspeed + decodeAirspeed(data, airspeedType_0_9, airspeed, headingValid, heading); + } + int verticalRate; + decodeVerticalRate(data, verticalRate); + + bool groundspeedInconsistent = groundspeedSubType + && aircraft->m_groundspeedValid + && isSpeedAndHeadingInconsitent(groundspeed_0_9, track, aircraft->m_groundspeed, aircraft->m_track); + bool airspeedInconsistent = !groundspeedSubType + && (airspeedType_0_9 ? aircraft->m_trueAirspeedValid : aircraft->m_indicatedAirspeedValid) + && isSpeedAndHeadingInconsitent(airspeed, heading, airspeedType_0_9 ? aircraft->m_trueAirspeed : aircraft->m_indicatedAirspeed, aircraft->m_heading); + bool verticalRateInconsistent = aircraft->m_verticalRateValid && isVerticalRateInconsistent(verticalRate, aircraft->m_verticalRate); + + bool bds_0_9 = (tc == 19) && !groundspeedInconsistent && !airspeedInconsistent && !verticalRateInconsistent; + // BDS 1,0 - ELS - bool bds_1_0 = (data[4] == 0x10) && ((data[5] & 0x7c) == 0x00); + bool bds_1_0 = ((data[4] & 0xff) == 0x10) && ((data[5] & 0x7c) == 0x00); // BDS 1,7 - Common usage GICB capability report - ELS @@ -2127,7 +3857,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, // Flight/callsign - Extract 8 6-bit characters from 6 8-bit bytes, MSB first unsigned char c[9]; - char callsign[9]; + char callsignASCII[9]; c[0] = (data[5] >> 2) & 0x3f; // 6 c[1] = ((data[5] & 0x3) << 4) | ((data[6] & 0xf0) >> 4); // 2+4 c[2] = ((data[6] & 0xf) << 2) | ((data[7] & 0xc0) >> 6); // 4+2 @@ -2138,12 +3868,12 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, c[7] = (data[10] & 0x3f); // Map to ASCII for (int i = 0; i < 8; i++) { - callsign[i] = m_idMap[c[i]]; + callsignASCII[i] = m_idMap[c[i]]; } - callsign[8] = '\0'; - QString callsignTrimmed = QString(callsign).trimmed(); - bool invalidCallsign = QString(callsign).contains('#'); - bool bds_2_0 = (data[4] == 0x20) && !invalidCallsign; + callsignASCII[8] = '\0'; + QString callsignTrimmed = QString(callsignASCII).trimmed(); + bool invalidCallsign = QString(callsignASCII).contains('#'); + bool bds_2_0 = ((data[4] & 0xff) == 0x20) && !invalidCallsign; // BDS 2,1 - Aircraft and airline registration markings @@ -2184,7 +3914,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, int acas = data[6] & 0x7f; int threatType = (data[7] >> 2) & 0x3; - bool bds_3_0 = (data[4] == 0x30) && (acas < 48) && (threatType != 3); + bool bds_3_0 = ((data[4] & 0xff) == 0x30) && (acas < 48) && (threatType != 3); // BDS 4,0 - Selected vertical information - EHS @@ -2194,7 +3924,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, bool mcpSelectedAltInconsistent = (mcpSelectedAlt > maxAlt) || (!mcpSelectedAltStatus && (mcpSelectedAltFix != 0)); bool fmsSelectedAltStatus = (data[5] >> 2) & 0x1; - int fmsSelectedAltFix = ((data[5] & 0x3) << 10) | ((data[6] & 0xff) << 2) || (data[7] >> 6) & 0x3; + int fmsSelectedAltFix = ((data[5] & 0x3) << 10) | ((data[6] & 0xff) << 2) | ((data[7] >> 6) & 0x3); int fmsSelectedAlt = fmsSelectedAltFix * 16; // ft bool fmsSelectedAltInconsistent = (fmsSelectedAlt > maxAlt) || (!fmsSelectedAltStatus && (fmsSelectedAltFix != 0)); @@ -2273,7 +4003,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, bool humidityInconsistent = (humidity > 100.0) || (!humidityStatus && (humidityFix != 0)); // Occasionally get frames: 20000000000000 or 08000000000000 - these seem unlikely to be BDS 4,4 - bool noMetData = ((data[4] == 0x20) || (data[4] == 0x08)) && !(data[5] || data[6] || data[7] || data[8] || data[9] || data[10]); + bool noMetData = (((data[4] & 0xff) == 0x20) || ((data[4] & 0xff) == 0x08)) && !(data[5] || data[6] || data[7] || data[8] || data[9] || data[10]); bool bds_4_4 = !noMetData && !fomSourceInconsistent && !windSpeedInconsistent && !staticAirTemperatureInconsistent && !averageStaticPressureInconsistent && !turbulenceInconsistent && !humidityInconsistent; @@ -2339,7 +4069,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, if (trueTrackAngle < 0.0f) { trueTrackAngle += 360.0f; } - bool trueTrackAngleInconsistent = (aircraft->m_headingValid && (abs(trueTrackAngle - aircraft->m_heading) > maxHeadingDiff)) + bool trueTrackAngleInconsistent = (aircraft->m_trackValid && (abs(trueTrackAngle - aircraft->m_track) > maxHeadingDiff)) // FIXME: compare to heading or track? || (!trueTrackAngleStatus && (trueTrackAngleFix != 0)); bool groundSpeedStatus = data[6] & 0x1; @@ -2374,20 +4104,20 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, int latitudeFix = ((data[4] & 0x3f) << 13) | ((data[5] & 0xff) << 5) | ((data[6] >> 3) & 0x1f); latitudeFix = (latitudeFix << 12) >> 12; - float latitude = latitudeFix * (360.0f / 1048576.0f); + float latitude_5_1 = latitudeFix * (360.0f / 1048576.0f); int longitudeFix = ((data[6] & 0x7) << 17) | ((data[7] & 0xff) << 9) | ((data[8] & 0xff) << 1) | ((data[9] >> 7) & 0x1); longitudeFix = (longitudeFix << 12) >> 12; - float longitude = longitudeFix * (360.0f / 1048576.0f); + float longitude_5_1 = longitudeFix * (360.0f / 1048576.0f); bool positionInconsistent = !aircraft->m_positionValid - || (positionValid && aircraft->m_positionValid && ((abs(latitude - aircraft->m_latitude) > 2.0f) || (abs(longitude - aircraft->m_longitude) > 2.0f))) + || (positionValid && aircraft->m_positionValid && isPositionInconsistent(latitude_5_1, longitude_5_1, aircraft->m_latitude, aircraft->m_longitude)) || (!positionValid && ((latitudeFix != 0) || (longitudeFix != 0))); int pressureAltFix = ((data[9] & 0x7f) << 8) | (data[10] & 0xff); pressureAltFix = (pressureAltFix << 17) >> 17; int pressureAlt = pressureAltFix * 8; - bool pressureAltInconsistent = (pressureAlt > 50000) || (pressureAlt < -1000) || (positionValid && aircraft->m_altitudeValid && (abs(pressureAlt - aircraft->m_altitude) > 2000)) + bool pressureAltInconsistent = (pressureAlt > 50000) || (pressureAlt < -1000) || (positionValid && aircraft->m_altitudeValid && isAltitudeInconsistent(pressureAlt, aircraft->m_altitude)) || (!positionValid && (pressureAltFix != 0)); bool bds_5_1 = !positionInconsistent && !pressureAltInconsistent; @@ -2474,10 +4204,51 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, bool bds_6_0 = !magHeadingInconsistent && !indicatedAirspeedInconsistent && !machInconsistent && !baroAltRateInconsistent && !verticalVelInconsistent; - int possibleMatches = bds_1_0 + bds_1_7 + bds_2_0 + bds_2_1 + bds_3_0 + bds_4_0 + bds_4_1 + bds_4_4 + bds_4_5 + bds_5_0 + bds_5_1 + bds_5_3 + bds_6_0; + int possibleMatches = bds_0_5 + bds_0_8 + bds_0_9 + bds_1_0 + bds_1_7 + bds_2_0 + bds_2_1 + bds_3_0 + bds_4_0 + bds_4_1 + bds_4_4 + bds_4_5 + bds_5_0 + bds_5_1 + bds_5_3 + bds_6_0; if (possibleMatches == 1) { + if (bds_0_5) + { + // Only use this data if position and altitude where compared against a previous position, otherwise there's a high chance it may be wrong + if (aircraft->m_altitudeValid && aircraft->m_positionValid) + { + if (altitudeValid_0_5) { + aircraft->setAltitude(altitude_0_5, (tc >= 20) && (tc <= 22), dateTime, m_settings); + } + if (positionValid_0_5) { + updateAircraftPosition(aircraft, latitude_0_5, longitude_0_5, dateTime); + } + if (altitudeValid_0_5 && positionValid_0_5) { + aircraft->addCoordinate(dateTime, &m_aircraftModel); + } + } + } + if (bds_0_8) + { + updatedCallsign = aircraft->m_callsign != callsign_0_8; + if (updatedCallsign) { + setCallsign(aircraft, callsign_0_8); + } + } + if (bds_0_9) + { + if (groundspeedSubType) + { + aircraft->setGroundspeed(groundspeed_0_9, m_settings); + aircraft->setTrack(track, dateTime); + } + else + { + if (airspeedType_0_9) { + aircraft->setTrueAirspeed(airspeed, m_settings); + } else { + aircraft->setIndicatedAirspeed(airspeed, dateTime, m_settings); + } + aircraft->setHeading(heading, dateTime); + } + aircraft->setVerticalRate(verticalRate, m_settings); + } if (bds_1_7) { // Some of these bits are dynamic, so can't assume that because a bit isn't set, @@ -2510,12 +4281,8 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, if (bds_2_0) { updatedCallsign = aircraft->m_callsign != callsignTrimmed; - if (updatedCallsign) - { - aircraft->m_callsign = callsignTrimmed; - aircraft->m_callsignItem->setText(aircraft->m_callsign); - atcCallsign(aircraft); - callsignToFlight(aircraft); + if (updatedCallsign) { + setCallsign(aircraft, callsignTrimmed); } } if (bds_2_1) @@ -2566,6 +4333,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, aircraft->m_baro = baroSetting; aircraft->m_baroValid = true; aircraft->m_baroItem->setData(Qt::DisplayRole, std::round(aircraft->m_baro)); + updateQNH(aircraft, baroSetting); } if (modeStatus) @@ -2582,6 +4350,13 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, } mode = mode.trimmed(); aircraft->m_vModeItem->setText(mode); + + aircraft->m_vnavMode = vnavMode; + aircraft->m_vnavModeValid = true; + aircraft->m_altHoldMode = altHoldMode; + aircraft->m_altHoldModeValid = true; + aircraft->m_approachMode = approachMode; + aircraft->m_approachModeValid = true; } } if (bds_4_1) @@ -2642,21 +4417,12 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, } if (trueTrackAngleStatus) { - aircraft->m_heading = trueTrackAngle; - aircraft->m_headingValid = true; - aircraft->m_headingDateTime = dateTime; - aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); - aircraft->m_orientationDateTime = dateTime; + clearOldHeading(aircraft, dateTime, trueTrackAngle); + aircraft->setTrack(trueTrackAngle, dateTime); } if (groundSpeedStatus) { - aircraft->m_groundspeed = groundSpeed; - aircraft->m_groundspeedValid = true; - if (m_settings.m_siUnits) { - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, Units::knotsToIntegerKPH(aircraft->m_groundspeed)); - } else { - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, aircraft->m_groundspeed); - } + aircraft->setGroundspeed(groundSpeed, m_settings); } if (trackAngleRateStatus) { @@ -2685,15 +4451,15 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, } if (bds_5_1) { - // Position is specified as "coarse" - is it worth using? - qDebug() << "BDS 5,1 - " + // Position is specified as "coarse" (lsb = 90/131072 deg which is ~76m. CPR accuracy is 5.1m) + /*qDebug() << "BDS 5,1 - " << "ICAO:" << aircraft->m_icaoHex - << "latitude:" << latitude << "(ADS-B:" << aircraft->m_latitude << ") " - << "longitude:" << longitude << "(ADS-B:" << aircraft->m_longitude << ") " + << "latitude:" << latitude_5_1 << "(ADS-B:" << aircraft->m_latitude << ") " + << "longitude:" << longitude_5_1 << "(ADS-B:" << aircraft->m_longitude << ") " << "pressureAlt:" << pressureAlt << "(ADS-B:" << aircraft->m_altitude << ")" << "m_bdsCapabilities[5][1]: " << aircraft->m_bdsCapabilities[5][1] << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; - ; + ;*/ } if (bds_5_3) { @@ -2710,6 +4476,9 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, } if (bds_6_0) { + if (arMagHeadingStatus) { + aircraft->setHeading(magHeading, dateTime); + } if (indicatedAirspeedStatus) { aircraft->m_indicatedAirspeed = indicatedAirspeed; @@ -2719,6 +4488,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, } else { aircraft->m_indicatedAirspeedItem->setData(Qt::DisplayRole, aircraft->m_indicatedAirspeed); } + aircraft->m_indicatedAirspeedDateTime = dateTime; } if (machStatus) { @@ -2726,15 +4496,8 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, aircraft->m_machValid = true; aircraft->m_machItem->setData(Qt::DisplayRole, aircraft->m_mach); } - if (verticalVelStatus) - { - aircraft->m_verticalRate = verticalVel; - aircraft->m_verticalRateValid = true; - if (m_settings.m_siUnits) { - aircraft->m_verticalRateItem->setData(Qt::DisplayRole, Units::feetPerMinToIntegerMetresPerSecond(aircraft->m_verticalRate)); - } else { - aircraft->m_verticalRateItem->setData(Qt::DisplayRole, aircraft->m_verticalRate); - } + if (verticalVelStatus) { + aircraft->setVerticalRate(verticalVel, m_settings); } } @@ -2746,6 +4509,9 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, { qDebug() << "DF" << df << "matches" << possibleMatches + << "bds_0_5" << bds_0_5 + << "bds_0_8" << bds_0_8 + << "bds_0_9" << bds_0_9 << "bds_1_0" << bds_1_0 << "bds_1_7" << bds_1_7 << "bds_2_0" << bds_2_0 @@ -2763,6 +4529,20 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, qDebug() << data.toHex(); + qDebug() << (bds_0_5 ? "+" : "-") + << "BDS 0,5 - " + << "altitude:" << altitude_0_5 << "(ADS-B:" << aircraft->m_altitude << ") " + << "latitude:" << latitude_0_5 << "(ADS-B:" << aircraft->m_latitude << ") " + << "longitude:" << longitude_0_5 << "(ADS-B:" << aircraft->m_longitude << ") "; + qDebug() << (bds_0_8 ? "+" : "-") + << "BDS 0,8 - " + << "emitterCategory:" << emitterCategory << "(ADS-B:" << aircraft->m_emitterCategory << ") " + << "callsign:" << callsign_0_8 << "(ADS-B:" << aircraft->m_callsign << ") "; + qDebug() << (bds_0_9 ? "+" : "-") + << "BDS 0,9 - " + << "groundspeedSubType:" << groundspeedSubType + << "speed:" << (groundspeedSubType ? groundspeed_0_9 : airspeed) << "(ADS-B:" << (groundspeedSubType ? aircraft->m_groundspeed : (airspeedType_0_9 ? aircraft->m_trueAirspeed : aircraft->m_indicatedAirspeed)) << ") " + << "verticalRate:" << verticalRate << "(ADS-B:" << aircraft->m_verticalRate << ") "; qDebug() << (bds_1_0 ? "+" : "-") << "BDS 1,0 - "; qDebug() << (bds_1_7 ? "+" : "-") @@ -2772,7 +4552,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, qDebug() << (bds_2_0 ? "+" : "-") << "BDS 2,0 - " << "ICAO:" << aircraft->m_icaoHex - << "Callsign:" << callsignTrimmed; + << "Callsign:" << callsignTrimmed << "(ADS-B:" << aircraft->m_callsign << ") "; qDebug() << (bds_2_1 ? "+" : "-") << "BDS 2,1 - " << "ICAO:" << aircraft->m_icaoHex @@ -2842,8 +4622,8 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, qDebug() << (bds_5_1 ? "+" : "-") << "BDS 5,1 - " << "ICAO:" << aircraft->m_icaoHex - << "latitude:" << latitude << "(ADS-B:" << aircraft->m_latitude << ") " - << "longitude:" << longitude << "(ADS-B:" << aircraft->m_longitude << ") " + << "latitude:" << latitude_5_1 << "(ADS-B:" << aircraft->m_latitude << ") " + << "longitude:" << longitude_5_1 << "(ADS-B:" << aircraft->m_longitude << ") " << "pressureAlt:" << pressureAlt << "(ADS-B:" << aircraft->m_altitude << ")" << "m_bdsCapabilities[5][1]: " << aircraft->m_bdsCapabilities[5][1] << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; @@ -2900,6 +4680,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime qDebug() << "Gear down as on surface " << aircraft->m_icaoHex; } animations->append(gearAnimation(dateTime, false)); + animations->append(gearAngle(dateTime, true)); aircraft->m_gearDown = true; } @@ -2940,6 +4721,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime qDebug() << "Pitch up " << aircraft->m_icaoHex; } aircraft->m_pitchEst = 5.0; + animations->append(gearAngle(dateTime, false)); } // Retract landing gear after take-off @@ -3014,12 +4796,14 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime } // Gear down for landing - // We don't know airport elevation, so just base on speed and descent rate + // We don't know airport elevation, so base on speed and descent rate if (!aircraft->m_gearDown && !aircraft->m_onSurface && !aircraft->m_runwayAltitudeValid - && (aircraft->m_verticalRateValid && (aircraft->m_verticalRate < 0)) - && (aircraft->m_groundspeedValid && (aircraft->m_groundspeed < gearDownSpeed)) + && (aircraft->m_verticalRateValid && (aircraft->m_verticalRate < 100)) + && ( (aircraft->m_groundspeedValid && (aircraft->m_groundspeed < gearDownSpeed)) + || (aircraft->m_altitudeValid && (aircraft->m_altitude < 2000)) + ) ) { if (debug) { @@ -3058,7 +4842,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime } else { - // Propellors + // Propellors/fans if (!aircraft->m_engineStarted && aircraft->m_groundspeedValid && (aircraft->m_groundspeed > 0)) { if (debug) { @@ -3105,24 +4889,24 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime { aircraft->m_rollEst = 0.0; } - else if (aircraft->m_headingValid) + else if (aircraft->m_trackValid) { // Really need to use more data points for this - or better yet, get it from Mode-S frames - if (aircraft->m_prevHeadingDateTime.isValid()) + if (aircraft->m_prevTrackDateTime.isValid()) { - qint64 msecs = aircraft->m_prevHeadingDateTime.msecsTo(aircraft->m_headingDateTime); + qint64 msecs = aircraft->m_prevTrackDateTime.msecsTo(aircraft->m_headingDateTime); if (msecs > 0) { - float headingDiff = fmod(aircraft->m_heading - aircraft->m_prevHeading + 540.0, 360.0) - 180.0; - float roll = headingDiff / (msecs / 1000.0); - //qDebug() << "Heading Diff " << headingDiff << " msecs " << msecs << " roll " << roll; + float trackDiff = fmod(aircraft->m_track - aircraft->m_prevTrack + 540.0, 360.0) - 180.0; + float roll = trackDiff / (msecs / 1000.0); + //qDebug() << "Track Diff " << trackDiff << " msecs " << msecs << " roll " << roll; roll = std::min(roll, 15.0f); roll = std::max(roll, -15.0f); aircraft->m_rollEst = roll; } } - aircraft->m_prevHeadingDateTime = aircraft->m_headingDateTime; - aircraft->m_prevHeading = aircraft->m_heading; + aircraft->m_prevTrackDateTime = aircraft->m_trackDateTime; + aircraft->m_prevTrack = aircraft->m_track; } return animations; @@ -3130,14 +4914,27 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime SWGSDRangel::SWGMapAnimation *ADSBDemodGUI::gearAnimation(QDateTime startDateTime, bool up) { - // Gear up/down + // Gear up (0.0) / down (1.0) SWGSDRangel::SWGMapAnimation *animation = new SWGSDRangel::SWGMapAnimation(); animation->setName(new QString("libxplanemp/controls/gear_ratio")); animation->setStartDateTime(new QString(startDateTime.toString(Qt::ISODateWithMs))); animation->setReverse(up); animation->setLoop(0); animation->setDuration(5); - animation->setMultiplier(0.2); + animation->setMultiplier(0.2f); + return animation; +} + +SWGSDRangel::SWGMapAnimation *ADSBDemodGUI::gearAngle(QDateTime startDateTime, bool flat) +{ + // Gear deflection - on ground it should be flat (0.0) in the air at an angle (1.0) + SWGSDRangel::SWGMapAnimation *animation = new SWGSDRangel::SWGMapAnimation(); + animation->setName(new QString("libxplanemp/gear/tire_vertical_deflection_mtr")); + animation->setStartDateTime(new QString(startDateTime.toString(Qt::ISODateWithMs))); + animation->setReverse(flat); + animation->setLoop(0); + animation->setDuration(1); + animation->setMultiplier(1); return animation; } @@ -3151,7 +4948,7 @@ SWGSDRangel::SWGMapAnimation *ADSBDemodGUI::flapsAnimation(QDateTime startDateTi animation->setReverse(retract); animation->setLoop(0); animation->setDuration(5*std::abs(flaps-currentFlaps)); - animation->setMultiplier(0.2); + animation->setMultiplier(0.2f); if (retract) { animation->setStartOffset(1.0 - currentFlaps); } else { @@ -3169,7 +4966,7 @@ SWGSDRangel::SWGMapAnimation *ADSBDemodGUI::slatsAnimation(QDateTime startDateTi animation->setReverse(retract); animation->setLoop(0); animation->setDuration(5); - animation->setMultiplier(0.2); + animation->setMultiplier(0.2f); return animation; } @@ -3212,6 +5009,9 @@ void ADSBDemodGUI::checkStaticNotification(Aircraft *aircraft) case ADSB_COL_MODEL: match = aircraft->m_modelItem->data(Qt::DisplayRole).toString(); break; + case ADSB_COL_TYPE: + match = aircraft->m_typeItem->data(Qt::DisplayRole).toString(); + break; case ADSB_COL_REGISTRATION: match = aircraft->m_registrationItem->data(Qt::DisplayRole).toString(); break; @@ -3378,6 +5178,7 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${icao}", aircraft->m_icaoItem->data(Qt::DisplayRole).toString()); s = s.replace("${callsign}", aircraft->m_callsignItem->data(Qt::DisplayRole).toString()); s = s.replace("${aircraft}", aircraft->m_modelItem->data(Qt::DisplayRole).toString()); + s = s.replace("${type}", aircraft->m_typeItem->data(Qt::DisplayRole).toString()); s = s.replace("${speed}", aircraft->m_groundspeedItem->data(Qt::DisplayRole).toString()); // For backwards compatibility s = s.replace("${gs}", aircraft->m_groundspeedItem->data(Qt::DisplayRole).toString()); s = s.replace("${tas}", aircraft->m_trueAirspeedItem->data(Qt::DisplayRole).toString()); @@ -3388,6 +5189,7 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${verticalRate}", aircraft->m_verticalRateItem->data(Qt::DisplayRole).toString()); s = s.replace("${selHeading}", aircraft->m_selHeadingItem->data(Qt::DisplayRole).toString()); s = s.replace("${heading}", aircraft->m_headingItem->data(Qt::DisplayRole).toString()); + s = s.replace("${track}", aircraft->m_trackItem->data(Qt::DisplayRole).toString()); s = s.replace("${turnRate}", aircraft->m_turnRateItem->data(Qt::DisplayRole).toString()); s = s.replace("${roll}", aircraft->m_rollItem->data(Qt::DisplayRole).toString()); s = s.replace("${range}", aircraft->m_rangeItem->data(Qt::DisplayRole).toString()); @@ -3395,6 +5197,7 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${category}", aircraft->m_emitterCategoryItem->data(Qt::DisplayRole).toString()); s = s.replace("${status}", aircraft->m_statusItem->data(Qt::DisplayRole).toString()); s = s.replace("${squawk}", aircraft->m_squawkItem->data(Qt::DisplayRole).toString()); + s = s.replace("${ident}", aircraft->m_identItem->data(Qt::DisplayRole).toString()); s = s.replace("${registration}", aircraft->m_registrationItem->data(Qt::DisplayRole).toString()); s = s.replace("${manufacturer}", aircraft->m_manufacturerNameItem->data(Qt::DisplayRole).toString()); s = s.replace("${owner}", aircraft->m_ownerItem->data(Qt::DisplayRole).toString()); @@ -3402,6 +5205,13 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${ap}", aircraft->m_apItem->data(Qt::DisplayRole).toString()); s = s.replace("${vMode}", aircraft->m_vModeItem->data(Qt::DisplayRole).toString()); s = s.replace("${lMode}", aircraft->m_lModeItem->data(Qt::DisplayRole).toString()); + s = s.replace("${tcas}", aircraft->m_tcasItem->data(Qt::DisplayRole).toString()); + s = s.replace("${acas}", aircraft->m_acasItem->data(Qt::DisplayRole).toString()); + s = s.replace("${ra}", aircraft->m_raItem->data(Qt::DisplayRole).toString()); + s = s.replace("${maxSpeed}", aircraft->m_maxSpeedItem->data(Qt::DisplayRole).toString()); + s = s.replace("${version}", aircraft->m_versionItem->data(Qt::DisplayRole).toString()); + s = s.replace("${length}", aircraft->m_lengthItem->data(Qt::DisplayRole).toString()); + s = s.replace("${width}", aircraft->m_widthItem->data(Qt::DisplayRole).toString()); s = s.replace("${baro}", aircraft->m_baroItem->data(Qt::DisplayRole).toString()); s = s.replace("${headwind}", aircraft->m_headwindItem->data(Qt::DisplayRole).toString()); s = s.replace("${windSpeed}", aircraft->m_windSpeedItem->data(Qt::DisplayRole).toString()); @@ -3411,10 +5221,12 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${humidity}", aircraft->m_humidityItem->data(Qt::DisplayRole).toString()); s = s.replace("${latitude}", aircraft->m_latitudeItem->data(Qt::DisplayRole).toString()); s = s.replace("${longitude}", aircraft->m_longitudeItem->data(Qt::DisplayRole).toString()); + s = s.replace("${ic}", aircraft->m_interogatorCodeItem->data(Qt::DisplayRole).toString()); s = s.replace("${rssi}", aircraft->m_rssiItem->data(Qt::DisplayRole).toString()); s = s.replace("${flightstatus}", aircraft->m_flightStatusItem->data(Qt::DisplayRole).toString()); s = s.replace("${departure}", aircraft->m_depItem->data(Qt::DisplayRole).toString()); s = s.replace("${arrival}", aircraft->m_arrItem->data(Qt::DisplayRole).toString()); + s = s.replace("${stops}", aircraft->m_stopsItem->data(Qt::DisplayRole).toString()); s = s.replace("${std}", aircraft->m_stdItem->data(Qt::DisplayRole).toString()); s = s.replace("${etd}", aircraft->m_etdItem->data(Qt::DisplayRole).toString()); s = s.replace("${atd}", aircraft->m_atdItem->data(Qt::DisplayRole).toString()); @@ -3460,11 +5272,21 @@ bool ADSBDemodGUI::handleMessage(const Message& message) else if (ADSBDemodReport::MsgReportDemodStats::match(message)) { ADSBDemodReport::MsgReportDemodStats& report = (ADSBDemodReport::MsgReportDemodStats&) message; - if (m_settings.m_displayDemodStats) - { - ADSBDemodStats stats = report.getDemodStats(); - ui->stats->setText(QString("ADS-B: %1 Mode-S: %2 Matches: %3 CRC: %4 Type: %5 Avg Corr: %6 Demod Time: %7 Feed Time: %8").arg(stats.m_adsbFrames).arg(stats.m_modesFrames).arg(stats.m_correlatorMatches).arg(stats.m_crcFails).arg(stats.m_typeFails).arg(CalcDb::dbPower(m_correlationAvg.instantAverage()), 1, 'f', 1).arg(stats.m_demodTime, 1, 'f', 3).arg(stats.m_feedTime, 1, 'f', 3)); - } + ADSBDemodStats stats = report.getDemodStats(); + + ui->statsTable->item(ADSB_FRAMES, 0)->setData(Qt::DisplayRole, stats.m_adsbFrames); + ui->statsTable->item(MODE_S_FRAMES, 0)->setData(Qt::DisplayRole, stats.m_modesFrames); + int totalFrames = stats.m_adsbFrames + stats.m_modesFrames; + ui->statsTable->item(TOTAL_FRAMES, 0)->setData(Qt::DisplayRole, totalFrames); + ui->statsTable->item(CORRELATOR_MATCHES, 0)->setData(Qt::DisplayRole, stats.m_correlatorMatches); + float percentValid = 100.0f * totalFrames / (float) (stats.m_correlatorMatches - stats.m_preambleFails); + ui->statsTable->item(PERCENT_VALID, 0)->setData(Qt::DisplayRole, QString::number(percentValid, 'f', 2)); + ui->statsTable->item(PREAMBLE_FAILS, 0)->setData(Qt::DisplayRole, stats.m_preambleFails); + ui->statsTable->item(CRC_FAILS, 0)->setData(Qt::DisplayRole, stats.m_crcFails); + ui->statsTable->item(TYPE_FAILS, 0)->setData(Qt::DisplayRole, stats.m_typeFails); + ui->statsTable->item(INVALID_FAILS, 0)->setData(Qt::DisplayRole, stats.m_invalidFails); + ui->statsTable->item(ICAO_FAILS, 0)->setData(Qt::DisplayRole, stats.m_icaoFails); + ui->statsTable->item(AVERAGE_CORRELATION, 0)->setData(Qt::DisplayRole, QString::number(CalcDb::dbPower(m_correlationAvg.instantAverage()), 'f', 1)); return true; } else if (ADSBDemod::MsgConfigureADSBDemod::match(message)) @@ -3541,6 +5363,13 @@ void ADSBDemodGUI::on_threshold_valueChanged(int value) applySetting("correlationThreshold"); } +void ADSBDemodGUI::on_chipsThreshold_valueChanged(int value) +{ + ui->chipsThresholdText->setText(QString("%1").arg(value)); + m_settings.m_chipsThreshold = value; + applySetting("chipsThreshold"); +} + void ADSBDemodGUI::on_phaseSteps_valueChanged(int value) { ui->phaseStepsText->setText(QString("%1").arg(value)); @@ -3625,6 +5454,15 @@ void ADSBDemodGUI::on_findOnMapFeature_clicked() } } +void ADSBDemodGUI::on_deleteAircraft_clicked() +{ + QHash::iterator i = m_aircraft.begin(); + + while (i != m_aircraft.end()) { + removeAircraft(i, i.value()); + } +} + // Find aircraft on channel map void ADSBDemodGUI::findOnChannelMap(Aircraft *aircraft) { @@ -3644,6 +5482,25 @@ void ADSBDemodGUI::findOnChannelMap(Aircraft *aircraft) #endif } +void ADSBDemodGUI::statsTable_customContextMenuRequested(QPoint pos) +{ + QTableWidgetItem *item = ui->statsTable->itemAt(pos); + if (item) + { + QMenu* tableContextMenu = new QMenu(ui->statsTable); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + + int row = item->row(); + QAction* copyAction = new QAction("Copy", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + } +} + void ADSBDemodGUI::adsbData_customContextMenuRequested(QPoint pos) { QTableWidgetItem *item = ui->adsbData->itemAt(pos); @@ -3654,8 +5511,10 @@ void ADSBDemodGUI::adsbData_customContextMenuRequested(QPoint pos) Aircraft *aircraft = nullptr; if (m_aircraft.contains(icao)) { aircraft = m_aircraft.value(icao); + } else { + return; } - QString icaoHex = QString("%1").arg(icao, 1, 16); + QString icaoHex = QString("%1").arg(icao, 6, 16, '0'); QMenu* tableContextMenu = new QMenu(ui->adsbData); connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); @@ -3688,7 +5547,7 @@ void ADSBDemodGUI::adsbData_customContextMenuRequested(QPoint pos) QAction* viewOpenSkyAction = new QAction("View aircraft on opensky-network.org...", tableContextMenu); connect(viewOpenSkyAction, &QAction::triggered, this, [icaoHex]()->void { - QDesktopServices::openUrl(QUrl(QString("https://opensky-network.org/aircraft-profile?icao24=%1").arg(icaoHex))); + QDesktopServices::openUrl(QUrl(QString("https://old.opensky-network.org/aircraft-profile?icao24=%1").arg(icaoHex))); }); tableContextMenu->addAction(viewOpenSkyAction); @@ -3705,11 +5564,11 @@ void ADSBDemodGUI::adsbData_customContextMenuRequested(QPoint pos) // Edit aircraft - if (!aircraft->m_aircraftInfo) + /*if (!aircraft->m_aircraftInfo) { QAction* addOpenSkyAction = new QAction("Add aircraft to opensky-network.org...", tableContextMenu); connect(addOpenSkyAction, &QAction::triggered, this, []()->void { - QDesktopServices::openUrl(QUrl(QString("https://opensky-network.org/edit-aircraft-profile"))); + QDesktopServices::openUrl(QUrl(QString("https://old.opensky-network.org/edit-aircraft-profile"))); }); tableContextMenu->addAction(addOpenSkyAction); } @@ -3718,10 +5577,16 @@ void ADSBDemodGUI::adsbData_customContextMenuRequested(QPoint pos) QAction* editOpenSkyAction = new QAction("Edit aircraft on opensky-network.org...", tableContextMenu); connect(editOpenSkyAction, &QAction::triggered, this, [icaoHex]()->void { - QDesktopServices::openUrl(QUrl(QString("https://opensky-network.org/edit-aircraft-profile?icao24=%1").arg(icaoHex))); + QDesktopServices::openUrl(QUrl(QString("https://old.opensky-network.org/edit-aircraft-profile?icao24=%1").arg(icaoHex))); }); tableContextMenu->addAction(editOpenSkyAction); - } + }*/ + + QAction* editSDMAction = new QAction("Edit aircraft on sdm.virtualradarserver.co.uk...", tableContextMenu); + connect(editSDMAction, &QAction::triggered, this, []()->void { + QDesktopServices::openUrl(QUrl(QString("https://sdm.virtualradarserver.co.uk/Edit/Aircraft"))); + }); + tableContextMenu->addAction(editSDMAction); // Find on Map if (aircraft->m_positionValid) @@ -3841,19 +5706,13 @@ void ADSBDemodGUI::on_spb_currentIndexChanged(int value) applySetting("samplesPerBi"); } -void ADSBDemodGUI::on_correlateFullPreamble_clicked(bool checked) -{ - m_settings.m_correlateFullPreamble = checked; - applySetting("correlateFullPreamble"); -} - void ADSBDemodGUI::on_demodModeS_clicked(bool checked) { m_settings.m_demodModeS = checked; applySetting("demodModeS"); } -void ADSBDemodGUI::on_getOSNDB_clicked() +void ADSBDemodGUI::on_getAircraftDB_clicked() { // Don't try to download while already in progress if (m_progressDialog == nullptr) @@ -3890,16 +5749,54 @@ void ADSBDemodGUI::on_getAirspacesDB_clicked() } } +void ADSBDemodGUI::on_coverage_clicked(bool checked) +{ + m_settings.m_displayCoverage = checked; + applySetting("displayCoverage"); + updateAirspaces(); +} + +void ADSBDemodGUI::on_displayChart_clicked(bool checked) +{ + m_settings.m_displayChart = checked; + applySetting("displayChart"); + ui->chart->setVisible(m_settings.m_displayChart); +} + +void ADSBDemodGUI::on_stats_clicked(bool checked) +{ + m_settings.m_displayDemodStats = checked; + ui->statsTable->setVisible(m_settings.m_displayDemodStats); + applySetting("displayDemodStats"); +} + +void ADSBDemodGUI::on_ic_globalCheckStateChanged(int state) +{ + for (int i = 0; i < ui->ic->count(); i++) + { + int ic = ui->ic->itemText(i).toInt(); + int idx = ic - 1; + bool checked = ui->ic->isChecked(i); + m_settings.m_displayIC[idx] = checked; + bool visible = m_airspaceModel.contains(&m_interogators[idx].m_airspace); + if (checked && !visible) { + m_airspaceModel.addAirspace(&m_interogators[idx].m_airspace); + } else if (!checked && visible) { + m_airspaceModel.removeAirspace(&m_interogators[idx].m_airspace); + } + } +} + void ADSBDemodGUI::on_flightPaths_clicked(bool checked) { m_settings.m_flightPaths = checked; - m_aircraftModel.setFlightPaths(checked); + m_aircraftModel.setSettings(&m_settings); } void ADSBDemodGUI::on_allFlightPaths_clicked(bool checked) { m_settings.m_allFlightPaths = checked; - m_aircraftModel.setAllFlightPaths(checked); + m_aircraftModel.setSettings(&m_settings); } void ADSBDemodGUI::on_atcLabels_clicked(bool checked) @@ -3909,6 +5806,20 @@ void ADSBDemodGUI::on_atcLabels_clicked(bool checked) applySetting("atcLabels"); } +void ADSBDemodGUI::on_displayOrientation_clicked(bool checked) +{ + m_settings.m_displayOrientation = checked; + applySetting("displayOrientation"); + ui->splitter->setOrientation(m_settings.m_displayOrientation ? Qt::Horizontal : Qt::Vertical); +} + +void ADSBDemodGUI::on_displayRadius_clicked(bool checked) +{ + m_settings.m_displayRadius = checked; + setShowContainmentRadius(m_settings.m_displayRadius); + applySetting("displayRadius"); +} + QString ADSBDemodGUI::getDataDir() { // Get directory to store app data in (aircraft & airport databases and user-definable icons) @@ -4061,13 +5972,79 @@ void ADSBDemodGUI::get3DModel(Aircraft *aircraft) { if (mm->match(aircraft->m_aircraftInfo->m_model, aircraft->m_aircraftInfo->m_manufacturerName, aircraftType)) { + QString operatorICAO = aircraft->m_aircraftInfo->m_operatorICAO; + // Look for operator specific livery - if (!aircraft->m_aircraftInfo->m_operatorICAO.isEmpty()) { - model = get3DModel(aircraftType, aircraft->m_aircraftInfo->m_operatorICAO); + if (!operatorICAO.isEmpty()) { + model = get3DModel(aircraftType, operatorICAO); } - if (model.isEmpty()) { - // Try for aircraft with out specific livery - model = get3DModel(aircraftType); + + if (model.isEmpty()) + { + // Try similar operator (E.g. EasyJet instead of EasyJet Europe) + static const QHash alternateOperator = { + {"EJU", "EZY"}, + {"WUK", "WZZ"}, + {"TFL", "TOM"}, + {"NOZ", "NAX"}, + {"NSZ", "NAX"}, + {"BCS", "DHK"}, + }; + + if (alternateOperator.contains(operatorICAO)) + { + operatorICAO = alternateOperator.value(operatorICAO); + model = get3DModel(aircraftType, operatorICAO); + } + + if (model.isEmpty()) + { + + if (m_settings.m_favourLivery && !operatorICAO.isEmpty()) + { + // Try to find similar aircraft with matching livery + static const QHash alternateTypes = { + {"B788", {"B77W", "B77L", "B772", "B773", "B763", "A332", "A333"}}, + {"B77W", {"B77L", "B772", "B773", "B788", "B763", "A332", "A333"}}, + {"B77L", {"B77W", "B772", "B773", "B788", "B763", "A332", "A333"}}, + {"B772", {"B77W", "B77L", "B773", "B788", "B763", "A332", "A333"}}, + {"B773", {"B77W", "B77L", "B772", "B788", "B763", "A332", "A333"}}, + {"A332", {"A333", "B77W", "B77L", "B773", "B772", "B788", "B763"}}, + {"A333", {"A332", "B77W", "B77L", "B773", "B772", "B788", "B763"}}, + {"A342", {"A343", "A345", "A346"}}, + {"A343", {"A342", "A345", "A346"}}, + {"A345", {"A343", "A342", "A346"}}, + {"A346", {"A345", "A343", "A342"}}, + {"B744", {"B74F"}}, + {"B74F", {"B744"}}, + {"B733", {"B734", "B737", "B738", "B739", "B752", "A320", "A319", "A321"}}, + {"B734", {"B733", "B737", "B738", "B739", "B752", "A320", "A319", "A321"}}, + {"B737", {"B733", "B734", "B738", "B739", "B752", "A320", "A319", "A321"}}, + {"B738", {"B733", "B734", "B737", "B739", "B752", "A320", "A319", "A321"}}, + {"B739", {"B733", "B734", "B737", "B738", "B752", "A320", "A319", "A321"}}, + {"A319", {"A320", "A321", "B733", "B734", "B737", "B738", "B739"}}, + {"A320", {"A319", "A321", "B733", "B734", "B737", "B738", "B739"}}, + {"A321", {"A319", "A320", "B733", "B734", "B737", "B738", "B739"}}, + {"A306", {"A332", "A333", "B763"}}, + }; + + if (alternateTypes.contains(aircraftType)) + { + for (const auto& alternate : alternateTypes.value(aircraftType)) { + model = get3DModel(alternate, operatorICAO); + if (!model.isEmpty()) { + break; + } + } + } + } + + if (model.isEmpty()) + { + // Try for aircraft without specific livery + model = get3DModel(aircraftType); + } + } } if (!model.isEmpty()) { @@ -4353,7 +6330,7 @@ void ADSBDemodGUI::update3DModels() m_labelAltitudeOffset.insert("SB20", 6.5f); m_labelAltitudeOffset.insert("SF34", 6.0f); - // Map from database names to 3D model names + // Map from OpenSky database names to 3D model names m_3DModelMatch.append(new ModelMatch("A300.*", "A306")); // A300 B4 is A300-600, but use for others as closest match m_3DModelMatch.append(new ModelMatch("A310.*", "A310")); m_3DModelMatch.append(new ModelMatch("A318.*", "A318")); @@ -4362,7 +6339,9 @@ void ADSBDemodGUI::update3DModels() m_3DModelMatch.append(new ModelMatch("A.?321.*", "A321")); m_3DModelMatch.append(new ModelMatch("A330.2.*", "A332")); m_3DModelMatch.append(new ModelMatch("A330.3.*", "A333")); - m_3DModelMatch.append(new ModelMatch("A330.4.*", "A342")); + m_3DModelMatch.append(new ModelMatch("A330.7.*", "A333")); // BelugaXL + m_3DModelMatch.append(new ModelMatch("A330.8.*", "A332")); // 200 Neo + m_3DModelMatch.append(new ModelMatch("A330.9.*", "A333")); // 300 Neo m_3DModelMatch.append(new ModelMatch("A340.3.*", "A343")); m_3DModelMatch.append(new ModelMatch("A340.5.*", "A345")); m_3DModelMatch.append(new ModelMatch("A340.6.*", "A346")); @@ -4512,6 +6491,16 @@ void ADSBDemodGUI::updateAirspaces() } } } + if (m_settings.m_displayCoverage) + { + m_airspaceModel.addAirspace(&m_coverageAirspace[0]); + m_airspaceModel.addAirspace(&m_coverageAirspace[1]); + } + for (int i = 0; i < ADSB_IC_MAX; i++) { + if (m_interogators[i].m_valid && m_settings.m_displayIC[i]) { + m_airspaceModel.addAirspace(&m_interogators[i].m_airspace); + } + } } void ADSBDemodGUI::updateNavAids() @@ -4669,14 +6658,14 @@ void ADSBDemodGUI::highlightAircraft(Aircraft *aircraft) { // Restore colour m_highlightAircraft->m_isHighlighted = false; - m_aircraftModel.aircraftUpdated(m_highlightAircraft); + m_aircraftModel.highlightChanged(m_highlightAircraft); } // Highlight this aircraft m_highlightAircraft = aircraft; if (aircraft) { aircraft->m_isHighlighted = true; - m_aircraftModel.aircraftUpdated(aircraft); + m_aircraftModel.highlightChanged(aircraft); if (m_settings.m_displayPhotos) { // Download photo @@ -4745,6 +6734,20 @@ void ADSBDemodGUI::on_displaySettings_clicked() } } +void ADSBDemodGUI::setShowContainmentRadius(bool show) +{ + QQuickItem *item = ui->map->rootObject(); + if (!item) + { + qCritical("ADSBDemodGUI::setShowContainmentRadius: Map not found. Are all required Qt plugins installed?"); + return; + } + else + { + QQmlProperty::write(item, "showContainmentRadius", show); + } +} + void ADSBDemodGUI::applyMapSettings() { #ifdef QT_LOCATION_FOUND @@ -4790,6 +6793,7 @@ void ADSBDemodGUI::applyMapSettings() // Create the map using the specified provider QQmlProperty::write(item, "smoothing", MainCore::instance()->getSettings().getMapSmoothing()); QQmlProperty::write(item, "aircraftMinZoomLevel", m_settings.m_aircraftMinZoom); + QQmlProperty::write(item, "showContainmentRadius", m_settings.m_displayRadius); QQmlProperty::write(item, "mapProvider", mapProvider); QVariantMap parameters; QString mapType; @@ -4914,6 +6918,13 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_airspaceModel(this), m_trackAircraft(nullptr), m_highlightAircraft(nullptr), + m_chart(nullptr), + m_adsbFrameRateSeries(nullptr), + m_modesFrameRateSeries(nullptr), + m_aircraftSeries(nullptr), + m_xAxis(nullptr), + m_fpsYAxis(nullptr), + m_aircraftYAxis(nullptr), m_speech(nullptr), m_progressDialog(nullptr), m_loadingData(false) @@ -4964,7 +6975,7 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); - m_adsbDemod = reinterpret_cast(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI); + m_adsbDemod = reinterpret_cast(rxChannel); m_adsbDemod->setMessageQueueToGUI(getInputMessageQueue()); connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); @@ -4972,6 +6983,15 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb CRightClickEnabler *feedRightClickEnabler = new CRightClickEnabler(ui->feed); connect(feedRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(feedSelect(const QPoint &))); + CRightClickEnabler *coverageClickEnabler = new CRightClickEnabler(ui->coverage); + connect(coverageClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(clearCoverage(const QPoint &))); + + CRightClickEnabler *statsClickEnabler = new CRightClickEnabler(ui->stats); + connect(statsClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(clearStats(const QPoint &))); + + CRightClickEnabler *displayChartClickEnabler = new CRightClickEnabler(ui->displayChart); + connect(displayChartClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(clearChart(const QPoint &))); + ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); ui->warning->setVisible(false); @@ -4999,6 +7019,8 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + ui->ic->setText("IC"); + // Set size of airline icons ui->adsbData->setIconSize(QSize(85, 20)); // Resize the table using dummy data @@ -5031,6 +7053,14 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui->flightDetails->setVisible(false); ui->aircraftDetails->setVisible(false); + ui->statsTable->horizontalHeader()->setStretchLastSection(true); + ui->statsTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->statsTable, SIGNAL(customContextMenuRequested(QPoint)), SLOT(statsTable_customContextMenuRequested(QPoint))); + TableTapAndHold *statsTapAndHold = new TableTapAndHold(ui->statsTable); + connect(statsTapAndHold, &TableTapAndHold::tapAndHold, this, &ADSBDemodGUI::statsTable_customContextMenuRequested); + + plotChart(); + // Read aircraft information database, if it has previously been downloaded AircraftInformation::init(); connect(&m_osnDB, &OsnDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL); @@ -5038,6 +7068,7 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(&m_osnDB, &OsnDB::downloadProgress, this, &ADSBDemodGUI::downloadProgress); connect(&m_osnDB, &OsnDB::downloadAircraftInformationFinished, this, &ADSBDemodGUI::downloadAircraftInformationFinished); m_aircraftInfo = OsnDB::getAircraftInformation(); + m_routeInfo = OsnDB::getAircraftRouteInformation(); // Read airport information database, if it has previously been downloaded connect(&m_ourAirportsDB, &OurAirportsDB::downloadingURL, this, &ADSBDemodGUI::downloadingURL); @@ -5074,6 +7105,10 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb // Get airport weather when requested connect(&m_airportModel, &AirportModel::requestMetar, this, &ADSBDemodGUI::requestMetar); + resetStats(); + + initCoverageMap(); + // Add airports within range of My Position updateAirports(); updateAirspaces(); @@ -5162,6 +7197,8 @@ void ADSBDemodGUI::applySettings(const QStringList& settingsKeys, bool force) ADSBDemod::MsgConfigureADSBDemod* message = ADSBDemod::MsgConfigureADSBDemod::create(m_settings, m_settingsKeys, force); m_adsbDemod->getInputMessageQueue()->push(message); + + m_settingsKeys.clear(); } } @@ -5192,12 +7229,14 @@ void ADSBDemodGUI::displaySettings(const QStringList& settingsKeys, bool force) ui->rfBW->setValue((int)m_settings.m_rfBandwidth); ui->spb->setCurrentIndex(m_settings.m_samplesPerBit/2-1); - ui->correlateFullPreamble->setChecked(m_settings.m_correlateFullPreamble); ui->demodModeS->setChecked(m_settings.m_demodModeS); ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold, 0, 'f', 1)); ui->threshold->setValue((int)(m_settings.m_correlationThreshold*10.0f)); + ui->chipsThresholdText->setText(QString("%1").arg(m_settings.m_chipsThreshold)); + ui->chipsThreshold->setValue((int)(m_settings.m_chipsThreshold)); + ui->phaseStepsText->setText(QString("%1").arg(m_settings.m_interpolatorPhaseSteps)); ui->phaseSteps->setValue(m_settings.m_interpolatorPhaseSteps); ui->tapsPerPhaseText->setText(QString("%1").arg(m_settings.m_interpolatorTapsPerPhase, 0, 'f', 1)); @@ -5213,21 +7252,33 @@ void ADSBDemodGUI::displaySettings(const QStringList& settingsKeys, bool force) ui->feed->setChecked(m_settings.m_feedEnabled); ui->flightPaths->setChecked(m_settings.m_flightPaths); - m_aircraftModel.setFlightPaths(m_settings.m_flightPaths); ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths); - m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths); - m_aircraftModel.setSettings(&m_settings); ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename)); ui->logEnable->setChecked(m_settings.m_logEnabled); + ui->manualQNH->setChecked(m_settings.m_manualQNH); + ui->qnh->setEnabled(m_settings.m_manualQNH); + ui->qnh->setValue(m_settings.m_qnh); + + ui->atcLabels->setChecked(m_settings.m_atcLabels); + ui->coverage->setChecked(m_settings.m_displayCoverage); + ui->displayChart->setChecked(m_settings.m_displayChart); + ui->chart->setVisible(m_settings.m_displayChart); + ui->stats->setChecked(m_settings.m_displayDemodStats); + ui->statsTable->setVisible(m_settings.m_displayDemodStats); + ui->displayOrientation->setChecked(m_settings.m_displayOrientation); + ui->splitter->setOrientation(m_settings.m_displayOrientation ? Qt::Horizontal : Qt::Vertical); + ui->displayRadius->setChecked(m_settings.m_displayRadius); + updateIndexLabel(); if (settingsKeys.contains("tableFontName") || settingsKeys.contains("tableFontSize") || force) { QFont font(m_settings.m_tableFontName, m_settings.m_tableFontSize); ui->adsbData->setFont(font); + ui->statsTable->setFont(font); } // Set units in column headers @@ -5341,6 +7392,55 @@ void ADSBDemodGUI::blockApplySettings(bool block) m_doApplySettings = !block; } +static double roundUpNearestHundred(double x) +{ + return 100 * (int) floor(((int) (x + 99.0)) / 100.0); +} + +int ADSBDemodGUI::countActiveAircraft() +{ + int total = 0; + QDateTime now = QDateTime::currentDateTime(); + qint64 nowSecs = now.toSecsSinceEpoch(); + QHash::iterator i = m_aircraft.begin(); + + while (i != m_aircraft.end()) + { + Aircraft *aircraft = i.value(); + qint64 secondsSinceLastFrame = nowSecs - aircraft->m_updateTime.toSecsSinceEpoch(); + + if (secondsSinceLastFrame < 10) { + total++; + } + + ++i; + } + + return total; +} + +void ADSBDemodGUI::removeAircraft(QHash::iterator& i, Aircraft *aircraft) +{ + // Don't try to track it anymore + if (m_trackAircraft == aircraft) + { + m_adsbDemod->clearTarget(); + m_trackAircraft = nullptr; + } + + // Remove map model + m_aircraftModel.removeAircraft(aircraft); + // Remove row from table + ui->adsbData->removeRow(aircraft->m_icaoItem->row()); + // Remove aircraft from hash + i = m_aircraft.erase(i); + // Remove from map feature + clearFromMap(aircraft->m_icaoHex); + + // And finally free its memory + delete aircraft; +} + void ADSBDemodGUI::tick() { double magsqAvg, magsqPeak; @@ -5360,8 +7460,11 @@ void ADSBDemodGUI::tick() m_tickCount++; - // Tick is called 20x a second - lets check this every 10 seconds - if (m_tickCount % (20*10) == 0) + // Tick is called 20x a second + const int ticksPerSecond = 20; + + // Check for old aircraft every 10 seconds + if (m_tickCount % (ticksPerSecond*10) == 0) { // Remove aircraft that haven't been heard of for a user-defined time, as probably out of range QDateTime now = QDateTime::currentDateTime(); @@ -5371,40 +7474,153 @@ void ADSBDemodGUI::tick() while (i != m_aircraft.end()) { Aircraft *aircraft = i.value(); - qint64 secondsSinceLastFrame = nowSecs - aircraft->m_time.toSecsSinceEpoch(); + qint64 secondsSinceLastFrame = nowSecs - aircraft->m_updateTime.toSecsSinceEpoch(); - if (secondsSinceLastFrame >= m_settings.m_removeTimeout) - { - // Don't try to track it anymore - if (m_trackAircraft == aircraft) - { - m_adsbDemod->clearTarget(); - m_trackAircraft = nullptr; - } - - // Remove map model - m_aircraftModel.removeAircraft(aircraft); - // Remove row from table - ui->adsbData->removeRow(aircraft->m_icaoItem->row()); - // Remove aircraft from hash - i = m_aircraft.erase(i); - // Remove from map feature - clearFromMap(QString("%1").arg(aircraft->m_icao, 0, 16)); - - // And finally free its memory - delete aircraft; - } - else - { + if (secondsSinceLastFrame >= m_settings.m_removeTimeout) { + removeAircraft(i, aircraft); + } else { ++i; } } } // Create and send aircraft report every second for WebAPI - if (m_tickCount % (20*1) == 0) { + if (m_tickCount % ticksPerSecond == 0) { sendAircraftReport(); } + + // Calculate RX frame rate every second + if ((m_tickCount % ticksPerSecond) == 0) + { + QDateTime currentDateTime = QDateTime::currentDateTime(); + qint64 ms = m_frameRateTime.msecsTo(currentDateTime); + + if (ms > 0) + { + double s = ms / 1000.0; + double adsbRate = m_adsbFrameRateCount / s; + double modesFrameRate = m_modesFrameRateCount / s; + double totalRate = (m_adsbFrameRateCount + m_modesFrameRateCount) / s; + double dataRate = (m_totalBytes * 8 / 1000) / s; // kpbs + + ui->statsTable->item(ADSB_RATE, 0)->setData(Qt::DisplayRole, adsbRate); + ui->statsTable->item(MODE_S_RATE, 0)->setData(Qt::DisplayRole, modesFrameRate); + ui->statsTable->item(TOTAL_RATE, 0)->setData(Qt::DisplayRole, totalRate); + if (totalRate > m_maxRateState) + { + m_maxRateState = totalRate; + ui->statsTable->item(MAX_RATE, 0)->setData(Qt::DisplayRole, m_maxRateState); + } + ui->statsTable->item(DATA_RATE, 0)->setData(Qt::DisplayRole, dataRate); + + bool xAtMax = true; + if (m_adsbFrameRateSeries) + { + if (m_adsbFrameRateSeries->count() > 0) { + xAtMax = m_adsbFrameRateSeries->at(m_adsbFrameRateSeries->count() - 1).x() == m_xAxis->max().toMSecsSinceEpoch(); + } + m_adsbFrameRateSeries->append(currentDateTime.toMSecsSinceEpoch(), adsbRate); + } + if (m_modesFrameRateSeries) { + m_modesFrameRateSeries->append(currentDateTime.toMSecsSinceEpoch(), modesFrameRate); + } + + if (m_xAxis && xAtMax) { + m_xAxis->setMax(currentDateTime); + } + if (m_fpsYAxis) + { + if (m_fpsYAxis->max() < adsbRate) { + m_fpsYAxis->setMax(roundUpNearestHundred(adsbRate)); + } + if (m_fpsYAxis->max() < modesFrameRate) { + m_fpsYAxis->setMax(roundUpNearestHundred(modesFrameRate)); + } + } + + m_frameRateTime = currentDateTime; + m_adsbFrameRateCount = 0; + m_modesFrameRateCount = 0; + m_totalBytes = 0; + } + + if (m_aircraftSeries) + { + int active = countActiveAircraft(); + + m_aircraftSeries->append(currentDateTime.toMSecsSinceEpoch(), active); + + if (m_aircraftYAxis->max() < active + 1) { + m_aircraftYAxis->setMax(active + 1); + } + } + + // Average data 10 minutes old over 1 minute, so we don't have too many points + const int ageMins = 10; + const int averagePeriodMins = 1; + if ((m_tickCount % (ticksPerSecond*60*averagePeriodMins)) == 0) + { + QDateTime endTime, startTime; + + if (m_averageTime.isValid()) + { + startTime = m_averageTime; + endTime = startTime.addSecs(averagePeriodMins*60); + } + else + { + endTime = QDateTime::currentDateTime().addSecs(-ageMins*60); + startTime = endTime.addSecs(-averagePeriodMins*60); + } + + if (m_aircraftSeries) { + averageSeries(m_aircraftSeries, startTime, endTime); + } + if (m_modesFrameRateSeries) { + averageSeries(m_modesFrameRateSeries, startTime, endTime); + } + if (m_adsbFrameRateSeries) { + averageSeries(m_adsbFrameRateSeries, startTime, endTime); + } + + m_averageTime = endTime; + } + } +} + +// Replace data in series between specified times with its average +void ADSBDemodGUI::averageSeries(QLineSeries *series, const QDateTime& startTime, const QDateTime& endTime) +{ + int startIdx = 0; + int endIdx = -1; + + for (int i = series->count() - 1; i >= 0; i--) + { + QDateTime dt = QDateTime::fromMSecsSinceEpoch(series->at(i).x()); + + if ((endIdx == -1) && (dt <= endTime)) + { + endIdx = i; + } + else if (dt < startTime) + { + startIdx = i + 1; + break; + } + } + int count = (endIdx - startIdx) + 1; + + if ((endIdx != -1) && (count > 1)) + { + double sum = 0.0; + for (int i = startIdx; i <= endIdx; i++) { + sum += series->at(i).y(); + } + double avg = sum / count; + series->removePoints(startIdx, count); + qint64 midPoint = startTime.toMSecsSinceEpoch() + (endTime.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch()) / 2; + series->insert(startIdx, QPointF(midPoint, avg)); + } } void ADSBDemodGUI::sendAircraftReport() @@ -5442,48 +7658,47 @@ void ADSBDemodGUI::resizeTable() int row = ui->adsbData->rowCount(); ui->adsbData->setRowCount(row + 1); ui->adsbData->setItem(row, ADSB_COL_ICAO, new QTableWidgetItem("ICAO ID")); - ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, new QTableWidgetItem("Callsign--")); + ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, new QTableWidgetItem("WWW88WW")); ui->adsbData->setItem(row, ADSB_COL_ATC_CALLSIGN, new QTableWidgetItem("ATC Callsign-")); ui->adsbData->setItem(row, ADSB_COL_MODEL, new QTableWidgetItem("Aircraft12345")); + ui->adsbData->setItem(row, ADSB_COL_TYPE, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_SIDEVIEW, new QTableWidgetItem("sideview")); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, new QTableWidgetItem("airbrigdecargo1")); - ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, new QTableWidgetItem("Alt (ft)")); - ui->adsbData->setItem(row, ADSB_COL_HEADING, new QTableWidgetItem("Hd (o)")); - ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, new QTableWidgetItem("VR (ft/m)")); - ui->adsbData->setItem(row, ADSB_COL_RANGE, new QTableWidgetItem("D (km)")); - ui->adsbData->setItem(row, ADSB_COL_AZEL, new QTableWidgetItem("Az/El (o)")); - ui->adsbData->setItem(row, ADSB_COL_LATITUDE, new QTableWidgetItem("-90.00000")); - ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, new QTableWidgetItem("-180.000000")); - ui->adsbData->setItem(row, ADSB_COL_CATEGORY, new QTableWidgetItem("Heavy")); - ui->adsbData->setItem(row, ADSB_COL_STATUS, new QTableWidgetItem("No emergency")); - ui->adsbData->setItem(row, ADSB_COL_SQUAWK, new QTableWidgetItem("Squawk")); - ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, new QTableWidgetItem("G-12345")); ui->adsbData->setItem(row, ADSB_COL_COUNTRY, new QTableWidgetItem("Country")); - ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("Registered")); - ui->adsbData->setItem(row, ADSB_COL_MANUFACTURER, new QTableWidgetItem("The Boeing Company")); - ui->adsbData->setItem(row, ADSB_COL_OWNER, new QTableWidgetItem("British Airways")); - ui->adsbData->setItem(row, ADSB_COL_OPERATOR_ICAO, new QTableWidgetItem("Operator")); - ui->adsbData->setItem(row, ADSB_COL_TIME, new QTableWidgetItem("99:99:99")); - ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames")); - ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001")); - ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0")); - ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, new QTableWidgetItem("scheduled")); - ui->adsbData->setItem(row, ADSB_COL_DEP, new QTableWidgetItem("WWWW")); - ui->adsbData->setItem(row, ADSB_COL_ARR, new QTableWidgetItem("WWWW")); - ui->adsbData->setItem(row, ADSB_COL_STD, new QTableWidgetItem("12:00 -1")); - ui->adsbData->setItem(row, ADSB_COL_ETD, new QTableWidgetItem("12:00 -1")); - ui->adsbData->setItem(row, ADSB_COL_ATD, new QTableWidgetItem("12:00 -1")); - ui->adsbData->setItem(row, ADSB_COL_STA, new QTableWidgetItem("12:00 +1")); - ui->adsbData->setItem(row, ADSB_COL_ETA, new QTableWidgetItem("12:00 +1")); - ui->adsbData->setItem(row, ADSB_COL_ATA, new QTableWidgetItem("12:00 +1")); - ui->adsbData->setItem(row, ADSB_COL_SEL_ALTITUDE, new QTableWidgetItem("Sel Alt (ft)")); - ui->adsbData->setItem(row, ADSB_COL_SEL_HEADING, new QTableWidgetItem("Sel Hd (o)")); - ui->adsbData->setItem(row, ADSB_COL_BARO, new QTableWidgetItem("Baro (mb)")); - ui->adsbData->setItem(row, ADSB_COL_AP, new QTableWidgetItem("AP")); - ui->adsbData->setItem(row, ADSB_COL_V_MODE, new QTableWidgetItem("V Mode")); - ui->adsbData->setItem(row, ADSB_COL_L_MODE, new QTableWidgetItem("L Mode")); + ui->adsbData->setItem(row, ADSB_COL_GROUND_SPEED, new QTableWidgetItem("GS (kn)")); ui->adsbData->setItem(row, ADSB_COL_TRUE_AIRSPEED, new QTableWidgetItem("TAS (kn)")); ui->adsbData->setItem(row, ADSB_COL_INDICATED_AIRSPEED, new QTableWidgetItem("IAS (kn)")); ui->adsbData->setItem(row, ADSB_COL_MACH, new QTableWidgetItem("0.999")); + ui->adsbData->setItem(row, ADSB_COL_SEL_ALTITUDE, new QTableWidgetItem("Sel Alt (ft)")); + ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, new QTableWidgetItem("Surface")); + ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, new QTableWidgetItem("VR (ft/m)")); + ui->adsbData->setItem(row, ADSB_COL_SEL_HEADING, new QTableWidgetItem("Sel Hd (o)")); + ui->adsbData->setItem(row, ADSB_COL_HEADING, new QTableWidgetItem("Hd (o)")); + ui->adsbData->setItem(row, ADSB_COL_TRACK, new QTableWidgetItem("Trk (o)")); + ui->adsbData->setItem(row, ADSB_COL_TURNRATE, new QTableWidgetItem("TR (o/s)")); + ui->adsbData->setItem(row, ADSB_COL_ROLL, new QTableWidgetItem("Roll (o)")); + ui->adsbData->setItem(row, ADSB_COL_RANGE, new QTableWidgetItem("D (km)")); + ui->adsbData->setItem(row, ADSB_COL_AZEL, new QTableWidgetItem("Az/El (o)")); + ui->adsbData->setItem(row, ADSB_COL_CATEGORY, new QTableWidgetItem("Rotorcraft")); + ui->adsbData->setItem(row, ADSB_COL_STATUS, new QTableWidgetItem("No emergency")); + ui->adsbData->setItem(row, ADSB_COL_SQUAWK, new QTableWidgetItem("Squawk")); + ui->adsbData->setItem(row, ADSB_COL_IDENT, new QTableWidgetItem("Ident")); + ui->adsbData->setItem(row, ADSB_COL_REGISTRATION, new QTableWidgetItem("G-888888")); + ui->adsbData->setItem(row, ADSB_COL_REGISTERED, new QTableWidgetItem("8888-88-88")); + ui->adsbData->setItem(row, ADSB_COL_MANUFACTURER, new QTableWidgetItem("The Boeing Company")); + ui->adsbData->setItem(row, ADSB_COL_OWNER, new QTableWidgetItem("British Airways")); + ui->adsbData->setItem(row, ADSB_COL_OPERATOR_ICAO, new QTableWidgetItem("Operator")); + ui->adsbData->setItem(row, ADSB_COL_AP, new QTableWidgetItem("AP")); + ui->adsbData->setItem(row, ADSB_COL_V_MODE, new QTableWidgetItem("V Mode")); + ui->adsbData->setItem(row, ADSB_COL_L_MODE, new QTableWidgetItem("L Mode")); + ui->adsbData->setItem(row, ADSB_COL_TCAS, new QTableWidgetItem("TCAS")); + ui->adsbData->setItem(row, ADSB_COL_ACAS, new QTableWidgetItem("No ACAS")); + ui->adsbData->setItem(row, ADSB_COL_RA, new QTableWidgetItem("RA")); + ui->adsbData->setItem(row, ADSB_COL_MAX_SPEED, new QTableWidgetItem("600-1200")); + ui->adsbData->setItem(row, ADSB_COL_VERSION, new QTableWidgetItem("Version")); + ui->adsbData->setItem(row, ADSB_COL_LENGTH, new QTableWidgetItem("85")); + ui->adsbData->setItem(row, ADSB_COL_WIDTH, new QTableWidgetItem("72.5")); + ui->adsbData->setItem(row, ADSB_COL_BARO, new QTableWidgetItem("Baro (mb)")); ui->adsbData->setItem(row, ADSB_COL_HEADWIND, new QTableWidgetItem("H Wnd (kn)")); ui->adsbData->setItem(row, ADSB_COL_EST_AIR_TEMP, new QTableWidgetItem("OAT (C)")); ui->adsbData->setItem(row, ADSB_COL_WIND_SPEED, new QTableWidgetItem("Wnd (kn)")); @@ -5491,7 +7706,35 @@ void ADSBDemodGUI::resizeTable() ui->adsbData->setItem(row, ADSB_COL_STATIC_PRESSURE, new QTableWidgetItem("P (hPa)")); ui->adsbData->setItem(row, ADSB_COL_STATIC_AIR_TEMP, new QTableWidgetItem("T (C)")); ui->adsbData->setItem(row, ADSB_COL_HUMIDITY, new QTableWidgetItem("U (%)")); - ui->adsbData->setItem(row, ADSB_COL_TIS_B, new QTableWidgetItem("TIS-B")); + ui->adsbData->setItem(row, ADSB_COL_LATITUDE, new QTableWidgetItem("-90.00000")); + ui->adsbData->setItem(row, ADSB_COL_LONGITUDE, new QTableWidgetItem("-180.000000")); + ui->adsbData->setItem(row, ADSB_COL_IC, new QTableWidgetItem("63")); + ui->adsbData->setItem(row, ADSB_COL_TIME, new QTableWidgetItem("99:99:99")); + ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames")); + ui->adsbData->setItem(row, ADSB_COL_ADSB_FRAMECOUNT, new QTableWidgetItem("ADS-B FC")); + ui->adsbData->setItem(row, ADSB_COL_MODES_FRAMECOUNT, new QTableWidgetItem("Mode S FC")); + ui->adsbData->setItem(row, ADSB_COL_NON_TRANSPONDER, new QTableWidgetItem("Non-transponder")); + ui->adsbData->setItem(row, ADSB_COL_TIS_B_FRAMECOUNT, new QTableWidgetItem("TIS-B FC")); + ui->adsbData->setItem(row, ADSB_COL_ADSR_FRAMECOUNT, new QTableWidgetItem("ADS-R FC")); + ui->adsbData->setItem(row, ADSB_COL_RADIUS, new QTableWidgetItem("< 0.1 NM")); + ui->adsbData->setItem(row, ADSB_COL_NACP, new QTableWidgetItem("< 0.05 NM")); + ui->adsbData->setItem(row, ADSB_COL_NACV, new QTableWidgetItem("< 0.3 m/s")); + ui->adsbData->setItem(row, ADSB_COL_GVA, new QTableWidgetItem(">= 150 m")); + ui->adsbData->setItem(row, ADSB_COL_NIC, new QTableWidgetItem("< 0.05 NM")); + ui->adsbData->setItem(row, ADSB_COL_NIC_BARO, new QTableWidgetItem("")); + ui->adsbData->setItem(row, ADSB_COL_SIL, new QTableWidgetItem("<= 1e-7 ph")); + ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001")); + ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0")); + ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, new QTableWidgetItem("scheduled")); + ui->adsbData->setItem(row, ADSB_COL_DEP, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_ARR, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_STOPS, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_STD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_ETD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_ATD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_STA, new QTableWidgetItem("12:00 +1")); + ui->adsbData->setItem(row, ADSB_COL_ETA, new QTableWidgetItem("12:00 +1")); + ui->adsbData->setItem(row, ADSB_COL_ATA, new QTableWidgetItem("12:00 +1")); ui->adsbData->resizeColumnsToContents(); ui->adsbData->setRowCount(row); } @@ -5561,6 +7804,7 @@ void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& fli aircraft->m_flightStatusItem->setText(flight.m_flightStatus); aircraft->m_depItem->setText(flight.m_departureICAO); aircraft->m_arrItem->setText(flight.m_arrivalICAO); + aircraft->m_stopsItem->setText(""); aircraft->m_stdItem->setText(dataTimeToShortString(flight.m_departureScheduled)); aircraft->m_etdItem->setText(dataTimeToShortString(flight.m_departureEstimated)); aircraft->m_atdItem->setText(dataTimeToShortString(flight.m_departureActual)); @@ -5655,6 +7899,8 @@ void ADSBDemodGUI::on_logOpen_clicked() QHash colIndexes = CSV::readHeader(in, {"Data", "Correlation"}, error); if (error.isEmpty()) { + int dateCol = colIndexes.value("Date"); + int timeCol = colIndexes.value("Time"); int dataCol = colIndexes.value("Data"); int correlationCol = colIndexes.value("Correlation"); int maxCol = std::max(dataCol, correlationCol); @@ -5673,20 +7919,20 @@ void ADSBDemodGUI::on_logOpen_clicked() { if (cols.size() > maxCol) { - QDateTime dateTime = QDateTime::currentDateTime(); // So they aren't removed immediately as too old + QDateTime dateTime = QDateTime(QDate::fromString(cols[dateCol]), QTime::fromString(cols[timeCol])); QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1()); float correlation = cols[correlationCol].toFloat(); int df = (bytes[0] >> 3) & ADS_B_DF_MASK; // Downlink format - if ((df == 4) || (df == 5) || (df == 17) || (df == 18) || (df == 20) || (df == 21)) + if ( (m_settings.m_demodModeS && ((df == 0) || (df == 4) || (df == 5) || (df == 11) || (df == 16) || (df == 19) || (df == 20) || (df == 21) || (df == 22) || (df == 24))) + || (df == 17) || (df == 18)) { int crcCalc = 0; - if ((df == 4) || (df == 5) || (df == 20) || (df == 21)) // handleADSB requires calculated CRC for Mode-S frames + if ((df == 0) || (df == 4) || (df == 5) || (df == 16) || (df == 20) || (df == 21)) // handleADSB requires calculated CRC for Mode-S frames { crc.init(); crc.calculate((const uint8_t *)bytes.data(), bytes.size()-3); crcCalc = crc.get(); } - //qDebug() << "bytes.size " << bytes.size() << " crc " << Qt::hex << crcCalc; handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false); if ((count > 0) && (count % 100000 == 0)) { @@ -5794,6 +8040,7 @@ void ADSBDemodGUI::downloadAircraftInformationFinished() QApplication::processEvents(); } m_aircraftInfo = OsnDB::getAircraftInformation(); + m_routeInfo = OsnDB::getAircraftRouteInformation(); m_aircraftModel.updateAircraftInformation(m_aircraftInfo); if (m_progressDialog) { @@ -5902,6 +8149,28 @@ void ADSBDemodGUI::showEvent(QShowEvent *event) ChannelGUI::showEvent(event); } +static void scale(qint64& start, qint64& end, qint64 min, int delta, qreal centre) +{ + qint64 diff = end - start; + double scale = pow(0.50, abs(delta) / 120.0); + qint64 newRange; + + if (delta < 0) { + newRange = diff / scale; + } else { + newRange = diff * scale; + } + + diff = std::max(min/2, diff); + newRange = std::max(min, newRange); + if (delta < 0) { + start = start - centre * diff; + } else { + start = start + centre * newRange; + } + end = start + newRange; +} + bool ADSBDemodGUI::eventFilter(QObject *obj, QEvent *event) { if (obj == ui->map) @@ -5918,6 +8187,83 @@ bool ADSBDemodGUI::eventFilter(QObject *obj, QEvent *event) } } } + else if (obj == ui->chart) + { + if (event->type() == QEvent::Wheel) + { + // Use wheel to zoom in / out of X axis or Y axis if shift held + QWheelEvent *wheelEvent = static_cast(event); + + int delta = wheelEvent->angleDelta().y(); // delta is typically 120 for one click of wheel + + if (m_adsbFrameRateSeries) + { + QPointF point = wheelEvent->position(); + QRectF plotArea = m_chart->plotArea(); + + if (wheelEvent->modifiers() & Qt::ShiftModifier) + { + // Center scaling on cursor location + qreal y = (point.y() - plotArea.y()) / plotArea.height(); + y = 1.0 - std::min(1.0, std::max(0.0, y)); + + qint64 min, max; + + if (m_adsbFrameRateSeries->isVisible() || m_modesFrameRateSeries->isVisible()) + { + min = (qint64) m_fpsYAxis->min(); + max = (qint64) m_fpsYAxis->max(); + + scale(min, max, 2LL, delta / 2, y); + + min = std::max(0LL, min); + max = std::min(5000LL, max); + + m_fpsYAxis->setMin((qreal) min); + m_fpsYAxis->setMax((qreal) max); + } + + if (m_aircraftSeries->isVisible()) + { + min = (qint64) m_aircraftYAxis->min(); + max = (qint64) m_aircraftYAxis->max(); + + scale(min, max, 2LL, delta / 2, y); + + min = std::max(0LL, min); + max = std::min(5000LL, max); + + m_aircraftYAxis->setMin((qreal) min); + m_aircraftYAxis->setMax((qreal) max); + } + } + else + { + if (m_adsbFrameRateSeries->count() > 1) + { + // Center scaling on cursor location + qreal x = (point.x() - plotArea.x()) / plotArea.width(); + x = std::min(1.0, std::max(0.0, x)); + + qint64 startMS = m_xAxis->min().toMSecsSinceEpoch(); + qint64 endMS = m_xAxis->max().toMSecsSinceEpoch(); + + scale(startMS, endMS, 10000LL, delta, x); + + // Don't let range exceed available data + startMS = std::max((qint64) m_adsbFrameRateSeries->at(0).x(), startMS); + endMS = std::min((qint64) m_adsbFrameRateSeries->at(m_adsbFrameRateSeries->count() - 1).x(), endMS); + QDateTime start = QDateTime::fromMSecsSinceEpoch(startMS); + QDateTime end = QDateTime::fromMSecsSinceEpoch(endMS); + m_xAxis->setMin(start); + m_xAxis->setMax(end); + } + } + } + wheelEvent->accept(); + return true; + } + } return ChannelGUI::eventFilter(obj, event); } @@ -5996,11 +8342,8 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) Aircraft *aircraft = getAircraft(icao, newAircraft); QString callsign = state[1].toString().trimmed(); - if (!callsign.isEmpty()) - { - aircraft->m_callsign = callsign; - aircraft->m_callsignItem->setText(aircraft->m_callsign); - atcCallsign(aircraft); + if (!callsign.isEmpty()) { + setCallsign(aircraft, callsign); } QDateTime timePosition = dateTime; @@ -6009,8 +8352,9 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) } else { timePosition = QDateTime::fromSecsSinceEpoch(state[3].toInt()); } - aircraft->m_time = QDateTime::fromSecsSinceEpoch(state[4].toInt()); - QTime time = aircraft->m_time.time(); + aircraft->m_rxTime = QDateTime::fromSecsSinceEpoch(state[4].toInt()); + aircraft->m_updateTime = QDateTime::currentDateTime(); + QTime time = aircraft->m_rxTime.time(); aircraft->m_timeItem->setText(QString("%1:%2:%3").arg(time.hour(), 2, 10, QLatin1Char('0')).arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0'))); aircraft->m_adsbFrameCount++; aircraft->m_adsbFrameCountItem->setData(Qt::DisplayRole, aircraft->m_adsbFrameCount); @@ -6019,48 +8363,34 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) { if (!state[5].isNull() && !state[6].isNull()) { - aircraft->m_longitude = state[5].toDouble(); - aircraft->m_latitude = state[6].toDouble(); - aircraft->m_longitudeItem->setData(Qt::DisplayRole, aircraft->m_longitude); - aircraft->m_latitudeItem->setData(Qt::DisplayRole, aircraft->m_latitude); - updatePosition(aircraft); + updateAircraftPosition(aircraft, state[6].toDouble(), state[5].toDouble(), timePosition); aircraft->m_cprValid[0] = false; aircraft->m_cprValid[1] = false; } if (!state[7].isNull()) { - aircraft->m_altitude = (int)Units::metresToFeet(state[7].toDouble()); - aircraft->m_altitudeValid = true; - aircraft->m_altitudeGNSS = false; - aircraft->m_altitudeItem->setData(Qt::DisplayRole, aircraft->m_altitude); + aircraft->setAltitude((int)Units::metresToFeet(state[7].toDouble()), false, dateTime, m_settings); + aircraft->m_altitudeDateTime = timePosition; } if (!state[5].isNull() && !state[6].isNull() && !state[7].isNull()) { - QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); + /*QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); aircraft->m_coordinateDateTimes.push_back(dateTime); + */ + aircraft->addCoordinate(dateTime, &m_aircraftModel); } - aircraft->m_positionDateTime = timePosition; } aircraft->m_onSurface = state[8].toBool(false); - if (!state[9].isNull()) - { - aircraft->m_groundspeed = (int)state[9].toDouble(); - aircraft->m_groundspeedItem->setData(Qt::DisplayRole, aircraft->m_groundspeed); - aircraft->m_groundspeedValid = true; + aircraft->m_onSurfaceValid = true; + if (!state[9].isNull()) { + aircraft->setGroundspeed((int)state[9].toDouble(), m_settings); } - if (!state[10].isNull()) - { - aircraft->m_heading = (float)state[10].toDouble(); - aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); - aircraft->m_headingValid = true; - aircraft->m_headingDateTime = aircraft->m_time; + if (!state[10].isNull()) { + aircraft->setTrack((float)state[10].toDouble(), aircraft->m_rxTime); } - if (!state[11].isNull()) - { - aircraft->m_verticalRate = (int)state[10].toDouble(); - aircraft->m_verticalRateItem->setData(Qt::DisplayRole, aircraft->m_verticalRate); - aircraft->m_verticalRateValid = true; + if (!state[11].isNull()) { + aircraft->setVerticalRate((int)state[10].toDouble(), m_settings); } if (!state[14].isNull()) { @@ -6221,33 +8551,73 @@ void ADSBDemodGUI::weatherUpdated(const AviationWeather::METAR &metar) m_airportModel.updateWeather(metar.m_icao, metar.m_text, metar.decoded()); } +void ADSBDemodGUI::on_manualQNH_clicked(bool checked) +{ + m_settings.m_manualQNH = checked; + applySetting("manualQNH"); + ui->qnh->setEnabled(m_settings.m_manualQNH); +} + +void ADSBDemodGUI::on_qnh_valueChanged(int value) +{ + m_settings.m_qnh = value; + applySetting("qnh"); +} + +void ADSBDemodGUI::updateQNH(const Aircraft *aircraft, float qnh) +{ + if (!m_settings.m_manualQNH) + { + // Ignore aircraft that have QNH set to STD. + if ( ((qnh < 1012) || (std::floor(qnh) > 1013)) + || ( (aircraft->m_altitudeValid && (aircraft->m_altitude < (m_settings.m_transitionAlt - 1000))) + && (aircraft->m_selAltitudeValid && (aircraft->m_selAltitude < m_settings.m_transitionAlt)) // If we have qnh, we'll have selAltitude as well + ) + ) { + // Use moving average, otherwise it can jump around if we can receive aircraft from different airports with different QNH + m_qnhAvg(qnh); + ui->qnh->setValue((int)round(m_qnhAvg.instantAverage())); + } + } +} + void ADSBDemodGUI::makeUIConnections() { QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ADSBDemodGUI::on_deltaFrequency_changed); QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &ADSBDemodGUI::on_rfBW_valueChanged); QObject::connect(ui->threshold, &QDial::valueChanged, this, &ADSBDemodGUI::on_threshold_valueChanged); + QObject::connect(ui->chipsThreshold, &QDial::valueChanged, this, &ADSBDemodGUI::on_chipsThreshold_valueChanged); QObject::connect(ui->phaseSteps, &QDial::valueChanged, this, &ADSBDemodGUI::on_phaseSteps_valueChanged); QObject::connect(ui->tapsPerPhase, &QDial::valueChanged, this, &ADSBDemodGUI::on_tapsPerPhase_valueChanged); QObject::connect(ui->adsbData, &QTableWidget::cellClicked, this, &ADSBDemodGUI::on_adsbData_cellClicked); QObject::connect(ui->adsbData, &QTableWidget::cellDoubleClicked, this, &ADSBDemodGUI::on_adsbData_cellDoubleClicked); QObject::connect(ui->spb, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_spb_currentIndexChanged); - QObject::connect(ui->correlateFullPreamble, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_correlateFullPreamble_clicked); QObject::connect(ui->demodModeS, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_demodModeS_clicked); QObject::connect(ui->feed, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_feed_clicked); QObject::connect(ui->notifications, &QToolButton::clicked, this, &ADSBDemodGUI::on_notifications_clicked); QObject::connect(ui->flightInfo, &QToolButton::clicked, this, &ADSBDemodGUI::on_flightInfo_clicked); QObject::connect(ui->findOnMapFeature, &QToolButton::clicked, this, &ADSBDemodGUI::on_findOnMapFeature_clicked); - QObject::connect(ui->getOSNDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getOSNDB_clicked); + QObject::connect(ui->deleteAircraft, &QToolButton::clicked, this, &ADSBDemodGUI::on_deleteAircraft_clicked); + QObject::connect(ui->getAircraftDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAircraftDB_clicked); QObject::connect(ui->getAirportDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirportDB_clicked); QObject::connect(ui->getAirspacesDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirspacesDB_clicked); QObject::connect(ui->flightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_flightPaths_clicked); QObject::connect(ui->allFlightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_allFlightPaths_clicked); QObject::connect(ui->atcLabels, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_atcLabels_clicked); + QObject::connect(ui->coverage, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_coverage_clicked); + QObject::connect(ui->displayChart, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_displayChart_clicked); + QObject::connect(ui->displayOrientation, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_displayOrientation_clicked); + QObject::connect(ui->displayRadius, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_displayRadius_clicked); + QObject::connect(ui->stats, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_stats_clicked); + QObject::connect(ui->ic, &CheckList::globalCheckStateChanged, this, &ADSBDemodGUI::on_ic_globalCheckStateChanged); QObject::connect(ui->amDemod, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_amDemod_currentIndexChanged); QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &ADSBDemodGUI::on_displaySettings_clicked); QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_logEnable_clicked); QObject::connect(ui->logFilename, &QToolButton::clicked, this, &ADSBDemodGUI::on_logFilename_clicked); QObject::connect(ui->logOpen, &QToolButton::clicked, this, &ADSBDemodGUI::on_logOpen_clicked); + QObject::connect(ui->manualQNH, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_manualQNH_clicked); + QObject::connect(ui->qnh, QOverload::of(&QSpinBox::valueChanged), this, &ADSBDemodGUI::on_qnh_valueChanged); + } void ADSBDemodGUI::updateAbsoluteCenterFrequency() @@ -6255,3 +8625,223 @@ void ADSBDemodGUI::updateAbsoluteCenterFrequency() setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset); } +void ADSBDemodGUI::initCoverageMap() +{ + float lat = m_azEl.getLocationSpherical().m_latitude; + float lon = m_azEl.getLocationSpherical().m_longitude; + for (int i = 0; i < 2; i++) + { + m_maxRange[i].resize(360/ADSBDemodGUI::m_maxRangeDeg, 0.0f); + m_coverageAirspace[i].m_polygon.resize(2 * 360/ADSBDemodGUI::m_maxRangeDeg); + m_coverageAirspace[i].m_center.setX(lon); + m_coverageAirspace[i].m_center.setY(lat); + if (i == 0) + { + m_coverageAirspace[i].m_position.setX(lon); + m_coverageAirspace[i].m_position.setY(lat); + m_coverageAirspace[i].m_bottom.m_alt = 0; + m_coverageAirspace[i].m_top.m_alt = 10000; + m_coverageAirspace[i].m_name = "Coverage Map Low"; + } + else + { + m_coverageAirspace[i].m_position.setX(lon); + m_coverageAirspace[i].m_position.setY(lat + 0.01); + m_coverageAirspace[i].m_bottom.m_alt = 10000; + m_coverageAirspace[i].m_top.m_alt = 66000; + m_coverageAirspace[i].m_name = "Coverage Map High"; + } + } + clearCoverageMap(); +} + +void ADSBDemodGUI::clearCoverageMap() +{ + for (int j = 0; j < 2; j++) + { + for (int i = 0; i < m_maxRange[j].size(); i++) { + m_maxRange[j][i] = 0.0f; + } + const float d = 0.01f; + for (int i = 0; i < m_coverageAirspace[j].m_polygon.size(); i+=2) + { + float f = i / (float) m_coverageAirspace[j].m_polygon.size(); + m_coverageAirspace[j].m_polygon[i].setX(m_coverageAirspace[j].m_center.x() + d * sin(f * 2*M_PI)); + m_coverageAirspace[j].m_polygon[i].setY(m_coverageAirspace[j].m_center.y() + d * cos(f * 2*M_PI)); + m_coverageAirspace[j].m_polygon[i+1].setX(m_coverageAirspace[j].m_center.x() + d * sin(f * 2*M_PI)); + m_coverageAirspace[j].m_polygon[i+1].setY(m_coverageAirspace[j].m_center.y() + d * cos(f * 2*M_PI)); + } + } +} + +// 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) +{ + double startLatRad = startLatitude*M_PI/180.0; + double startLongRad = startLongitude*M_PI/180.0; + double theta = bearing*M_PI/180.0; + double earthRadius = 6378137.0; // At equator + double delta = distance/earthRadius; + double endLatRad = std::asin(sin(startLatRad)*cos(delta) + cos(startLatRad)*sin(delta)*cos(theta)); + double endLongRad = startLongRad + std::atan2(sin(theta)*sin(delta)*cos(startLatRad), cos(delta) - sin(startLatRad)*sin(endLatRad)); + endLatitude = endLatRad*180.0/M_PI; + endLongitude = endLongRad*180.0/M_PI; +} + +void ADSBDemodGUI::updateCoverageMap(float azimuth, float elevation, float distance, float altitude) +{ + int i = (int) (azimuth / ADSBDemodGUI::m_maxRangeDeg); + int k = altitude >= 10000 ? 1 : 0; + if (distance > m_maxRange[k][i]) + { + m_maxRange[k][i] = distance; + + float lat; + float lon; + float b1 = i * ADSBDemodGUI::m_maxRangeDeg; + calcRadialEndPoint(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, distance, b1, lat, lon); + m_coverageAirspace[k].m_polygon[i*2].setX(lon); + m_coverageAirspace[k].m_polygon[i*2].setY(lat); + + float b2 = (i + 1) * ADSBDemodGUI::m_maxRangeDeg; + calcRadialEndPoint(m_azEl.getLocationSpherical().m_latitude, m_azEl.getLocationSpherical().m_longitude, distance, b2, lat, lon); + m_coverageAirspace[k].m_polygon[i*2+1].setX(lon); + m_coverageAirspace[k].m_polygon[i*2+1].setY(lat); + + m_airspaceModel.airspaceUpdated(&m_coverageAirspace[k]); + } +} + +void ADSBDemodGUI::clearCoverage(const QPoint& p) +{ + (void) p; + + clearCoverageMap(); + for (int i = 0; i < 2; i++) { + m_airspaceModel.airspaceUpdated(&m_coverageAirspace[i]); + } +} + +void ADSBDemodGUI::clearStats(const QPoint& p) +{ + (void) p; + + resetStats(); +} + +void ADSBDemodGUI::clearChart(const QPoint& p) +{ + (void) p; + + if (m_adsbFrameRateSeries) { + m_adsbFrameRateSeries->clear(); + } + if (m_modesFrameRateSeries) { + m_modesFrameRateSeries->clear(); + } + if (m_aircraftSeries) { + m_aircraftSeries->clear(); + } + resetChartAxes(); + m_averageTime = QDateTime(); +} + +void ADSBDemodGUI::resetChartAxes() +{ + m_xAxis->setMin(QDateTime::currentDateTime()); + m_xAxis->setMax(QDateTime::currentDateTime().addSecs(60*60)); + m_fpsYAxis->setMin(0); + m_fpsYAxis->setMax(100); + m_aircraftYAxis->setMin(0); + m_aircraftYAxis->setMax(10); +} + +void ADSBDemodGUI::plotChart() +{ + QChart *oldChart = m_chart; + + m_chart = new QChart(); + + m_chart->layout()->setContentsMargins(0, 0, 0, 0); + m_chart->setMargins(QMargins(1, 1, 1, 1)); + m_chart->setTheme(QChart::ChartThemeDark); + m_chart->legend()->setAlignment(Qt::AlignRight); + + m_adsbFrameRateSeries = new QLineSeries(); + m_adsbFrameRateSeries->setName("ADS-B"); + + m_modesFrameRateSeries = new QLineSeries(); + m_modesFrameRateSeries->setName("Mode S"); + + m_aircraftSeries = new QLineSeries(); + m_aircraftSeries->setName("Aircraft"); + + m_xAxis = new QDateTimeAxis(); + m_fpsYAxis = new QValueAxis(); + m_aircraftYAxis = new QValueAxis(); + resetChartAxes(); + + m_chart->addAxis(m_xAxis, Qt::AlignBottom); + m_chart->addAxis(m_fpsYAxis, Qt::AlignLeft); + m_chart->addAxis(m_aircraftYAxis, Qt::AlignRight); + + m_fpsYAxis->setTitleText("FPS"); + m_aircraftYAxis->setTitleText("Total"); + + m_chart->addSeries(m_adsbFrameRateSeries); + m_chart->addSeries(m_modesFrameRateSeries); + m_chart->addSeries(m_aircraftSeries); + + m_adsbFrameRateSeries->attachAxis(m_xAxis); + m_adsbFrameRateSeries->attachAxis(m_fpsYAxis); + + m_modesFrameRateSeries->attachAxis(m_xAxis); + m_modesFrameRateSeries->attachAxis(m_fpsYAxis); + + m_aircraftSeries->attachAxis(m_xAxis); + m_aircraftSeries->attachAxis(m_aircraftYAxis); + + ui->chart->setChart(m_chart); + ui->chart->installEventFilter(this); + + const auto markers = m_chart->legend()->markers(); + for (QLegendMarker *marker : markers) { + connect(marker, &QLegendMarker::clicked, this, &ADSBDemodGUI::legendMarkerClicked); + } + + delete oldChart; +} + +void ADSBDemodGUI::legendMarkerClicked() +{ + QLegendMarker* marker = qobject_cast(sender()); + marker->series()->setVisible(!marker->series()->isVisible()); + marker->setVisible(true); + + // Dim the marker, if series is not visible + qreal alpha = 1.0; + + if (!marker->series()->isVisible()) { + alpha = 0.5; + } + + QColor color; + QBrush brush = marker->labelBrush(); + color = brush.color(); + color.setAlphaF(alpha); + brush.setColor(color); + marker->setLabelBrush(brush); + + brush = marker->brush(); + color = brush.color(); + color.setAlphaF(alpha); + brush.setColor(color); + marker->setBrush(brush); + + QPen pen = marker->pen(); + color = pen.color(); + color.setAlphaF(alpha); + pen.setColor(color); + marker->setPen(pen); +} diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.h b/plugins/channelrx/demodadsb/adsbdemodgui.h index 9957f3c57..1be80d480 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.h +++ b/plugins/channelrx/demodadsb/adsbdemodgui.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "channel/channelgui.h" #include "dsp/dsptypes.h" @@ -56,11 +57,17 @@ class WebAPIAdapterInterface; class HttpDownloadManager; class ADSBDemodGUI; class ADSBOSMTemplateServer; +class CheckList; +class AircraftModel; namespace Ui { class ADSBDemodGUI; } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +using namespace QtCharts; +#endif + // Custom widget to allow formatted decimal numbers to be sorted numerically class CustomDoubleTableWidgetItem : public QTableWidgetItem { @@ -89,12 +96,16 @@ struct Aircraft { QString m_icaoHex; QString m_callsign; // Flight callsign QString m_flight; // Guess at flight number + bool m_globalPosition; // Position has been determined from global decode Real m_latitude; // Latitude in decimal degrees Real m_longitude; // Longitude in decimal degrees - int m_altitude; // Altitude in feet + float m_radius; // Horizontal containment radius limit (Rc) in metres + int m_altitude; // Altitude in feet (will be 0 if on surface) + int m_pressureAltitude; // Pressure altitude in feet for Map PFD altimeter (can be negative on surface) bool m_onSurface; // Indicates if on surface or airborne bool m_altitudeGNSS; // Altitude is GNSS HAE (Height above WGS-84 ellipsoid) rather than barometric alitute (relative to 29.92 Hg) - float m_heading; // Heading or track in degrees + float m_heading; // Heading in degrees magnetic + float m_track; // Track in degrees true? int m_verticalRate; // Vertical climb rate in ft/min QString m_emitterCategory; // Aircraft type QString m_status; // Aircraft status @@ -102,23 +113,37 @@ struct Aircraft { Real m_range; // Distance from station to aircraft Real m_azimuth; // Azimuth from station to aircraft Real m_elevation; // Elevation from station to aircraft - QDateTime m_time; // When last updated + QDateTime m_rxTime; // When last frame received (can be long ago if reading from log file) + QDateTime m_updateTime; // Last time we updated data for this aircraft (used for determining when to remove an aircraft) int m_selAltitude; // Selected altitude in MCP/FCU or FMS in feet int m_selHeading; // Selected heading in MCP/FCU in degrees - int m_baro; // Aircraft baro setting in mb (Mode-S) + float m_baro; // Aircraft baro setting in mb (Mode-S) float m_roll; // In degrees int m_groundspeed; // In knots float m_turnRate; // In degrees per second int m_trueAirspeed; // In knots int m_indicatedAirspeed; // In knots float m_mach; // Mach number + bool m_autopilot; + bool m_vnavMode; + bool m_altHoldMode; + bool m_approachMode; + bool m_lnavMode; + bool m_tcasOperational; // Appears only to be true if TA/RA, false if TA ONLY bool m_bdsCapabilities[16][16]; // BDS capabilities are indicaited by BDS 1.7 + int m_adsbVersion; + bool m_nicSupplementA; + bool m_nicSupplementB; + bool m_nicSupplementC; bool m_positionValid; // Indicates if we have valid data for the above fields bool m_altitudeValid; + bool m_pressureAltitudeValid; + bool m_onSurfaceValid; bool m_headingValid; + bool m_trackValid; bool m_verticalRateValid; bool m_selAltitudeValid; bool m_selHeadingValid; @@ -129,16 +154,30 @@ struct Aircraft { bool m_trueAirspeedValid; bool m_indicatedAirspeedValid; bool m_machValid; + bool m_autopilotValid; + bool m_vnavModeValid; + bool m_altHoldModeValid; + bool m_approachModeValid; + bool m_lnavModeValid; + bool m_tcasOperationalValid; + bool m_bdsCapabilitiesValid; + bool m_adsbVersionValid; + bool m_nicSupplementAValid; + bool m_nicSupplementBValid; + bool m_nicSupplementCValid; // State for calculating position using two CPR frames bool m_cprValid[2]; - Real m_cprLat[2]; - Real m_cprLong[2]; + double m_cprLat[2]; + double m_cprLong[2]; QDateTime m_cprTime[2]; int m_adsbFrameCount; // Number of ADS-B frames for this aircraft + int m_modesFrameCount; // Number of Mode-S frames for this aircraft + int m_nonTransponderFrameCount; int m_tisBFrameCount; + int m_adsrFrameCount; float m_minCorrelation; float m_maxCorrelation; float m_correlation; @@ -148,8 +187,12 @@ struct Aircraft { bool m_isHighlighted; // Are we highlighting this aircraft in the table and map bool m_showAll; - QVariantList m_coordinates; // Coordinates we've recorded the aircraft at + QList m_coordinates; // Coordinates we've recorded the aircraft at, split up in to altitude ranges + QList m_recentCoordinates; // Last 20 seconds of coordinates for ATC mode QList m_coordinateDateTimes; + QList m_coordinateColors; // 0-7 index to 8 color palette according to altitude + QList m_recentCoordinateColors; + int m_lastColor; AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database QString m_aircraft3DModel; // 3D model for map based on aircraft type @@ -159,6 +202,7 @@ struct Aircraft { ADSBDemodGUI *m_gui; QString m_flagIconURL; QString m_airlineIconURL; + QString m_sideviewIconURL; // For animation on 3D map float m_runwayAltitude; @@ -168,10 +212,14 @@ struct Aircraft { bool m_rotorStarted; // Rotors started on 'Rotorcraft' bool m_engineStarted; // Engines started (typically propellors) QDateTime m_positionDateTime; - QDateTime m_orientationDateTime; + QDateTime m_orientationDateTime; // FIXME QDateTime m_headingDateTime; - QDateTime m_prevHeadingDateTime; - int m_prevHeading; + QDateTime m_trackDateTime; + QDateTime m_altitudeDateTime; + QDateTime m_indicatedAirspeedDateTime; + QDateTime m_prevTrackDateTime; + int m_prevTrack; + float m_trackWhenHeadingSet; float m_pitchEst; // Estimated pitch based on vertical rate float m_rollEst; // Estimated roll based on rate of change in heading @@ -182,30 +230,48 @@ struct Aircraft { QTableWidgetItem *m_callsignItem; QTableWidgetItem* m_atcCallsignItem; QTableWidgetItem *m_modelItem; + QTableWidgetItem *m_typeItem; + QTableWidgetItem *m_sideviewItem; QTableWidgetItem *m_airlineItem; QTableWidgetItem *m_latitudeItem; QTableWidgetItem *m_longitudeItem; QTableWidgetItem *m_altitudeItem; QTableWidgetItem *m_headingItem; + QTableWidgetItem *m_trackItem; QTableWidgetItem *m_verticalRateItem; CustomDoubleTableWidgetItem *m_rangeItem; QTableWidgetItem *m_azElItem; QTableWidgetItem *m_emitterCategoryItem; QTableWidgetItem *m_statusItem; QTableWidgetItem *m_squawkItem; + QTableWidgetItem *m_identItem; QTableWidgetItem *m_registrationItem; QTableWidgetItem *m_countryItem; QTableWidgetItem *m_registeredItem; QTableWidgetItem *m_manufacturerNameItem; QTableWidgetItem *m_ownerItem; QTableWidgetItem *m_operatorICAOItem; + QTableWidgetItem *m_interogatorCodeItem; QTableWidgetItem *m_timeItem; + QTableWidgetItem *m_totalFrameCountItem; QTableWidgetItem *m_adsbFrameCountItem; + QTableWidgetItem *m_modesFrameCountItem; + QTableWidgetItem *m_nonTransponderItem; + QTableWidgetItem *m_tisBFrameCountItem; + QTableWidgetItem *m_adsrFrameCountItem; + QTableWidgetItem *m_radiusItem; + QTableWidgetItem *m_nacpItem; + QTableWidgetItem *m_nacvItem; + QTableWidgetItem *m_gvaItem; + QTableWidgetItem *m_nicItem; + QTableWidgetItem *m_nicBaroItem; + QTableWidgetItem *m_silItem; QTableWidgetItem *m_correlationItem; QTableWidgetItem *m_rssiItem; QTableWidgetItem *m_flightStatusItem; QTableWidgetItem *m_depItem; QTableWidgetItem *m_arrItem; + QTableWidgetItem *m_stopsItem; QTableWidgetItem *m_stdItem; QTableWidgetItem *m_etdItem; QTableWidgetItem *m_atdItem; @@ -218,6 +284,13 @@ struct Aircraft { QTableWidgetItem *m_apItem; QTableWidgetItem *m_vModeItem; QTableWidgetItem *m_lModeItem; + QTableWidgetItem *m_tcasItem; + QTableWidgetItem *m_acasItem; + QTableWidgetItem *m_raItem; + QTableWidgetItem *m_maxSpeedItem; + QTableWidgetItem *m_versionItem; + QTableWidgetItem *m_lengthItem; + QTableWidgetItem *m_widthItem; QTableWidgetItem *m_rollItem; QTableWidgetItem *m_groundspeedItem; QTableWidgetItem *m_turnRateItem; @@ -231,16 +304,18 @@ struct Aircraft { QTableWidgetItem *m_staticPressureItem; QTableWidgetItem *m_staticAirTempItem; QTableWidgetItem *m_humidityItem; - QTableWidgetItem *m_tisBItem; Aircraft(ADSBDemodGUI *gui) : m_icao(0), + m_globalPosition(false), m_latitude(0), m_longitude(0), + m_radius(0.0f), m_altitude(0), m_onSurface(false), m_altitudeGNSS(false), m_heading(0), + m_track(0), m_verticalRate(0), m_azimuth(0), m_elevation(0), @@ -253,9 +328,22 @@ struct Aircraft { m_trueAirspeed(0), m_indicatedAirspeed(0), m_mach(0.0f), + m_autopilot(false), + m_vnavMode(false), + m_altHoldMode(false), + m_approachMode(false), + m_lnavMode(false), + m_adsbVersion(0), + m_tcasOperational(false), + m_nicSupplementA(false), + m_nicSupplementB(false), + m_nicSupplementC(false), m_positionValid(false), m_altitudeValid(false), + m_pressureAltitudeValid(false), + m_onSurfaceValid(false), m_headingValid(false), + m_trackValid(false), m_verticalRateValid(false), m_selAltitudeValid(false), m_selHeadingValid(false), @@ -266,9 +354,22 @@ struct Aircraft { m_trueAirspeedValid(false), m_indicatedAirspeedValid(false), m_machValid(false), + m_autopilotValid(false), + m_vnavModeValid(false), + m_altHoldModeValid(false), + m_approachModeValid(false), + m_lnavModeValid(false), + m_tcasOperationalValid(false), m_bdsCapabilitiesValid(false), + m_adsbVersionValid(false), + m_nicSupplementAValid(false), + m_nicSupplementBValid(false), + m_nicSupplementCValid(false), m_adsbFrameCount(0), + m_modesFrameCount(0), + m_nonTransponderFrameCount(0), m_tisBFrameCount(0), + m_adsrFrameCount(0), m_minCorrelation(INFINITY), m_maxCorrelation(-INFINITY), m_correlation(0.0f), @@ -302,9 +403,12 @@ struct Aircraft { m_callsignItem = new QTableWidgetItem(); m_atcCallsignItem = new QTableWidgetItem(); m_modelItem = new QTableWidgetItem(); + m_typeItem = new QTableWidgetItem(); + m_sideviewItem = new QTableWidgetItem(); m_airlineItem = new QTableWidgetItem(); m_altitudeItem = new QTableWidgetItem(); m_headingItem = new QTableWidgetItem(); + m_trackItem = new QTableWidgetItem(); m_verticalRateItem = new QTableWidgetItem(); m_rangeItem = new CustomDoubleTableWidgetItem(); m_azElItem = new QTableWidgetItem(); @@ -313,19 +417,34 @@ struct Aircraft { m_emitterCategoryItem = new QTableWidgetItem(); m_statusItem = new QTableWidgetItem(); m_squawkItem = new QTableWidgetItem(); + m_identItem = new QTableWidgetItem(); m_registrationItem = new QTableWidgetItem(); m_countryItem = new QTableWidgetItem(); m_registeredItem = new QTableWidgetItem(); m_manufacturerNameItem = new QTableWidgetItem(); m_ownerItem = new QTableWidgetItem(); m_operatorICAOItem = new QTableWidgetItem(); + m_interogatorCodeItem = new QTableWidgetItem(); m_timeItem = new QTableWidgetItem(); + m_totalFrameCountItem = new QTableWidgetItem(); m_adsbFrameCountItem = new QTableWidgetItem(); + m_modesFrameCountItem = new QTableWidgetItem(); + m_nonTransponderItem = new QTableWidgetItem(); + m_tisBFrameCountItem = new QTableWidgetItem(); + m_adsrFrameCountItem = new QTableWidgetItem(); + m_radiusItem = new QTableWidgetItem(); + m_nacpItem = new QTableWidgetItem(); + m_nacvItem = new QTableWidgetItem(); + m_gvaItem = new QTableWidgetItem(); + m_nicItem = new QTableWidgetItem(); + m_nicBaroItem = new QTableWidgetItem(); + m_silItem = new QTableWidgetItem(); m_correlationItem = new QTableWidgetItem(); m_rssiItem = new QTableWidgetItem(); m_flightStatusItem = new QTableWidgetItem(); m_depItem = new QTableWidgetItem(); m_arrItem = new QTableWidgetItem(); + m_stopsItem = new QTableWidgetItem(); m_stdItem = new QTableWidgetItem(); m_etdItem = new QTableWidgetItem(); m_atdItem = new QTableWidgetItem(); @@ -338,6 +457,13 @@ struct Aircraft { m_apItem = new QTableWidgetItem(); m_vModeItem = new QTableWidgetItem(); m_lModeItem = new QTableWidgetItem(); + m_tcasItem = new QTableWidgetItem(); + m_acasItem = new QTableWidgetItem(); + m_raItem = new QTableWidgetItem(); + m_maxSpeedItem = new QTableWidgetItem(); + m_versionItem = new QTableWidgetItem(); + m_lengthItem = new QTableWidgetItem(); + m_widthItem = new QTableWidgetItem(); m_rollItem = new QTableWidgetItem(); m_groundspeedItem = new QTableWidgetItem(); m_turnRateItem = new QTableWidgetItem(); @@ -351,16 +477,15 @@ struct Aircraft { m_staticPressureItem = new QTableWidgetItem(); m_staticAirTempItem = new QTableWidgetItem(); m_humidityItem = new QTableWidgetItem(); - m_tisBItem = new QTableWidgetItem(); } QString getImage() const; QString getText(const ADSBDemodSettings *settings, bool all=false) const; // Label to use for aircraft on map - QString getLabel(const ADSBDemodSettings *settings) const; + QString getLabel(const ADSBDemodSettings *settings, QDateTime& dateTime) const; // Name to use when selected as a target (E.g. for use as target name in Rotator Controller) - QString targetName() + QString targetName() const { if (!m_callsign.isEmpty()) return QString("Callsign: %1").arg(m_callsign); @@ -368,6 +493,71 @@ struct Aircraft { return QString("ICAO: %1").arg(m_icao, 0, 16); } + void setOnSurface(const QDateTime& dateTime); + void setAltitude(int altitudeFt, bool gnss, const QDateTime& dateTime, const ADSBDemodSettings& settings); + void setVerticalRate(int verticalRate, const ADSBDemodSettings& settings); + void setGroundspeed(float groundspeed, const ADSBDemodSettings& settings); + void setTrueAirspeed(int airspeed, const ADSBDemodSettings& settings); + void setIndicatedAirspeed(int airspeed, const QDateTime& dateTime, const ADSBDemodSettings& settings); + void setTrack(float track, const QDateTime& dateTime); + void setHeading(float heading, const QDateTime& dateTime); + void addCoordinate(const QDateTime& dateTime, AircraftModel *model); + void clearCoordinates(AircraftModel *model); +}; + +class AircraftPathModel : public QAbstractListModel { + Q_OBJECT + +public: + using QAbstractListModel::QAbstractListModel; + enum MarkerRoles{ + coordinatesRole = Qt::UserRole + 1, + colorRole = Qt::UserRole + 2, + }; + + AircraftPathModel(AircraftModel *aircraftModel, Aircraft *aircraft) : + m_aircraftModel(aircraftModel), + m_aircraft(aircraft), + m_paths(0), + m_showFullPath(false), + m_showATCPath(false) + { + settingsUpdated(); + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override { + (void) parent; + return m_paths; + } + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + void add(); + void updateLast(); + void removed(); + void clear(); + void settingsUpdated(); + + Qt::ItemFlags flags(const QModelIndex &index) const override + { + (void) index; + return Qt::ItemIsEnabled; + } + + QHash roleNames() const { + QHash roles; + roles[coordinatesRole] = "coordinates"; + roles[colorRole] = "color"; + return roles; + } + +private: + + AircraftModel *m_aircraftModel; + Aircraft *m_aircraft; + int m_paths; // Should match m_aircraft->m_coordinates.count() + bool m_showFullPath; + bool m_showATCPath; }; // Aircraft data model used by QML map item @@ -385,12 +575,16 @@ public: aircraftPathRole = Qt::UserRole + 6, showAllRole = Qt::UserRole + 7, highlightedRole = Qt::UserRole + 8, - targetRole = Qt::UserRole + 9 + targetRole = Qt::UserRole + 9, + radiusRole = Qt::UserRole + 10, + showRadiusRole = Qt::UserRole + 11, + aircraftPathModelRole = Qt::UserRole + 12, }; Q_INVOKABLE void addAircraft(Aircraft *aircraft) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_aircrafts.append(aircraft); + m_pathModels.append(new AircraftPathModel(this, aircraft)); endInsertRows(); } @@ -409,7 +603,8 @@ public: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } - void aircraftUpdated(Aircraft *aircraft) { + void aircraftUpdated(Aircraft *aircraft) + { int row = m_aircrafts.indexOf(aircraft); if (row >= 0) { @@ -418,30 +613,72 @@ public: } } - void allAircraftUpdated() { - /* - // Not sure why this doesn't work - it should be more efficient - // than the following code - emit dataChanged(index(0), index(rowCount())); - */ - for (int i = 0; i < m_aircrafts.count(); i++) + void highlightChanged(Aircraft *aircraft) + { + int row = m_aircrafts.indexOf(aircraft); + if (row >= 0) { - QModelIndex idx = index(i); + m_pathModels[row]->settingsUpdated(); + QModelIndex idx = index(row); emit dataChanged(idx, idx); } } - void removeAircraft(Aircraft *aircraft) { + void clearCoords(Aircraft *aircraft) + { + int row = m_aircrafts.indexOf(aircraft); + if (row >= 0) { + m_pathModels[row]->clear(); + } + } + + void aircraftCoordsUpdated(Aircraft *aircraft) + { + int row = m_aircrafts.indexOf(aircraft); + if (row >= 0) { + m_pathModels[row]->updateLast(); + } + } + + void aircraftCoordsAdded(Aircraft *aircraft) + { + int row = m_aircrafts.indexOf(aircraft); + if (row >= 0) { + m_pathModels[row]->add(); + } + } + + void aircraftCoordsRemoved(Aircraft *aircraft) + { + int row = m_aircrafts.indexOf(aircraft); + if (row >= 0) { + m_pathModels[row]->removed(); + } + } + + void allAircraftUpdated() + { + emit dataChanged(index(0), index(rowCount()-1)); + + for (int i = 0; i < m_aircrafts.count(); i++) { + m_pathModels[i]->settingsUpdated(); + } + } + + void removeAircraft(Aircraft *aircraft) + { int row = m_aircrafts.indexOf(aircraft); if (row >= 0) { beginRemoveRows(QModelIndex(), row, row); m_aircrafts.removeAt(row); + delete m_pathModels.takeAt(row); endRemoveRows(); } } - QHash roleNames() const { + QHash roleNames() const + { QHash roles; roles[positionRole] = "position"; roles[headingRole] = "heading"; @@ -452,22 +689,12 @@ public: roles[showAllRole] = "showAll"; roles[highlightedRole] = "highlighted"; roles[targetRole] = "target"; + roles[radiusRole] = "containmentRadius"; + roles[aircraftPathModelRole] = "aircraftPathModel"; return roles; } - void setFlightPaths(bool flightPaths) - { - m_flightPaths = flightPaths; - allAircraftUpdated(); - } - - void setAllFlightPaths(bool allFlightPaths) - { - m_allFlightPaths = allFlightPaths; - allAircraftUpdated(); - } - - void setSettings(const ADSBDemodSettings *settings) + void setSettings(const ADSBDemodSettings *settings) { m_settings = settings; allAircraftUpdated(); @@ -487,11 +714,11 @@ public: } } + const ADSBDemodSettings *m_settings; + private: QList m_aircrafts; - bool m_flightPaths; - bool m_allFlightPaths; - const ADSBDemodSettings *m_settings; + QList m_pathModels; }; // Airport data model used by QML map item @@ -667,26 +894,34 @@ public: airspacePolygonRole = Qt::UserRole + 6 }; - Q_INVOKABLE void addAirspace(Airspace *airspace) { + Q_INVOKABLE void addAirspace(Airspace *airspace) + { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_airspaces.append(airspace); - // Convert QPointF to QVariantList of QGeoCoordinates - QVariantList polygon; - for (const auto p : airspace->m_polygon) - { - QGeoCoordinate coord(p.y(), p.x(), airspace->topHeightInMetres()); - polygon.push_back(QVariant::fromValue(coord)); - } - m_polygons.append(polygon); + updatePolygon(airspace, -1); endInsertRows(); } - int rowCount(const QModelIndex &parent = QModelIndex()) const override { + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { Q_UNUSED(parent) return m_airspaces.count(); } - void removeAllAirspaces() { + void removeAirspace(Airspace *airspace) + { + int idx = m_airspaces.indexOf(airspace); + if (idx >= 0) + { + beginRemoveRows(QModelIndex(), idx, idx); + m_airspaces.removeAt(idx); + m_polygons.removeAt(idx); + endRemoveRows(); + } + } + + void removeAllAirspaces() + { if (m_airspaces.count() > 0) { beginRemoveRows(QModelIndex(), 0, m_airspaces.count() - 1); @@ -696,6 +931,23 @@ public: } } + void airspaceUpdated(const Airspace *airspace) + { + int row = m_airspaces.indexOf(airspace); + if (row >= 0) + { + updatePolygon(airspace, row); + + QModelIndex idx = index(row); + emit dataChanged(idx, idx); + } + } + + bool contains(const Airspace *airspace) + { + return m_airspaces.contains(airspace); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override; @@ -706,7 +958,8 @@ public: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } - QHash roleNames() const { + QHash roleNames() const + { QHash roles; roles[nameRole] = "name"; roles[detailsRole] = "details"; @@ -720,6 +973,23 @@ public: private: QList m_airspaces; QList m_polygons; + + void updatePolygon(const Airspace *airspace, int row) + { + // Convert QPointF to QVariantList of QGeoCoordinates + QVariantList polygon; + for (const auto p : airspace->m_polygon) + { + QGeoCoordinate coord(p.y(), p.x(), airspace->topHeightInMetres()); + polygon.push_back(QVariant::fromValue(coord)); + } + if (row == -1) { + m_polygons.append(polygon); + } else { + m_polygons.replace(row, polygon); + } + } + }; // NavAid model used for each NavAid on the map @@ -867,6 +1137,23 @@ protected: class ADSBDemodGUI : public ChannelGUI { Q_OBJECT + struct Interogator { + Real m_minLatitude; + Real m_maxLatitude; + Real m_minLongitude; + Real m_maxLongitude; + bool m_valid; + Airspace m_airspace; + + Interogator() : + m_valid(false) + { + } + + void update(int ic, Aircraft *aircraft, AirspaceModel *airspaceModel, CheckList *checkList, bool display); + void calcPoly(); + }; + public: static ADSBDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); @@ -923,6 +1210,7 @@ private: QHash m_aircraft; // Hashed on ICAO QSharedPointer> m_aircraftInfo; + QSharedPointer> m_routeInfo; // Hashed on callsign QSharedPointer> m_airportInfo; // Hashed on id AircraftModel m_aircraftModel; AirportModel m_airportModel; @@ -936,12 +1224,85 @@ private: Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report MovingAverageUtil m_correlationAvg; MovingAverageUtil m_correlationOnesAvg; + MovingAverageUtil m_qnhAvg; + Aircraft *m_highlightAircraft; // Aircraft we want to highlight, when selected in table float m_currentAirportRange; // Current settings, so we only update if changed ADSBDemodSettings::AirportType m_currentAirportMinimumSize; bool m_currentDisplayHeliports; + static const int m_maxRangeDeg = 5; + QList m_maxRange[2]; + Airspace m_coverageAirspace[2]; + + Interogator m_interogators[ADSB_IC_MAX]; + + enum StatsRow { + ADSB_FRAMES, + MODE_S_FRAMES, + TOTAL_FRAMES, + ADSB_RATE, + MODE_S_RATE, + TOTAL_RATE, + DATA_RATE, + CORRELATOR_MATCHES, + PERCENT_VALID, + PREAMBLE_FAILS, + CRC_FAILS, + TYPE_FAILS, + INVALID_FAILS, + ICAO_FAILS, + RANGE_FAILS, + ALT_FAILS, + AVERAGE_CORRELATION, + TC_0, + TC_1_4, + TC_5_8, + TC_9_18, + TC_19, + TC_20_22, + TC_24, + TC_28, + TC_29, + TC_31, + TC_RESERVED, + DF0, + DF4, + DF5, + DF11, + DF16, + DF17, + DF18, + DF19, + DF20_21, + DF22, + DF24, + MAX_RANGE, + MAX_ALTITUDE, + MAX_RATE + }; + + qint64 m_rangeFails; + qint64 m_altFails; + QDateTime m_frameRateTime; + qint64 m_adsbFrameRateCount; + qint64 m_modesFrameRateCount; + qint64 m_totalBytes; + float m_maxRangeStat; + float m_maxAltitudeStat; + float m_maxRateState; + qint64 m_dfStats[32]; + qint64 m_tcStats[32]; + QChart *m_chart; + QLineSeries *m_adsbFrameRateSeries; + QLineSeries *m_modesFrameRateSeries; + QLineSeries *m_aircraftSeries; + QDateTimeAxis *m_xAxis; + QValueAxis *m_fpsYAxis; + QValueAxis *m_aircraftYAxis; + QDateTime m_averageTime; + #ifdef QT_TEXTTOSPEECH_FOUND QTextToSpeech *m_speech; #endif @@ -977,6 +1338,7 @@ private: static const QString m_flightStatuses[]; static const QString m_hazardSeverity[]; static const QString m_fomSources[]; + static const QString m_nacvStrings[]; explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~ADSBDemodGUI(); @@ -990,8 +1352,6 @@ private: void makeUIConnections(); void updateAbsoluteCenterFrequency(); - void updatePosition(Aircraft *aircraft); - bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition); void clearFromMap(const QString& name); void sendToMap(Aircraft *aircraft, QList *animations); Aircraft *getAircraft(int icao, bool &newAircraft); @@ -1006,10 +1366,24 @@ private: float correlationOnes, unsigned crc, bool updateModel); - void decodeModeS(const QByteArray data, int df, Aircraft *aircraft); + + void decodeID(const QByteArray& data, QString& emitterCategory, QString& callsign); + void decodeGroundspeed(const QByteArray& data, float& v, float& h); + void decodeAirspeed(const QByteArray& data, bool& tas, int& as, bool& hdgValid, float& hdg); + void decodeVerticalRate(const QByteArray& data, int& verticalRate); + void updateAircraftPosition(Aircraft *aircraft, double latitude, double longitude, const QDateTime& dateTime); + bool validateGlobalPosition(double latitude, double longitude, bool countFailure); + bool validateLocalPosition(double latitude, double longitude, bool surfacePosition, bool countFailure); + bool decodeGlobalPosition(int f, const double cprLat[2], const double cprLong[2], const QDateTime cprTime[2], double& latitude, double& longitude, bool countFailure); + bool decodeLocalPosition(int f, double cprLat, double cprLong, bool onSurface, const QDateTime& dateTime, const Aircraft *aircraft, double& latitude, double& longitude, bool countFailure); + void decodeCpr(const QByteArray& data, int& f, double& latCpr, double& lonCpr) const; + bool decodeAltitude(const QByteArray& data, int& altFt) const; + void decodeModeSAltitude(const QByteArray& data, const QDateTime dateTime, Aircraft *aircraft); + void decodeModeS(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft); void decodeCommB(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft, bool &updatedCallsign); QList *animate(QDateTime dateTime, Aircraft *aircraft); SWGSDRangel::SWGMapAnimation *gearAnimation(QDateTime startDateTime, bool up); + SWGSDRangel::SWGMapAnimation *gearAngle(QDateTime startDateTime, bool flat); SWGSDRangel::SWGMapAnimation *flapsAnimation(QDateTime startDateTime, float currentFlaps, float flaps); SWGSDRangel::SWGMapAnimation *slatsAnimation(QDateTime startDateTime, bool retract); SWGSDRangel::SWGMapAnimation *rotorAnimation(QDateTime startDateTime, bool stop); @@ -1022,18 +1396,18 @@ private: QString subAircraftString(Aircraft *aircraft, const QString &string); void resizeTable(); QString getDataDir(); - void readAirportDB(const QString& filename); - void readAirportFrequenciesDB(const QString& filename); void update3DModels(); void updateAirports(); void updateAirspaces(); void updateNavAids(); void updateChannelList(); + void removeAircraft(QHash::iterator& i, Aircraft *aircraft); QAction *createCheckableItem(QString& text, int idx, bool checked); Aircraft* findAircraftByFlight(const QString& flight); QString dataTimeToShortString(QDateTime dt); void initFlightInformation(); void initAviationWeather(); + void setShowContainmentRadius(bool show); void applyMapSettings(); void updatePhotoText(Aircraft *aircraft); void updatePhotoFlightInformation(Aircraft *aircraft); @@ -1045,16 +1419,33 @@ private: void applyImportSettings(); void sendAircraftReport(); void updatePosition(float latitude, float longitude, float altitude); + void clearOldHeading(Aircraft *aircraft, const QDateTime& dateTime, float newTrack); + void updateQNH(const Aircraft *aircraft, float qnh); + void setCallsign(Aircraft *aircraft, const QString& callsign); + + void initCoverageMap(); + void clearCoverageMap(); + void updateCoverageMap(float azimuth, float elevation, float distance, float altitude); void leaveEvent(QEvent*); void enterEvent(EnterEventType*); + void updateDFStats(int df); + bool updateTCStats(int tc, int row, int low, int high); + void resetStats(); + void plotChart(); + int countActiveAircraft(); + void averageSeries(QLineSeries *series, const QDateTime& startTime, const QDateTime& endTime); + void legendMarkerClicked(); + private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int value); void on_threshold_valueChanged(int value); + void on_chipsThreshold_valueChanged(int value); void on_phaseSteps_valueChanged(int value); void on_tapsPerPhase_valueChanged(int value); + void statsTable_customContextMenuRequested(QPoint pos); void adsbData_customContextMenuRequested(QPoint point); void on_adsbData_cellClicked(int row, int column); void on_adsbData_cellDoubleClicked(int row, int column); @@ -1063,18 +1454,24 @@ private slots: void columnSelectMenu(QPoint pos); void columnSelectMenuChecked(bool checked = false); void on_spb_currentIndexChanged(int value); - void on_correlateFullPreamble_clicked(bool checked); void on_demodModeS_clicked(bool checked); void on_feed_clicked(bool checked); void on_notifications_clicked(); void on_flightInfo_clicked(); void on_findOnMapFeature_clicked(); - void on_getOSNDB_clicked(); + void on_deleteAircraft_clicked(); + void on_getAircraftDB_clicked(); void on_getAirportDB_clicked(); void on_getAirspacesDB_clicked(); + void on_coverage_clicked(bool checked); + void on_displayChart_clicked(bool checked); + void on_stats_clicked(bool checked); void on_flightPaths_clicked(bool checked); void on_allFlightPaths_clicked(bool checked); void on_atcLabels_clicked(bool checked); + void on_displayOrientation_clicked(bool checked); + void on_displayRadius_clicked(bool checked); + void on_ic_globalCheckStateChanged(int state); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); @@ -1101,6 +1498,12 @@ private slots: void devicePositionChanged(float latitude, float longitude, float altitude); void requestMetar(const QString& icao); void weatherUpdated(const AviationWeather::METAR &metar); + void on_manualQNH_clicked(bool checked); + void on_qnh_valueChanged(int value); + void clearCoverage(const QPoint& p); + void clearStats(const QPoint& p); + void clearChart(const QPoint& p); + void resetChartAxes(); signals: void homePositionChanged(); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.ui b/plugins/channelrx/demodadsb/adsbdemodgui.ui index 1cab19173..e92e959f8 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.ui +++ b/plugins/channelrx/demodadsb/adsbdemodgui.ui @@ -6,7 +6,7 @@ 0 0 - 506 + 770 1046 @@ -36,7 +36,7 @@ 0 0 500 - 141 + 161 @@ -107,7 +107,7 @@ PointingHandCursor - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus Demod shift frequency from center in Hz @@ -120,7 +120,7 @@ Hz - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -129,7 +129,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -151,13 +151,13 @@ Channel power - Qt::RightToLeft + Qt::LayoutDirection::RightToLeft -100.0 dB - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -228,7 +228,7 @@ 2300000 - Qt::Horizontal + Qt::Orientation::Horizontal @@ -242,7 +242,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -252,10 +252,36 @@ + + + + Check to set QNH manually + + + QNH + + + true + + + + + + + Air pressure in hPa + + + 500 + + + 1500 + + + - Qt::Vertical + Qt::Orientation::Vertical @@ -296,7 +322,7 @@ 4 - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter @@ -401,7 +427,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -421,20 +447,7 @@ - Qt::Vertical - - - - - - - Correlate against full preamble. - - - FP - - - true + Qt::Orientation::Vertical @@ -483,20 +496,65 @@ + + + + PCE + + + + + + + + 24 + 24 + + + + Number of allowed premable chip errors for Mode S frames + + + 0 + + + 8 + + + 1 + + + 8 + + + + + + + + 30 + 0 + + + + 16 + + + - + - Download the latest Opensky-Network aircraft database (80MB) + Download the latest aircraft and route database (45MB) ... - + :/icons/aircraft.png:/icons/aircraft.png @@ -510,7 +568,7 @@ ... - + :/icons/airport.png:/icons/airport.png @@ -524,7 +582,7 @@ ... - + :/icons/vor.png:/icons/vor.png @@ -572,7 +630,7 @@ ^ - + :/icons/allflightpaths.png:/icons/allflightpaths.png @@ -592,7 +650,7 @@ ^ - + :/icons/controltower.png:/icons/controltower.png @@ -603,6 +661,107 @@ + + + + Display receiver coverage on map. Right click to reset coverage data. + + + ^ + + + + :/icons/coverage.png:/icons/coverage.png + + + true + + + true + + + + + + + Display statistics. Right click to reset data. + + + + + + + :/icons/stats.png:/icons/stats.png + + + true + + + true + + + + + + + Display chart. Right click to reset data + + + + + + + :/icons/chart.png:/icons/chart.png + + + true + + + true + + + + + + + Check to display table and map side by side. Uncheck for table on top of map + + + + + + + :/icons/vertical.png + :/icons/horizontal.png:/icons/vertical.png + + + true + + + true + + + + + + + Check to display aircraft containment radius on map + + + + + + + :/icons/radius.png:/icons/radius.png + + + true + + + true + + + @@ -726,19 +885,33 @@ - + - Demodulator statistics + Delete all aircraft - - + ... + + + + :/bin.png:/bin.png + + + false + + + + + + + Check to display radar coverage by Interogator Code - Qt::Horizontal + Qt::Orientation::Horizontal @@ -777,9 +950,9 @@ 0 - 140 - 500 - 600 + 190 + 756 + 627 @@ -797,7 +970,7 @@ ADS-B Data - + 0 @@ -819,7 +992,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -851,572 +1024,1295 @@ 3 - - - - 0 - 0 - + + + + 0 + 0 + - - QAbstractItemView::NoEditTriggers - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - ICAO ID - - - Aircraft International Civil Aviation Organization identifier. Links to www.planespotters.net - - - - - Callsign - - - Callsign. Links to www.flightradar24.com - - - - - ATC Callsign - - - Airline callsign used by ATC - - - - - Aircraft - - - Aircraft model - - - - - Airline - - - Airline logo - - - - - Country - - - Country of registration - - - - - GS (kn) - - - Groundspeed in knots or kilometers per hour - - - - - TAS (kn) - - - True airpeed in knots or kilometers per hour - - - - - IAS (kn) - - - Indicated airspeed in knots or kilometers per hour - - - - - Mach - - - Mach number - - - - - Sel Alt (ft) - - - Selected altitude in feet or metres (As set on MCP/FCU or by FMS) - - - - - Alt (ft) - - - Altitude in feet or metres - - - - - VR (ft/m) - - - Vertical climb rate in feet per minute or metres per second - - - - - Sel Hd (°) - - - Selected heading in degrees (As set on MCP/FCU or by FMS) - - - - - Hd (°) - - - Aircraft heading / track in degrees - - - - - TR (°/s) - - - Turn rate in degrees per second - - - - - Roll (°) - - - Roll angle in degrees - - - - - D (km) - - - Range or distance of aircraft from home location - - - - - Az/El (°) - - - Azimuth and elevation to aircraft from My Position. Double click to set as target. - - - - - Cat - - - Aircraft standard category - - - - - Status - - - Aircraft emergency status - - - - - Squawk - - - Mode-A transponder code - - - - - Reg - - - Aircraft registration - - - - - Registered - - - Date aircraft was registered - - - - - Manufacturer - - - Aircraft manufacturer - - - - - Owner - - - Owner of the aircraft - - - - - Operator - - - Aircraft operator ICAO code - - - - - AP - - - Autopilot enabled - - - - - V Mode - - - MCP/FCU vertical mode - - - - - L Mode - - - MCP/FCU lateral mode - - - - - Baro (mb) - - - Barometer setting in millibars - - - - - H Wnd (kn) - - - Headwind in knots or kilometers per hour - - - - - OAT (C) - - - Outside air temperature in degrees Celsius estimated from Mach and TAS - - - - - Wnd (kn) - - - Wind speed in knots - - - - - Wnd (°) - - - Wind direction in degrees - - - - - P (hPa) - - - Static air pressure in hectopascals - - - - - T (C) - - - Static air temperature in degrees Celsius - - - - - U (%) - - - Humidity in percent - - - - - Lat (°) - - - Latitude in degrees positive towards the North - - - - - Lon (°) - - - Longitude in degrees. Positive towards the East - - - - - Updated - - - Time when the last ADS-B message from this aircraft was received. - - - - - Frames - - - Number of ADS-B frames received from this aircraft - - - - - TIS-B - - - Number of TIS-B frames received with this aircraft's ICAO - - - - - Correlation - - - Correlation values for received frames. min/avg/max - - - - - RSSI - - - - - Flight Status - - - Status of flight - - - - - Dep - - - Departure airport - - - - - Arr - - - Arrival airport - - - - - STD - - - Scheduled time of departure - - - - - ETD - - - Estimated time of departure - - - - - ATD - - - Actual time of departure - - - - - STA - - - Scheduled time of arrival - - - - - ETA - - - Estimated time of arrival - - - - - ATA - - - Actual time of arrival - - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::Shape::NoFrame + + + 1 + + + Qt::Orientation::Horizontal + + + + + 1 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QAbstractItemView::EditTrigger::NoEditTriggers + + + QAbstractItemView::SelectionMode::SingleSelection + + + QAbstractItemView::SelectionBehavior::SelectRows + + + + ICAO ID + + + Aircraft International Civil Aviation Organization identifier. Links to www.planespotters.net + + + + + Callsign + + + Callsign. Links to www.flightradar24.com + + + + + ATC Callsign + + + Airline callsign used by ATC + + + + + Aircraft + + + Aircraft model + + + + + Type + + + ICAO aircraft type designator + + + + + Sideview + + + + + Airline + + + Airline logo + + + + + Country + + + Country of registration + + + + + GS (kn) + + + Groundspeed in knots or kilometers per hour + + + + + TAS (kn) + + + True airpeed in knots or kilometers per hour + + + + + IAS (kn) + + + Indicated airspeed in knots or kilometers per hour + + + + + Mach + + + Mach number + + + + + Sel Alt (ft) + + + Selected altitude in feet or metres (As set on MCP/FCU or by FMS) + + + + + Alt (ft) + + + Altitude in feet or metres + + + + + VR (ft/m) + + + Vertical climb rate in feet per minute or metres per second + + + + + Sel Hd (°) + + + Selected heading in degrees (As set on MCP/FCU or by FMS) + + + + + Hd (°) + + + Aircraft heading in degrees + + + + + Trk (°) + + + Aircraft track in degrees + + + + + TR (°/s) + + + Turn rate in degrees per second + + + + + Roll (°) + + + Roll angle in degrees + + + + + D (km) + + + Range or distance of aircraft from home location + + + + + Az/El (°) + + + Azimuth and elevation to aircraft from My Position. Double click to set as target. + + + + + Cat + + + Aircraft standard category + + + + + Status + + + Aircraft emergency status + + + + + Squawk + + + Mode-A transponder code + + + + + Ident + + + Ident switch active + + + + + Reg + + + Aircraft registration + + + + + Registered + + + Date aircraft was registered + + + + + Manufacturer + + + Aircraft manufacturer + + + + + Owner + + + Owner of the aircraft + + + + + Operator + + + Aircraft operator ICAO code + + + + + AP + + + Autopilot enabled + + + + + V Mode + + + MCP/FCU vertical mode + + + + + L Mode + + + MCP/FCU lateral mode + + + + + TCAS + + + TCAS operational. RA in red if active + + + + + ACAS + + + Airborne collision avoidance system capability + + + + + RA + + + ACAS active resolition advisory + + + + + Max AS (kn) + + + Maximum speed in knots + + + + + Version + + + ADS-B version + + + + + L (m) + + + Aircraft length in metres + + + + + W (m) + + + Aircraft width in metres + + + + + QNH (mb) + + + Barometer setting in millibars + + + + + H Wnd (kn) + + + Headwind in knots or kilometers per hour + + + + + OAT (C) + + + Outside air temperature in degrees Celsius estimated from Mach and TAS + + + + + Wnd (kn) + + + Wind speed in knots + + + + + Wnd (°) + + + Wind direction in degrees + + + + + P (hPa) + + + Static air pressure in hectopascals + + + + + T (C) + + + Static air temperature in degrees Celsius + + + + + U (%) + + + Humidity in percent + + + + + Lat (°) + + + Latitude in degrees positive towards the North + + + + + Lon (°) + + + Longitude in degrees. Positive towards the East + + + + + IC + + + Interrogator code (II/SI) + + + + + Updated + + + Time when the last ADS-B message from this aircraft was received. + + + + + Frames + + + Number of ADS-B and Mode S frames received from this aircraft + + + + + ADS-B FC + + + Number of ADS-B frames received from this aircraft + + + + + Mode S FC + + + Number of Mode S frames received from this aircraft + + + + + Non-transponder + + + Non transponder device frame count + + + + + TIS-B FC + + + Number of TIS-B frames received with this aircraft's ICAO + + + + + ADS-R FC + + + Number of ADS-R frames received with this aircraft's ICAO + + + + + Rc + + + Horizontal containment radius limit + + + + + NACp + + + Navigation accuracy category - 95% horizontal position + + + + + NACv + + + Navigation accuracy category - velocity + + + + + GVA + + + Geometric vertical accuracy + + + + + NIC + + + Navigation integrity category + + + + + NICbaro + + + Barometric altitude integrity code + + + + + SIL + + + Surveillance integrity level + + + + + Correlation + + + Correlation values for received frames. min/avg/max + + + + + RSSI + + + Received Signal Strength Indicator (dBFS) + + + + + Flight Status + + + Status of flight + + + + + Dep + + + Departure airport + + + + + Arr + + + Arrival airport + + + + + Stops + + + + + STD + + + Scheduled time of departure + + + + + ETD + + + Estimated time of departure + + + + + ATD + + + Actual time of departure + + + + + STA + + + Scheduled time of arrival + + + + + ETA + + + Estimated time of arrival + + + + + ATA + + + Actual time of arrival + + + + + + + + 4 + + + + + 4 + + + + + + 0 + 0 + + + + + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + + + + + + + + + + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + + + + Qt::TextFormat::RichText + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + + + + Qt::TextFormat::RichText + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + + + + + + + Qt::Orientation::Vertical + + + + 0 + 40 + + + + + + + + + + + + 0 + 0 + + + + + 320 + 0 + + + + + ADS-B Frames + + + Number of ABS-B frames received + + + + + Mode-S Frames + + + Number of Mode S frames received (Excluding ADS-B) + + + + + Total Frames + + + Total number of ADS-B and Mode S frames received + + + + + ASD-B rate (fps) + + + ADS-B received frame rate in frames per second + + + + + Mode-S rate (fps) + + + Mode S received frame rate in frames per second + + + + + Total rate (fps) + + + Total received frame rate in frames per second + + + + + Data Rate (kbps) + + + + + Correlator Matches + + + + + Valid (%) + + + + + Preamble Fails + + + + + CRC Fails + + + Number of frames where CRC is invalid + + + + + Type Fails + + + Frames where DF is invalid + + + + + Invalid Fails + + + + + ICAO Fails + + + + + Range Fails + + + Positions rejected due to range from station being too great + + + + + Alt Fails + + + + + Avg Corr + + + + + TC 0: No position + + + + + TC 1-4: Aircraft indentification + + + + + TC 5-8: Surface position + + + + + TC 9-18: Airborne position (Baro) + + + + + TC 19: Airborne velocity + + + + + TC 20-22: Airborne position (GNSS) + + + + + TC 24: Surface system status + + + + + TC 28: Aircraft status + + + + + TC 29: Target state and status + + + + + TC 31: Aircraft operation status + + + + + TC 23,25-27,30: Reserved + + + + + DF0: Short air-to-air ACAS + + + + + DF4: Surveillance Altitude + + + + + DF5: Surveillance Ident reply + + + + + DF11: All call reply + + + + + DF16: Long air-to-air ACAS + + + + + DF17: ADS-B + + + + + DF18: ADS-B NT / TIS-B / ADS-R + + + No transponder + + + + + DF19: Military + + + + + DF20-21: Comm B + + + + + DF22: Military + + + + + DF24: Comm D ELM + + + + + Max Range (km) + + + + + Max Altitude (ft) + + + + + Max Rate (fps) + + + + + Data + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + + - - - - 4 - - - - - 4 - - - - - - 0 - 0 - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - Qt::RichText - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::Vertical - - - - 0 - 40 - - - - - - @@ -1436,7 +2332,7 @@ Aircraft map - QQuickWidget::SizeRootObjectToView + QQuickWidget::ResizeMode::SizeRootObjectToView @@ -1444,6 +2340,17 @@ + + + + 300 + 250 + + + + Chart. Use wheel to scale x-axis and shift+wheel to scale y-axis + + @@ -1483,6 +2390,16 @@ QLabel
gui/clickablelabel.h
+ + CheckList + QComboBox +
gui/checklist.h
+
+ + QChartView + QGraphicsView +
QtCharts
+
deltaFrequency @@ -1491,9 +2408,8 @@ tapsPerPhase spb demodModeS - correlateFullPreamble threshold - getOSNDB + getAircraftDB getAirportDB getAirspacesDB displaySettings @@ -1507,12 +2423,11 @@ logOpen findOnMapFeature amDemod - adsbData map - + diff --git a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp index d5a1c31c6..cf2e14e76 100644 --- a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp @@ -25,7 +25,7 @@ // Map main ADS-B table column numbers to combo box indices std::vector ADSBDemodNotificationDialog::m_columnMap = { - ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL, + ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL, ADSB_COL_TYPE, ADSB_COL_ALTITUDE, ADSB_COL_GROUND_SPEED, ADSB_COL_RANGE, ADSB_COL_CATEGORY, ADSB_COL_STATUS, ADSB_COL_SQUAWK, ADSB_COL_REGISTRATION, ADSB_COL_MANUFACTURER, ADSB_COL_OWNER, ADSB_COL_OPERATOR_ICAO @@ -117,6 +117,7 @@ void ADSBDemodNotificationDialog::addRow(ADSBDemodSettings::NotificationSettings match->addItem("ICAO ID"); match->addItem("Callsign"); match->addItem("Aircraft"); + match->addItem("Type"); match->addItem("Alt (ft)"); match->addItem("GS (kn)"); match->addItem("D (km)"); diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp index c8aac3be6..10889854d 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp @@ -36,7 +36,8 @@ void ADSBDemodSettings::resetToDefaults() { m_inputFrequencyOffset = 0; m_rfBandwidth = 2*1300000; - m_correlationThreshold = 10.0f; // ADSBDemodSettings::m_palettes = { + {"Rainbow", &ADSBDemodSettings::m_rainbowPalette[0]}, + {"Pastel", &ADSBDemodSettings::m_pastelPalette[0]}, + {"Spectral", &ADSBDemodSettings::m_spectralPalette[0]}, + {"Blues", &ADSBDemodSettings::m_bluePalette[0]}, + {"Purples", &ADSBDemodSettings::m_purplePalette[0]}, + {"Grey", &ADSBDemodSettings::m_greyPalette[0]}, +}; \ No newline at end of file diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.h b/plugins/channelrx/demodadsb/adsbdemodsettings.h index b7909d816..80a8f58af 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.h +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.h @@ -29,63 +29,89 @@ class Serializable; // Number of columns in the table -#define ADSBDEMOD_COLUMNS 54 +#define ADSBDEMOD_COLUMNS 78 // ADS-B table columns #define ADSB_COL_ICAO 0 #define ADSB_COL_CALLSIGN 1 #define ADSB_COL_ATC_CALLSIGN 2 #define ADSB_COL_MODEL 3 -#define ADSB_COL_AIRLINE 4 -#define ADSB_COL_COUNTRY 5 -#define ADSB_COL_GROUND_SPEED 6 -#define ADSB_COL_TRUE_AIRSPEED 7 -#define ADSB_COL_INDICATED_AIRSPEED 8 -#define ADSB_COL_MACH 9 -#define ADSB_COL_SEL_ALTITUDE 10 -#define ADSB_COL_ALTITUDE 11 -#define ADSB_COL_VERTICALRATE 12 -#define ADSB_COL_SEL_HEADING 13 -#define ADSB_COL_HEADING 14 -#define ADSB_COL_TURNRATE 15 -#define ADSB_COL_ROLL 16 -#define ADSB_COL_RANGE 17 -#define ADSB_COL_AZEL 18 -#define ADSB_COL_CATEGORY 19 -#define ADSB_COL_STATUS 20 -#define ADSB_COL_SQUAWK 21 -#define ADSB_COL_REGISTRATION 22 -#define ADSB_COL_REGISTERED 23 -#define ADSB_COL_MANUFACTURER 24 -#define ADSB_COL_OWNER 25 -#define ADSB_COL_OPERATOR_ICAO 26 -#define ADSB_COL_AP 27 -#define ADSB_COL_V_MODE 28 -#define ADSB_COL_L_MODE 29 -#define ADSB_COL_BARO 30 -#define ADSB_COL_HEADWIND 31 -#define ADSB_COL_EST_AIR_TEMP 32 -#define ADSB_COL_WIND_SPEED 33 -#define ADSB_COL_WIND_DIR 34 -#define ADSB_COL_STATIC_PRESSURE 35 -#define ADSB_COL_STATIC_AIR_TEMP 36 -#define ADSB_COL_HUMIDITY 37 -#define ADSB_COL_LATITUDE 38 -#define ADSB_COL_LONGITUDE 39 -#define ADSB_COL_TIME 40 -#define ADSB_COL_FRAMECOUNT 41 -#define ADSB_COL_TIS_B 42 -#define ADSB_COL_CORRELATION 43 -#define ADSB_COL_RSSI 44 -#define ADSB_COL_FLIGHT_STATUS 45 -#define ADSB_COL_DEP 46 -#define ADSB_COL_ARR 47 -#define ADSB_COL_STD 48 -#define ADSB_COL_ETD 49 -#define ADSB_COL_ATD 50 -#define ADSB_COL_STA 51 -#define ADSB_COL_ETA 52 -#define ADSB_COL_ATA 53 +#define ADSB_COL_TYPE 4 +#define ADSB_COL_SIDEVIEW 5 +#define ADSB_COL_AIRLINE 6 +#define ADSB_COL_COUNTRY 7 +#define ADSB_COL_GROUND_SPEED 8 +#define ADSB_COL_TRUE_AIRSPEED 9 +#define ADSB_COL_INDICATED_AIRSPEED 10 +#define ADSB_COL_MACH 11 +#define ADSB_COL_SEL_ALTITUDE 12 +#define ADSB_COL_ALTITUDE 13 +#define ADSB_COL_VERTICALRATE 14 +#define ADSB_COL_SEL_HEADING 15 +#define ADSB_COL_HEADING 16 +#define ADSB_COL_TRACK 17 +#define ADSB_COL_TURNRATE 18 +#define ADSB_COL_ROLL 19 +#define ADSB_COL_RANGE 20 +#define ADSB_COL_AZEL 21 +#define ADSB_COL_CATEGORY 22 +#define ADSB_COL_STATUS 23 +#define ADSB_COL_SQUAWK 24 +#define ADSB_COL_IDENT 25 +#define ADSB_COL_REGISTRATION 26 +#define ADSB_COL_REGISTERED 27 +#define ADSB_COL_MANUFACTURER 28 +#define ADSB_COL_OWNER 29 +#define ADSB_COL_OPERATOR_ICAO 30 +#define ADSB_COL_AP 31 +#define ADSB_COL_V_MODE 32 +#define ADSB_COL_L_MODE 33 +#define ADSB_COL_TCAS 34 +#define ADSB_COL_ACAS 35 +#define ADSB_COL_RA 36 +#define ADSB_COL_MAX_SPEED 37 +#define ADSB_COL_VERSION 38 +#define ADSB_COL_LENGTH 39 +#define ADSB_COL_WIDTH 40 +#define ADSB_COL_BARO 41 +#define ADSB_COL_HEADWIND 42 +#define ADSB_COL_EST_AIR_TEMP 43 +#define ADSB_COL_WIND_SPEED 44 +#define ADSB_COL_WIND_DIR 45 +#define ADSB_COL_STATIC_PRESSURE 46 +#define ADSB_COL_STATIC_AIR_TEMP 47 +#define ADSB_COL_HUMIDITY 48 +#define ADSB_COL_LATITUDE 49 +#define ADSB_COL_LONGITUDE 50 +#define ADSB_COL_IC 51 +#define ADSB_COL_TIME 52 +#define ADSB_COL_FRAMECOUNT 53 +#define ADSB_COL_ADSB_FRAMECOUNT 54 +#define ADSB_COL_MODES_FRAMECOUNT 55 +#define ADSB_COL_NON_TRANSPONDER 56 +#define ADSB_COL_TIS_B_FRAMECOUNT 57 +#define ADSB_COL_ADSR_FRAMECOUNT 58 +#define ADSB_COL_RADIUS 59 +#define ADSB_COL_NACP 60 +#define ADSB_COL_NACV 61 +#define ADSB_COL_GVA 62 +#define ADSB_COL_NIC 63 +#define ADSB_COL_NIC_BARO 64 +#define ADSB_COL_SIL 65 +#define ADSB_COL_CORRELATION 66 +#define ADSB_COL_RSSI 67 +#define ADSB_COL_FLIGHT_STATUS 68 +#define ADSB_COL_DEP 69 +#define ADSB_COL_ARR 70 +#define ADSB_COL_STOPS 71 +#define ADSB_COL_STD 72 +#define ADSB_COL_ETD 73 +#define ADSB_COL_ATD 74 +#define ADSB_COL_STA 75 +#define ADSB_COL_ETA 76 +#define ADSB_COL_ATA 77 + +#define ADSB_IC_MAX 63 struct ADSBDemodSettings { @@ -104,6 +130,7 @@ struct ADSBDemodSettings int32_t m_inputFrequencyOffset; Real m_rfBandwidth; Real m_correlationThreshold; //!< Correlation power threshold in dB + int m_chipsThreshold; //!< How many chips in preamble can be incorrect for Mode S int m_samplesPerBit; int m_removeTimeout; //!< Time in seconds before removing an aircraft, unless a new frame is received @@ -159,7 +186,6 @@ struct ADSBDemodSettings QString m_tableFontName; //!< Font to use for table int m_tableFontSize; bool m_displayDemodStats; - bool m_correlateFullPreamble; bool m_demodModeS; //!< Demodulate all Mode-S frames, not just ADS-B QString m_amDemod; //!< AM Demod to tune to selected ATC frequency bool m_autoResizeTableColumns; @@ -186,13 +212,24 @@ struct ADSBDemodSettings bool m_displayPhotos; Serializable *m_rollupState; bool m_verboseModelMatching; - int m_airfieldElevation; //!< QFE in ft so aircraft takeoff/land from correct position int m_aircraftMinZoom; bool m_atcLabels; bool m_atcCallsigns; int m_transitionAlt; + float m_qnh; + bool m_manualQNH; + bool m_displayCoverage; + bool m_displayChart; + bool m_displayOrientation; + bool m_displayRadius; + bool m_displayIC[ADSB_IC_MAX]; + QString m_flightPathPaletteName; + bool m_favourLivery; //!< Favour airline livery over aircraft type when exact 3D model isn't available + + const QVariant *m_flightPathPalette; + ADSBDemodSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } @@ -203,6 +240,16 @@ struct ADSBDemodSettings void deserializeNotificationSettings(const QByteArray& data, QList& notificationSettings); void applySettings(const QStringList& settingsKeys, const ADSBDemodSettings& settings); QString getDebugString(const QStringList& settingsKeys, bool force = false) const; + void applyPalette(); + + static const QVariant m_rainbowPalette[8]; + static const QVariant m_pastelPalette[8]; + static const QVariant m_spectralPalette[8]; + static const QVariant m_bluePalette[8]; + static const QVariant m_purplePalette[8]; + static const QVariant m_greyPalette[8]; + + static const QHash m_palettes; }; #endif /* PLUGINS_CHANNELRX_DEMODADSB_ADSBDEMODSETTINGS_H_ */ diff --git a/plugins/channelrx/demodadsb/adsbdemodsink.cpp b/plugins/channelrx/demodadsb/adsbdemodsink.cpp index e4792392d..2763197a7 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsink.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsink.cpp @@ -19,14 +19,16 @@ #include #include +#include "util/profiler.h" + #include "adsbdemodsink.h" #include "adsbdemodsinkworker.h" +#include "adsbdemod.h" #include "adsb.h" ADSBDemodSink::ADSBDemodSink() : m_channelSampleRate(6000000), m_channelFrequencyOffset(0), - m_feedTime(0.0), m_sampleBuffer{nullptr, nullptr, nullptr}, m_worker(this), m_writeBuffer(0), @@ -53,7 +55,7 @@ ADSBDemodSink::~ADSBDemodSink() void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) { // Start timing how long we are in this function - m_startPoint = boost::chrono::steady_clock::now(); + PROFILER_START(); // Optimise for common case, where no resampling or frequency offset if ((m_interpolatorDistance == 1.0f) && (m_channelFrequencyOffset == 0)) @@ -113,8 +115,7 @@ void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const Sample } // Calculate number of seconds in this function - boost::chrono::duration sec = boost::chrono::steady_clock::now() - m_startPoint; - m_feedTime += sec.count(); + PROFILER_STOP("ADSB feed"); } void ADSBDemodSink::processOneSample(Real magsq) @@ -127,8 +128,17 @@ void ADSBDemodSink::processOneSample(Real magsq) m_writeIdx++; if (!m_bufferDateTimeValid[m_writeBuffer]) { - m_bufferFirstSampleDateTime[m_writeBuffer] = QDateTime::currentDateTime(); + QDateTime dateTime = QDateTime::currentDateTime(); + if (m_minFirstSampleDateTime.isValid() && (dateTime < m_minFirstSampleDateTime)) { + dateTime = m_minFirstSampleDateTime; + } + m_bufferFirstSampleDateTime[m_writeBuffer] = dateTime; m_bufferDateTimeValid[m_writeBuffer] = true; + + // Make sure timestamps from different buffers are in order, even if we receive samples faster than real time + const qint64 samplesPerSecondMSec = ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit / 1000; + const qint64 offsetMSec = m_bufferSize / samplesPerSecondMSec; + m_minFirstSampleDateTime = dateTime.addMSecs(offsetMSec); } if (m_writeIdx >= m_bufferSize) { @@ -138,15 +148,9 @@ void ADSBDemodSink::processOneSample(Real magsq) if (m_writeBuffer >= m_buffers) m_writeBuffer = 0; - // Don't include time spent waiting for a buffer - boost::chrono::duration sec = boost::chrono::steady_clock::now() - m_startPoint; - m_feedTime += sec.count(); - if (m_worker.isRunning()) m_bufferWrite[m_writeBuffer].acquire(); - m_startPoint = boost::chrono::steady_clock::now(); - m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer m_bufferDateTimeValid[m_writeBuffer] = false; @@ -253,7 +257,6 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, const QStri << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_rfBandwidth: " << settings.m_rfBandwidth << " m_correlationThreshold: " << settings.m_correlationThreshold - << " m_correlateFullPreamble: " << settings.m_correlateFullPreamble << " m_demodModeS: " << settings.m_demodModeS << " m_samplesPerBit: " << settings.m_samplesPerBit << " force: " << force; @@ -285,3 +288,9 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, const QStri m_settings.applySettings(settingsKeys, settings); } } + +void ADSBDemodSink::resetStats() +{ + ADSBDemod::MsgResetStats* msg = ADSBDemod::MsgResetStats::create(); + m_worker.getInputMessageQueue()->push(msg); +} diff --git a/plugins/channelrx/demodadsb/adsbdemodsink.h b/plugins/channelrx/demodadsb/adsbdemodsink.h index 163a857ee..c57e312d5 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsink.h +++ b/plugins/channelrx/demodadsb/adsbdemodsink.h @@ -19,9 +19,6 @@ #ifndef INCLUDE_ADSBDEMODSINK_H #define INCLUDE_ADSBDEMODSINK_H -#define BOOST_CHRONO_HEADER_ONLY -#include - #include "dsp/channelsamplesink.h" #include "dsp/nco.h" #include "dsp/interpolator.h" @@ -60,6 +57,7 @@ public: void setMessageQueueToWorker(MessageQueue *messageQueue) { m_messageQueueToWorker = messageQueue; } void startWorker(); void stopWorker(); + void resetStats(); private: friend ADSBDemodSinkWorker; @@ -83,9 +81,6 @@ private: Real m_interpolatorDistance; Real m_interpolatorDistanceRemain; - boost::chrono::steady_clock::time_point m_startPoint; - double m_feedTime; //!< Time spent in feed() - // Triple buffering for sharing sample data between two threads // Top area of each buffer is not used by writer, as it's used by the reader // for copying the last few samples of the previous buffer, so it can @@ -96,6 +91,7 @@ private: QSemaphore m_bufferWrite[3]; //!< Semaphore to control write access to the buffers QSemaphore m_bufferRead[3]; //!< Semaphore to control read access from the buffers QDateTime m_bufferFirstSampleDateTime[3]; //!< Time for first sample in the buffer + QDateTime m_minFirstSampleDateTime; bool m_bufferDateTimeValid[3]; ADSBDemodSinkWorker m_worker; //!< Worker thread that does the actual demodulation int m_writeBuffer; //!< Which of the 3 buffers we're writing in to diff --git a/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp b/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp index a4c9a5fc5..4c266a252 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp @@ -15,29 +15,292 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#define BOOST_CHRONO_HEADER_ONLY -#include - #include #include "util/db.h" +#include "util/profiler.h" +#include "util/units.h" +#include "util/osndb.h" +#include "util/popcount.h" #include "adsbdemodreport.h" #include "adsbdemodsink.h" #include "adsbdemodsinkworker.h" +#include "adsbdemod.h" #include "adsb.h" MESSAGE_CLASS_DEFINITION(ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker, Message) +const Real ADSBDemodSinkWorker::m_correlationScale = 3.0f; + +static int grayToBinary(int gray, int bits) +{ + int binary = 0; + for (int i = bits - 1; i >= 0; i--) { + binary = binary | ((((1 << (i+1)) & binary) >> 1) ^ ((1 << i) & gray)); + } + return binary; +} + +static int gillhamToFeet(int n) +{ + int c1 = (n >> 10) & 1; + int a1 = (n >> 9) & 1; + int c2 = (n >> 8) & 1; + int a2 = (n >> 7) & 1; + int c4 = (n >> 6) & 1; + int a4 = (n >> 5) & 1; + int b1 = (n >> 4) & 1; + int b2 = (n >> 3) & 1; + int d2 = (n >> 2) & 1; + int b4 = (n >> 1) & 1; + int d4 = n & 1; + + int n500 = grayToBinary((d2 << 7) | (d4 << 6) | (a1 << 5) | (a2 << 4) | (a4 << 3) | (b1 << 2) | (b2 << 1) | b4, 4); + int n100 = grayToBinary((c1 << 2) | (c2 << 1) | c4, 3) - 1; + + if (n100 == 6) { + n100 = 4; + } + if (n500 %2 != 0) { + n100 = 4 - n100; + } + + return -1200 + n500*500 + n100*100; +} + +static int decodeModeSAltitude(const QByteArray& data) +{ + int altitude = 0; // Altitude in feet + int altitudeCode = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); + + if (altitudeCode & 0x40) // M bit indicates metres + { + int altitudeMetres = ((altitudeCode & 0x1f80) >> 1) | (altitudeCode & 0x3f); + altitude = Units::metresToFeet(altitudeMetres); + } + else + { + // Remove M and Q bits + int altitudeFix = ((altitudeCode & 0x1f80) >> 2) | ((altitudeCode & 0x20) >> 1) | (altitudeCode & 0xf); + + // Convert to feet + if (altitudeCode & 0x10) { + altitude = altitudeFix * 25 - 1000; + } else { + altitude = gillhamToFeet(altitudeFix); + } + } + return altitude; +} + +void ADSBDemodSinkWorker::handleModeS(unsigned char *data, int bytes, unsigned icao, int df, int firstIndex, unsigned short preamble, Real preambleCorrelation, Real correlationOnes, const QDateTime& dateTime, unsigned crc) +{ + // Ignore downlink formats we can't decode / unlikely + if ((df != 19) && (df != 22) && (df < 24)) + { + QList l; + + if (m_modeSOnlyIcaos.contains(icao)) + { + l = m_modeSOnlyIcaos.value(icao); + if (abs(l.last().m_firstIndex - firstIndex) < 4) { + return; // Duplicate + } + } + else + { + // Stop hash table from getting too big - clear every 10 seconds or so + QDateTime currentDateTime = QDateTime::currentDateTime(); + if (m_lastClear.secsTo(currentDateTime) >= 10) + { + //qDebug() << "Clearing ModeS only hash. size=" << m_modeSOnlyIcaos.size(); + m_modeSOnlyIcaos.clear(); + m_lastClear = currentDateTime; + } + } + + RXRecord rx; + rx.m_data = QByteArray((char*)data, bytes); + rx.m_firstIndex = firstIndex; + rx.m_preamble = preamble; + rx.m_preambleCorrelation = preambleCorrelation; + rx.m_correlationOnes = correlationOnes; + rx.m_dateTime = dateTime; + rx.m_crc = crc; + l.append(rx); + m_modeSOnlyIcaos.insert(icao, l); + + // Have we heard from the same address several times in the last 10 seconds? + if (l.size() >= 5) + { + // Check all frames have consistent altitudes and identifiers + bool idConsistent = true; + bool altitudeConsistent = true; + int altitude = -1; + int id = -1; + + for (int i = 0; i < l.size(); i++) + { + int df2 = ((l[i].m_data[0] >> 3) & ADS_B_DF_MASK); + Real corr = CalcDb::dbPower(m_correlationScale * l[i].m_preambleCorrelation); + + int curAltitude = -1; + int curId = -1; + + if ((df2 == 0) || (df2 == 4) || (df2 == 16) || (df2 == 20)) + { + int curAltitude = decodeModeSAltitude(l[i].m_data); + if (altitude == -1) + { + altitude = curAltitude; + } + else + { + if (abs(curAltitude - altitude) > 1000) { + altitudeConsistent = false; + } + } + } + else if ((df2 == 5) || (df2 == 21)) + { + int curId = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); // No decode - we just want to know if it changes + + if (id == -1) + { + id = curId; + } + else + { + if (id != curId) { + idConsistent = false; + } + } + } + } + + // FIXME: We could optionally check to see if aircraft ICAO is in db, but the db isn't complete + + if (altitudeConsistent && idConsistent) + { + // Forward all frames + for (int i = 0; i < l.size(); i++) { + forwardFrame((const unsigned char *) l[i].m_data.data(), l[i].m_data.size(), l[i].m_preambleCorrelation, l[i].m_correlationOnes, l[i].m_dateTime, l[i].m_crc); + } + + m_icaos.insert(icao, l.back().m_dateTime.toMSecsSinceEpoch()); + } + else + { + m_modeSOnlyIcaos.remove(icao); + } + } + } +} + +// Check if a Mode S frame has reserved bits set or reserved field values +// Refer: Annex 10 Volume IV +bool ADSBDemodSinkWorker::modeSValid(unsigned char *data, unsigned df) +{ + bool invalid = false; + + if (df == 0) + { + invalid = ((data[0] & 0x1) | (data[1] & 0x18) | (data[2] & 0x60)) != 0; + } + else if ((df == 4) || (df == 20)) + { + unsigned fs = data[0] & 0x3; + unsigned dr = (data[1] >> 3) & 0x1f; + + invalid = (fs == 6) || (fs == 7) || ((dr >= 8) && (dr <= 15)); + } + else if ((df == 5) || (df == 21)) + { + unsigned fs = data[0] & 0x3; + unsigned dr = (data[1] >> 3) & 0x1f; + + invalid = (fs == 6) || (fs == 7) || ((dr >= 8) && (dr <= 15)) || ((data[3] & 0x40) != 0); + } + else if (df == 11) + { + unsigned ca = data[0] & 0x7; + + invalid = ((ca >= 1) && (ca <= 3)); + } + else if (df == 16) + { + invalid = ((data[0] & 0x3) | (data[1] & 0x18) | (data[2] & 0x60)) != 0; + } + + return invalid; +} + +// Check if valid ICAO address - i.e. not a reserved address - see Table 9-1 in Annex X Volume III +bool ADSBDemodSinkWorker::icaoValid(unsigned icao) +{ + unsigned msn = (icao >> 20) & 0xf; + + return (icao != 0) && (msn != 0xf) && (msn != 0xb) && (msn != 0xd); +} + +// Is it less than a minute since the last received frame for this ICAO +bool ADSBDemodSinkWorker::icaoHeardRecently(unsigned icao, const QDateTime &dateTime) +{ + const int timeLimitMSec = 60*1000; + + if (m_icaos.contains(icao)) + { + if ((dateTime.toMSecsSinceEpoch() - m_icaos.value(icao)) < timeLimitMSec) { + return true; + } else { + m_icaos.remove(icao); + } + } + + return false; +} + +void ADSBDemodSinkWorker::forwardFrame(const unsigned char *data, int size, float preambleCorrelation, float correlationOnes, const QDateTime& dateTime, unsigned crc) +{ + // Pass to GUI + if (m_sink->getMessageQueueToGUI()) + { + ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( + QByteArray((char*)data, size), + preambleCorrelation, + correlationOnes, + dateTime, + crc); + m_sink->getMessageQueueToGUI()->push(msg); + } + // Pass to worker to feed to other servers + if (m_sink->getMessageQueueToWorker()) + { + ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( + QByteArray((char*)data, size), + preambleCorrelation, + correlationOnes, + dateTime, + crc); + m_sink->getMessageQueueToWorker()->push(msg); + } +} + void ADSBDemodSinkWorker::run() { int readBuffer = 0; + m_lastClear = QDateTime::currentDateTime(); // Acquire first buffer m_sink->m_bufferRead[readBuffer].acquire(); + if (isInterruptionRequested()) { + return; + } + // Start recording how much time is spent processing in this method - boost::chrono::steady_clock::time_point startPoint = boost::chrono::steady_clock::now(); + PROFILER_START(); // Check for updated settings handleInputMessages(); @@ -51,8 +314,6 @@ void ADSBDemodSinkWorker::run() << " samplesPerFrame: " << samplesPerFrame << " samplesPerChip: " << samplesPerChip << " samplesPerBit: " << samplesPerBit - << " correlateFullPreamble: " << m_settings.m_correlateFullPreamble - << " correlationScale: " << m_correlationScale << " correlationThreshold: " << m_settings.m_correlationThreshold; int readIdx = m_sink->m_samplesPerFrame - 1; @@ -63,61 +324,33 @@ void ADSBDemodSinkWorker::run() // Correlate received signal with expected preamble // chip+ indexes are 0, 2, 7, 9 - // correlating over first 6 bits gives a reduction in per-sample - // processing, but more than doubles the number of false matches Real preambleCorrelationOnes = 0.0; Real preambleCorrelationZeros = 0.0; - if (m_settings.m_correlateFullPreamble) + for (int i = 0; i < samplesPerChip; i++) { - for (int i = 0; i < samplesPerChip; i++) - { - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i]; + preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i]; - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i]; + preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i]; - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i]; + preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i]; - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i]; + preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 12*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 13*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 12*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 13*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 14*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 15*samplesPerChip + i]; - } - } - else - { - for (int i = 0; i < samplesPerChip; i++) - { - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i]; - - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i]; - - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i]; - - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i]; - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i]; - - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i]; - preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i]; - - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i]; - preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i]; - } + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 14*samplesPerChip + i]; + preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 15*samplesPerChip + i]; } // Use the ratio of ones power over zeros power, as we don't care how powerful the signal @@ -138,6 +371,7 @@ void ADSBDemodSinkWorker::run() int firstIdx = startIdx; m_demodStats.m_correlatorMatches++; + // Skip over preamble startIdx += samplesPerBit*ADS_B_PREAMBLE_BITS; @@ -147,6 +381,7 @@ void ADSBDemodSinkWorker::run() int currentBit; unsigned char currentByte = 0; int df; + int firstIndex = 0; for (int bit = 0; bit < ADS_B_ES_BITS; bit++) { @@ -165,6 +400,9 @@ void ADSBDemodSinkWorker::run() currentByte |= currentBit << (7-(bit & 0x7)); if ((bit & 0x7) == 0x7) { + if (byteIdx == 0) { + firstIndex = startIdx; + } data[byteIdx++] = currentByte; currentByte = 0; // Don't try to demodulate any further, if this isn't an ADS-B frame @@ -178,6 +416,10 @@ void ADSBDemodSinkWorker::run() } } + Real preambleCorrelationScaled = preambleCorrelation * m_correlationScale; + Real correlationOnes = preambleCorrelationOnes / samplesPerChip; + QDateTime dateTime = rxDateTime(firstIdx, readBuffer); + // Is ADS-B? df = ((data[0] >> 3) & ADS_B_DF_MASK); if ((df == 17) || (df == 18)) @@ -188,109 +430,177 @@ void ADSBDemodSinkWorker::run() m_crc.calculate(data, ADS_B_ES_BYTES-3); if (parity == m_crc.get()) { - // Got a valid frame - m_demodStats.m_adsbFrames++; - // Get 24-bit ICAO and save in hash of ICAOs that have been seen + // Get 24-bit ICAO unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); - m_icaos.insert(icao, true); - // Don't try to re-demodulate the same frame - // We could possibly allow a partial overlap here - readIdx += (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1; - // Pass to GUI - if (m_sink->getMessageQueueToGUI()) + + if (icaoValid(icao)) { - ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( - QByteArray((char*)data, sizeof(data)), - preambleCorrelation * m_correlationScale, - preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer), - m_crc.get()); - m_sink->getMessageQueueToGUI()->push(msg); + // Got a valid frame + m_demodStats.m_adsbFrames++; + // Save in hash of ICAOs that have been seen + m_icaos.insert(icao, dateTime.toMSecsSinceEpoch()); + // Don't try to re-demodulate the same frame + // We could possibly allow a partial overlap here + readIdx += (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1; + forwardFrame(data, sizeof(data), preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get()); } - // Pass to worker to feed to other servers - if (m_sink->getMessageQueueToWorker()) + else { - ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( - QByteArray((char*)data, sizeof(data)), - preambleCorrelation * m_correlationScale, - preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer), - m_crc.get()); - m_sink->getMessageQueueToWorker()->push(msg); + m_demodStats.m_icaoFails++; } } else + { m_demodStats.m_crcFails++; + } } else if (m_settings.m_demodModeS) { - int bytes; + // Decode premable, as correlation alone results in too many false positives for Mode S frames + startIdx = readIdx; + unsigned short preamble = 0; + QVector preambleChips(16); - // Determine number of bytes in frame depending on downlink format - if ((df == 0) || (df == 4) || (df == 5) || (df == 11)) { - bytes = 56/8; - } else if ((df == 16) || (df == 20) || (df == 21) || (df >= 24)) { - bytes = 112/8; - } else { - bytes = 0; - } - if (bytes > 0) + for (int bit = 0; bit < ADS_B_PREAMBLE_BITS; bit++) { - // Extract received parity - int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1]; - // Calculate CRC on received frame - m_crc.init(); - m_crc.calculate(data, bytes-3); - int crc = m_crc.get(); - // DF4 / DF5 / DF20 / DF21 have ICAO address XORed in to parity. - // Extract ICAO from parity and see if it matches an aircraft we've already - // received an ADS-B frame from - if ((df == 4) || (df == 5) || (df == 20) || (df == 21)) + preambleChips[bit*2] = 0.0f; + preambleChips[bit*2+1] = 0.0f; + for (int i = 0; i < samplesPerChip; i++) { - unsigned icao = (parity ^ crc) & 0xffffff; - if (m_icaos.contains(icao)) { - crc ^= icao; - } + preambleChips[bit*2] += m_sink->m_sampleBuffer[readBuffer][startIdx+i]; + preambleChips[bit*2+1] += m_sink->m_sampleBuffer[readBuffer][startIdx+samplesPerChip+i]; } - // For DF11, the last 7 bits may have an address/interogration identifier (II) - // XORed in, so we ignore those bits - if ((parity == crc) || ((df == 11) && ((parity & 0xffff80) == (crc & 0xffff80)))) + + startIdx += samplesPerBit; + } + startIdx = firstIdx; + + float onesAvg = (preambleChips[0] + preambleChips[2] + preambleChips[7] + preambleChips[9]) / 4.0f; + float zerosAvg = (preambleChips[1] + preambleChips[3] + preambleChips[4] + preambleChips[5] + preambleChips[6] + preambleChips[8] + + preambleChips[10] + + preambleChips[11] + preambleChips[12] + preambleChips[13] + preambleChips[14] + preambleChips[15]) / 12.0f; + float midPoint = zerosAvg + (onesAvg - zerosAvg) / 2.0f; + for (int i = 0; i < 16; i++) + { + unsigned chip = preambleChips[i] > midPoint; + preamble |= chip << (15-i); + } + + // qDebug() << "Preamble" << preambleChips << "zerosAvg" << zerosAvg << "onesAvg" << onesAvg << "midPoint" << midPoint << "chips" << Qt::hex << preamble; + + const unsigned short expectedPreamble = 0xa140; + int preambleDifferences = popcount(expectedPreamble ^ preamble); + + if (preambleDifferences <= m_settings.m_chipsThreshold) + { + int bytes; + + // Determine number of bytes in frame depending on downlink format + if ((df == 0) || (df == 4) || (df == 5) || (df == 11)) { + bytes = 56/8; + } else if ((df == 16) || (df == 19) || (df == 20) || (df == 21) || (df == 22) || ((df >= 24) && (df <= 27))) { + bytes = 112/8; + } else { + bytes = 0; + } + + if (bytes > 0) { - m_demodStats.m_modesFrames++; - // Pass to GUI (only pass formats it can decode) - if (m_sink->getMessageQueueToGUI() && ((df == 4) || (df == 5) || (df == 20) || (df == 21))) + bool invalid = modeSValid(data, df); + + if (!invalid) { - ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( - QByteArray((char*)data, bytes), - preambleCorrelation * m_correlationScale, - preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer), - m_crc.get()); - m_sink->getMessageQueueToGUI()->push(msg); + // Extract received parity + int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1]; + // Calculate CRC on received frame + m_crc.init(); + m_crc.calculate(data, bytes-3); + int crc = m_crc.get(); + bool forward = false; + + // ICAO address XORed in to parity, apart from DF11 + // Extract ICAO from parity and see if it matches an aircraft we've already + // received an ADS-B or Mode S frame from + // For DF11, the last 7 bits may have an iterogration code (II/SI) + // XORed in, so we ignore those bits. This does sometimes lead to false-positives + if (df != 11) + { + unsigned icao = (parity ^ crc) & 0xffffff; + + if (icaoValid(icao)) + { + if (icaoHeardRecently(icao, dateTime)) { + forward = true; + } else { + handleModeS(data, bytes, icao, df, firstIndex, preamble, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get()); + } + } + else + { + m_demodStats.m_icaoFails++; + } + } + else + { + // Ignore IC bits + parity &= 0xffff80; + crc &= 0xffff80; + if (parity == crc) + { + // Get 24-bit ICAO + unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); + + if (icaoValid(icao)) + { + if (icaoHeardRecently(icao, dateTime)) { + forward = true; + } else { + handleModeS(data, bytes, icao, df, firstIndex, preamble, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get()); + } + } + else + { + m_demodStats.m_icaoFails++; + } + } + else + { + m_demodStats.m_crcFails++; + } + } + if (forward) + { + m_demodStats.m_modesFrames++; + // Don't try to re-demodulate the same frame + // We could possibly allow a partial overlap here + readIdx += ((bytes*8)+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1; + forwardFrame(data, bytes, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get()); + } + else + { + m_demodStats.m_crcFails++; + } } - // Pass to worker to feed to other servers - if (m_sink->getMessageQueueToWorker()) + else { - ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( - QByteArray((char*)data, bytes), - preambleCorrelation * m_correlationScale, - preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer), - m_crc.get()); - m_sink->getMessageQueueToWorker()->push(msg); + m_demodStats.m_invalidFails++; } } else { - m_demodStats.m_crcFails++; + m_demodStats.m_typeFails++; } } else - m_demodStats.m_typeFails++; + { + m_demodStats.m_preambleFails++; + } } else + { m_demodStats.m_typeFails++; + } } + readIdx++; if (readIdx > m_sink->m_bufferSize - samplesPerFrame) { @@ -299,9 +609,7 @@ void ADSBDemodSinkWorker::run() nextBuffer = 0; // Update amount of time spent processing (don't include time spend in acquire) - boost::chrono::duration sec = boost::chrono::steady_clock::now() - startPoint; - m_demodStats.m_demodTime += sec.count(); - m_demodStats.m_feedTime = m_sink->m_feedTime; + PROFILER_STOP("ADS-B demod"); // Send stats to GUI if (m_sink->getMessageQueueToGUI()) @@ -319,7 +627,7 @@ void ADSBDemodSinkWorker::run() handleInputMessages(); // Resume timing how long we are processing - startPoint = boost::chrono::steady_clock::now(); + PROFILER_RESTART(); int samplesRemaining = m_sink->m_bufferSize - readIdx; if (samplesRemaining > 0) @@ -359,15 +667,6 @@ void ADSBDemodSinkWorker::handleInputMessages() QStringList settingsKeys = cfg->getSettingsKeys(); bool force = cfg->getForce(); - if (settingsKeys.contains("correlateFullPreamble") || force) - { - if (settings.m_correlateFullPreamble) { - m_correlationScale = 3.0; - } else { - m_correlationScale = 2.0; - } - } - if ((settingsKeys.contains("correlationThreshold") && (m_settings.m_correlationThreshold != settings.m_correlationThreshold)) || force) { m_correlationThresholdLinear = CalcDb::powerFromdB(settings.m_correlationThreshold); @@ -382,6 +681,10 @@ void ADSBDemodSinkWorker::handleInputMessages() } delete message; } + else if (ADSBDemod::MsgResetStats::match(*message)) + { + m_demodStats.reset(); + } } } diff --git a/plugins/channelrx/demodadsb/adsbdemodsinkworker.h b/plugins/channelrx/demodadsb/adsbdemodsinkworker.h index e8623efc5..38a8c45d4 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsinkworker.h +++ b/plugins/channelrx/demodadsb/adsbdemodsinkworker.h @@ -77,11 +77,29 @@ private: ADSBDemodSink *m_sink; ADSBDemodStats m_demodStats; Real m_correlationThresholdLinear; - Real m_correlationScale; + static const Real m_correlationScale; crcadsb m_crc; //!< Have as member to avoid recomputing LUT - QHash m_icaos; //!< ICAO addresses that have been received + QHash m_icaos; //!< ICAO addresses that have been received and msecsSinceEpoch last heard + QDateTime m_lastClear; + + struct RXRecord { + QByteArray m_data; + int m_firstIndex; + unsigned short m_preamble; + Real m_preambleCorrelation; + Real m_correlationOnes; + QDateTime m_dateTime; + unsigned m_crc; + }; + + QHash> m_modeSOnlyIcaos; QDateTime rxDateTime(int firstIdx, int readBuffer) const; + void handleModeS(unsigned char *data, int bytes, unsigned icao, int df, int firstIndex, unsigned short preamble, Real preambleCorrelation, Real correlationOnes, const QDateTime& dateTime, unsigned crc); + void forwardFrame(const unsigned char *data, int size, float preambleCorrelation, float correlationOnes, const QDateTime& dateTime, unsigned crc); + bool icaoHeardRecently(unsigned icao, const QDateTime &dateTime); + static bool icaoValid(unsigned icao); + static bool modeSValid(unsigned char *data, unsigned df); }; diff --git a/plugins/channelrx/demodadsb/adsbdemodstats.h b/plugins/channelrx/demodadsb/adsbdemodstats.h index 9b13d306a..8c3d361dc 100644 --- a/plugins/channelrx/demodadsb/adsbdemodstats.h +++ b/plugins/channelrx/demodadsb/adsbdemodstats.h @@ -25,20 +25,27 @@ struct ADSBDemodStats { qint64 m_correlatorMatches; //!< Total number of correlator matches qint64 m_adsbFrames; //!< How many ADS-B frames with correct CRCs qint64 m_modesFrames; //!< How many non-ADS-B Mode-S frames with correct CRCs + qint64 m_preambleFails; //!< How many non-ADS-B Mode S frames with errors in preamble qint64 m_crcFails; //!< How many frames we've demoded with incorrect CRCs qint64 m_typeFails; //!< How many frames we've demoded with unknown type (DF) so we can't check CRC - double m_demodTime; //!< How long we've spent in run() - double m_feedTime; //!< How long we've spent in feed() + qint64 m_invalidFails; //!< How many frames we've demoded with reserved bits set + qint64 m_icaoFails; //!< How many frames we've demoded with reserved ICAO - ADSBDemodStats() : - m_correlatorMatches(0), - m_adsbFrames(0), - m_modesFrames(0), - m_crcFails(0), - m_typeFails(0), - m_demodTime(0.0), - m_feedTime(0.0) + ADSBDemodStats() { + reset(); + } + + void reset() + { + m_correlatorMatches = 0; + m_adsbFrames = 0; + m_modesFrames = 0; + m_preambleFails = 0; + m_crcFails = 0; + m_typeFails = 0; + m_invalidFails = 0; + m_icaoFails = 0; } };