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; } };