From 08306919299f1c70cd28482d4da14c3a33683d94 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Fri, 26 Aug 2022 12:35:12 +0100 Subject: [PATCH] ADS-B: Add support for decoding Comm-B replies in Mode-S frames. This adds TAS, IAS, Mach, Selelected Alt, Selected Heading, Turn rate, Roll angle, Autopilot enabled, Vertical and lateral navigation modes, Baro setting, Headwind, OAT, Wind speed and direction, Pressure, Static air temperature, Humidity. --- plugins/channelrx/demodadsb/adsbdemodgui.cpp | 1805 +++++++++++++---- plugins/channelrx/demodadsb/adsbdemodgui.h | 144 +- plugins/channelrx/demodadsb/adsbdemodgui.ui | 210 +- .../demodadsb/adsbdemodnotificationdialog.cpp | 4 +- plugins/channelrx/demodadsb/adsbdemodreport.h | 13 +- .../channelrx/demodadsb/adsbdemodsettings.cpp | 4 +- .../channelrx/demodadsb/adsbdemodsettings.h | 89 +- .../demodadsb/adsbdemodsinkworker.cpp | 29 +- plugins/channelrx/demodadsb/readme.md | 85 +- sdrbase/CMakeLists.txt | 2 + sdrbase/util/osndb.cpp | 25 + .../demodadsb => sdrbase/util}/osndb.h | 201 +- sdrbase/util/planespotters.cpp | 30 +- sdrbase/util/planespotters.h | 5 +- sdrbase/util/units.h | 10 + 15 files changed, 2155 insertions(+), 501 deletions(-) create mode 100644 sdrbase/util/osndb.cpp rename {plugins/channelrx/demodadsb => sdrbase/util}/osndb.h (67%) diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index 1fe3db93a..9a0b8ddb1 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -42,6 +42,7 @@ #include "channel/channelwebapiutils.h" #include "feature/featurewebapiutils.h" #include "plugin/pluginapi.h" +#include "util/crc.h" #include "util/simpleserializer.h" #include "util/db.h" #include "util/units.h" @@ -63,8 +64,69 @@ #include "adsb.h" #include "adsbosmtemplateserver.h" -const char *Aircraft::m_speedTypeNames[] = { - "GS", "TAS", "IAS" +const char ADSBDemodGUI::m_idMap[] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ############-##0123456789######"; + +const QString ADSBDemodGUI::m_categorySetA[] = { + QStringLiteral("None"), + QStringLiteral("Light"), + QStringLiteral("Small"), + QStringLiteral("Large"), + QStringLiteral("High vortex"), + QStringLiteral("Heavy"), + QStringLiteral("High performance"), + QStringLiteral("Rotorcraft") +}; + +const QString ADSBDemodGUI::m_categorySetB[] = { + QStringLiteral("None"), + QStringLiteral("Glider/sailplane"), + QStringLiteral("Lighter-than-air"), + QStringLiteral("Parachutist"), + QStringLiteral("Ultralight"), + QStringLiteral("Reserved"), + QStringLiteral("UAV"), + QStringLiteral("Space vehicle") +}; + +const QString ADSBDemodGUI::m_categorySetC[] = { + QStringLiteral("None"), + QStringLiteral("Emergency vehicle"), + QStringLiteral("Service vehicle"), + QStringLiteral("Ground obstruction"), + QStringLiteral("Cluster obstacle"), + QStringLiteral("Line obstacle"), + QStringLiteral("Reserved"), + QStringLiteral("Reserved") +}; + +const QString ADSBDemodGUI::m_emergencyStatus[] = { + QStringLiteral("No emergency"), + QStringLiteral("General emergency"), + QStringLiteral("Lifeguard/Medical"), + QStringLiteral("Minimum fuel"), + QStringLiteral("No communications"), + QStringLiteral("Unlawful interference"), + QStringLiteral("Downed aircraft"), + QStringLiteral("Reserved") +}; + +const QString ADSBDemodGUI::m_flightStatuses[] = { + QStringLiteral("Airbourne"), + QStringLiteral("On-ground"), + QStringLiteral("Alert, airboune"), + QStringLiteral("Alert, on-ground"), + QStringLiteral("Alert, SPI"), + QStringLiteral("SPI"), + QStringLiteral("Reserved"), + QStringLiteral("Not assigned") +}; + +const QString ADSBDemodGUI::m_hazardSeverity[] = { + "NIL", "Light", "Moderate", "Severe" +}; + +const QString ADSBDemodGUI::m_fomSources[] = { + "Invalid", "INS", "GNSS", "DME/DME", "DME/VOR", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved", "Reserved" }; ADSBDemodGUI* ADSBDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) @@ -227,12 +289,12 @@ QString Aircraft::getText(bool all) const } } } - if (m_speedValid) + if (m_groundspeedValid) { if (m_gui->useSIUints()) { - list.append(QString("%1: %2 (kph)").arg(m_speedTypeNames[m_speedType]).arg(Units::knotsToIntegerKPH(m_speed))); + list.append(QString("GS: %1 (kph)").arg(Units::knotsToIntegerKPH(m_groundspeed))); } else { - list.append(QString("%1: %2 (kn)").arg(m_speedTypeNames[m_speedType]).arg(m_speed)); + list.append(QString("GS: %1 (kn)").arg(m_groundspeed)); } } if (m_verticalRateValid) @@ -768,8 +830,8 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetOrientation(1); swgMapItem->setHeading(aircraft->m_heading); - swgMapItem->setPitch(aircraft->m_pitch); - swgMapItem->setRoll(aircraft->m_roll); + swgMapItem->setPitch(aircraft->m_pitchEst); + swgMapItem->setRoll(aircraft->m_rollEst); swgMapItem->setOrientationDateTime(new QString(aircraft->m_positionDateTime.toString(Qt::ISODateWithMs))); } else @@ -789,100 +851,6 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListadsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem); 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_SPEED, aircraft->m_speedItem); ui->adsbData->setItem(row, ADSB_COL_HEADING, aircraft->m_headingItem); ui->adsbData->setItem(row, ADSB_COL_VERTICALRATE, aircraft->m_verticalRateItem); ui->adsbData->setItem(row, ADSB_COL_RANGE, aircraft->m_rangeItem); @@ -939,6 +906,26 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) ui->adsbData->setItem(row, ADSB_COL_STA, aircraft->m_staItem); ui->adsbData->setItem(row, ADSB_COL_ETA, aircraft->m_etaItem); ui->adsbData->setItem(row, ADSB_COL_ATA, aircraft->m_ataItem); + ui->adsbData->setItem(row, ADSB_COL_SEL_ALTITUDE, aircraft->m_selAltitudeItem); + ui->adsbData->setItem(row, ADSB_COL_SEL_HEADING, aircraft->m_selHeadingItem); + ui->adsbData->setItem(row, ADSB_COL_BARO, aircraft->m_baroItem); + 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_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); + ui->adsbData->setItem(row, ADSB_COL_TRUE_AIRSPEED, aircraft->m_trueAirspeedItem); + ui->adsbData->setItem(row, ADSB_COL_INDICATED_AIRSPEED, aircraft->m_indicatedAirspeedItem); + ui->adsbData->setItem(row, ADSB_COL_MACH, aircraft->m_machItem); + ui->adsbData->setItem(row, ADSB_COL_HEADWIND, aircraft->m_headwindItem); + ui->adsbData->setItem(row, ADSB_COL_EST_AIR_TEMP, aircraft->m_estAirTempItem); + ui->adsbData->setItem(row, ADSB_COL_WIND_SPEED, aircraft->m_windSpeedItem); + ui->adsbData->setItem(row, ADSB_COL_WIND_DIR, aircraft->m_windDirItem); + 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) { @@ -955,11 +942,11 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) QIcon *icon = nullptr; if (aircraft->m_aircraftInfo->m_operatorICAO.size() > 0) { - aircraft->m_airlineIconURL = getAirlineIconPath(aircraft->m_aircraftInfo->m_operatorICAO); + aircraft->m_airlineIconURL = AircraftInformation::getAirlineIconPath(aircraft->m_aircraftInfo->m_operatorICAO); if (aircraft->m_airlineIconURL.startsWith(':')) { aircraft->m_airlineIconURL = "qrc://" + aircraft->m_airlineIconURL.mid(1); } - icon = getAirlineIcon(aircraft->m_aircraftInfo->m_operatorICAO); + icon = AircraftInformation::getAirlineIcon(aircraft->m_aircraftInfo->m_operatorICAO); if (icon != nullptr) { aircraft->m_airlineItem->setSizeHint(QSize(85, 20)); @@ -974,49 +961,16 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) aircraft->m_airlineItem->setText(aircraft->m_aircraftInfo->m_owner); } // Try loading a flag based on registration - if ((aircraft->m_aircraftInfo->m_registration.size() > 0) && (m_prefixMap != nullptr)) + if (aircraft->m_aircraftInfo->m_registration.size() > 0) { - QString flag; - int idx = aircraft->m_aircraftInfo->m_registration.indexOf('-'); - if (idx >= 0) - { - QString prefix; - - // Some countries use AA-A - try these first as first letters are common - prefix = aircraft->m_aircraftInfo->m_registration.left(idx + 2); - if (m_prefixMap->contains(prefix)) - flag = m_prefixMap->value(prefix); - else - { - // Try letters before '-' - prefix = aircraft->m_aircraftInfo->m_registration.left(idx); - if (m_prefixMap->contains(prefix)) - flag = m_prefixMap->value(prefix); - } - } - else - { - // No '-' Could be one of a few countries or military. - // See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes - if (aircraft->m_aircraftInfo->m_registration.startsWith("N")) { - flag = m_prefixMap->value("N"); // US - } else if (aircraft->m_aircraftInfo->m_registration.startsWith("JA")) { - flag = m_prefixMap->value("JA"); // Japan - } else if (aircraft->m_aircraftInfo->m_registration.startsWith("HL")) { - flag = m_prefixMap->value("HL"); // Korea - } else if (aircraft->m_aircraftInfo->m_registration.startsWith("YV")) { - flag = m_prefixMap->value("YV"); // Venezuela - } else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(aircraft->m_aircraftInfo->m_operator))) { - flag = m_militaryMap->value(aircraft->m_aircraftInfo->m_operator); - } - } + QString flag = aircraft->m_aircraftInfo->getFlag(); if (flag != "") { - aircraft->m_flagIconURL = getFlagIconPath(flag); + aircraft->m_flagIconURL = AircraftInformation::getFlagIconPath(flag); if (aircraft->m_flagIconURL.startsWith(':')) { aircraft->m_flagIconURL = "qrc://" + aircraft->m_flagIconURL.mid(1); } - icon = getFlagIcon(flag); + icon = AircraftInformation::getFlagIcon(flag); if (icon != nullptr) { aircraft->m_countryItem->setSizeHint(QSize(40, 20)); @@ -1049,71 +1003,122 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) return aircraft; } +// Try to map callsign to flight number +void ADSBDemodGUI::callsignToFlight(Aircraft *aircraft) +{ + if (!aircraft->m_callsign.isEmpty()) + { + QRegularExpression flightNoExp("^[A-Z]{2,3}[0-9]{1,4}$"); + // Airlines line BA add a single charater suffix that can be stripped + // If the suffix is two characters, then it typically means a digit + // has been replaced which I don't know how to guess + // E.g Easyjet might use callsign EZY67JQ for flight EZY6267 + // BA use BAW90BG for BA890 + QRegularExpression suffixedFlightNoExp("^([A-Z]{2,3})([0-9]{1,4})[A-Z]?$"); + QRegularExpressionMatch suffixMatch; + + if (flightNoExp.match(aircraft->m_callsign).hasMatch()) + { + aircraft->m_flight = aircraft->m_callsign; + } + else if ((suffixMatch = suffixedFlightNoExp.match(aircraft->m_callsign)).hasMatch()) + { + aircraft->m_flight = QString("%1%2").arg(suffixMatch.captured(1)).arg(suffixMatch.captured(2)); + } + else + { + // Don't guess, to save wasting API calls + aircraft->m_flight = ""; + } + } + else + { + aircraft->m_flight = ""; + } +} + +// ADS-B and Mode-S selected altitudes have slightly different precision, so values can jump around a bit +// And we end up with altitudes such as 38008 rather than 38000 +// To remove this, we round to nearest 50 feet +int ADSBDemodGUI::roundTo50Feet(int alt) +{ + return ((alt + 25) / 50) * 50; +} + +// Estimate outside air temperature (static temperature) from Mach number and true airspeed, assuming dry air +bool ADSBDemodGUI::calcAirTemp(Aircraft *aircraft) +{ + if (aircraft->m_machValid && aircraft->m_trueAirspeedValid) + { + // Calculate speed of sound + float c = Units::knotsToMetresPerSecond(aircraft->m_trueAirspeed) / aircraft->m_mach; + + // Calculate temperature, given the speed of sound + float a = c / 331.3f; + float T = (a * a - 1.0f) * 273.15f; + + aircraft->m_estAirTempItem->setData(Qt::DisplayRole, (int)std::round(T)); + + return true; + } + else + { + return false; + } +} + void ADSBDemodGUI::handleADSB( const QByteArray data, const QDateTime dateTime, float correlation, float correlationOnes, + unsigned crc, bool updateModel) { - const char idMap[] = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ############-##0123456789######"; - const QString categorySetA[] = { - QStringLiteral("None"), - QStringLiteral("Light"), - QStringLiteral("Small"), - QStringLiteral("Large"), - QStringLiteral("High vortex"), - QStringLiteral("Heavy"), - QStringLiteral("High performance"), - QStringLiteral("Rotorcraft") - }; - const QString categorySetB[] = { - QStringLiteral("None"), - QStringLiteral("Glider/sailplane"), - QStringLiteral("Lighter-than-air"), - QStringLiteral("Parachutist"), - QStringLiteral("Ultralight"), - QStringLiteral("Reserved"), - QStringLiteral("UAV"), - QStringLiteral("Space vehicle") - }; - const QString categorySetC[] = { - QStringLiteral("None"), - QStringLiteral("Emergency vehicle"), - QStringLiteral("Service vehicle"), - QStringLiteral("Ground obstruction"), - QStringLiteral("Cluster obstacle"), - QStringLiteral("Line obstacle"), - QStringLiteral("Reserved"), - QStringLiteral("Reserved") - }; - const QString emergencyStatus[] = { - QStringLiteral("No emergency"), - QStringLiteral("General emergency"), - QStringLiteral("Lifeguard/Medical"), - QStringLiteral("Minimum fuel"), - QStringLiteral("No communications"), - QStringLiteral("Unlawful interference"), - QStringLiteral("Downed aircraft"), - QStringLiteral("Reserved") - }; - 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 - unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); // ICAO aircraft address - int tc = (data[4] >> 3) & 0x1f; // Type code + unsigned icao; + Aircraft *aircraft; - Aircraft *aircraft = getAircraft(icao, newAircraft); + 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() << "Skiping Mode-S from unknown aircraft - DF " << df << " ICAO " << Qt::hex << icao; + return; + } + } + else + { + 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) + { + aircraft->m_tisBFrameCount++; + aircraft->m_tisBItem->setData(Qt::DisplayRole, aircraft->m_tisBFrameCount); + } if (correlation < aircraft->m_minCorrelation) aircraft->m_minCorrelation = correlation; @@ -1131,20 +1136,21 @@ void ADSBDemodGUI::handleADSB( .arg(CalcDb::dbPower(m_correlationOnesAvg.instantAverage()), 3, 'f', 1)); // ADS-B, non-transponder ADS-B or TIS-B rebroadcast of ADS-B (ADS-R) - if ((df == 17) || ((df == 18) && ((ca == 0) || (ca == 1) || (ca == 6)))) + if ((df == 17) || ((df == 18) && (ca != 4))) { + if ((tc >= 1) && ((tc <= 4))) { - // Aircraft identification + // Aircraft identification - BDS 0,8 int ec = data[4] & 0x7; // Emitter category QString prevEmitterCategory = aircraft->m_emitterCategory; if (tc == 4) { - aircraft->m_emitterCategory = categorySetA[ec]; + aircraft->m_emitterCategory = m_categorySetA[ec]; } else if (tc == 3) { - aircraft->m_emitterCategory = categorySetB[ec]; + aircraft->m_emitterCategory = m_categorySetB[ec]; } else if (tc == 2) { - aircraft->m_emitterCategory = categorySetC[ec]; + aircraft->m_emitterCategory = m_categorySetC[ec]; } else { aircraft->m_emitterCategory = QStringLiteral("Reserved"); } @@ -1163,44 +1169,16 @@ void ADSBDemodGUI::handleADSB( c[7] = (data[10] & 0x3f); // Map to ASCII for (int i = 0; i < 8; i++) - callsign[i] = idMap[c[i]]; + callsign[i] = m_idMap[c[i]]; callsign[8] = '\0'; QString callsignTrimmed = QString(callsign).trimmed(); updatedCallsign = aircraft->m_callsign != callsignTrimmed; - - aircraft->m_callsign = callsignTrimmed; - aircraft->m_callsignItem->setText(aircraft->m_callsign); - - // Attempt to map callsign to flight number - if (!aircraft->m_callsign.isEmpty()) + if (updatedCallsign) { - QRegularExpression flightNoExp("^[A-Z]{2,3}[0-9]{1,4}$"); - // Airlines line BA add a single charater suffix that can be stripped - // If the suffix is two characters, then it typically means a digit - // has been replaced which I don't know how to guess - // E.g Easyjet might use callsign EZY67JQ for flight EZY6267 - // BA use BAW90BG for BA890 - QRegularExpression suffixedFlightNoExp("^([A-Z]{2,3})([0-9]{1,4})[A-Z]?$"); - QRegularExpressionMatch suffixMatch; - - if (flightNoExp.match(aircraft->m_callsign).hasMatch()) - { - aircraft->m_flight = aircraft->m_callsign; - } - else if ((suffixMatch = suffixedFlightNoExp.match(aircraft->m_callsign)).hasMatch()) - { - aircraft->m_flight = QString("%1%2").arg(suffixMatch.captured(1)).arg(suffixMatch.captured(2)); - } - else - { - // Don't guess, to save wasting API calls - aircraft->m_flight = ""; - } - } - else - { - aircraft->m_flight = ""; + aircraft->m_callsign = callsignTrimmed; + aircraft->m_callsignItem->setText(aircraft->m_callsign); + callsignToFlight(aircraft); } // Select 3D model based on category, if we don't already have one based on ICAO @@ -1230,7 +1208,7 @@ void ADSBDemodGUI::handleADSB( if (aircraft->m_onSurface) { - // Surface position + // Surface position - BDS 0,6 // There are a few airports that are below 0 MSL // https://en.wikipedia.org/wiki/List_of_lowest_airports @@ -1244,15 +1222,15 @@ void ADSBDemodGUI::handleADSB( if (movement == 0) { // No information available - aircraft->m_speedValid = false; - aircraft->m_speedItem->setData(Qt::DisplayRole, ""); + aircraft->m_groundspeedValid = false; + aircraft->m_groundspeedItem->setData(Qt::DisplayRole, ""); } else if (movement == 1) { // Aircraft stopped - aircraft->m_speedValid = true; - aircraft->m_speedItem->setData(Qt::DisplayRole, 0); - aircraft->m_speed = 0.0; + aircraft->m_groundspeedValid = true; + aircraft->m_groundspeedItem->setData(Qt::DisplayRole, 0); + aircraft->m_groundspeed = 0.0; } else if ((movement >= 2) && (movement <= 123)) { @@ -1294,15 +1272,14 @@ void ADSBDemodGUI::handleADSB( step = 5.0f; adjust = 109; } - aircraft->m_speed = base + (movement - adjust) * step; - aircraft->m_speedType = Aircraft::GS; - aircraft->m_speedValid = true; - aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_speed) : (int)std::round(aircraft->m_speed)); + 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)); } else if (movement == 124) { - aircraft->m_speedValid = true; - aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? 324 : 175); // Actually greater than this + aircraft->m_groundspeedValid = true; + aircraft->m_groundspeedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? 324 : 175); // Actually greater than this } int groundTrackStatus = (data[5] >> 3) & 1; @@ -1327,30 +1304,7 @@ void ADSBDemodGUI::handleADSB( } else { - // https://en.wikipedia.org/wiki/Gillham_code - 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; - } - - alt_ft = -1200 + n500*500 + n100*100; + alt_ft = gillhamToFeet(n); } aircraft->m_altitude = alt_ft; @@ -1499,7 +1453,7 @@ void ADSBDemodGUI::handleADSB( } else if (tc == 19) { - // Airbourne velocity + // Airbourne velocity - BDS 0,9 int st = data[4] & 0x7; // Subtype if ((st == 1) || (st == 2)) { @@ -1530,19 +1484,18 @@ void ADSBDemodGUI::handleADSB( h += 360.0; } - aircraft->m_heading = h; + aircraft->m_heading = h; // This is actually track, rather than heading aircraft->m_headingValid = true; aircraft->m_headingDateTime = dateTime; - aircraft->m_speed = v; - aircraft->m_speedType = Aircraft::GS; - aircraft->m_speedValid = true; + aircraft->m_groundspeed = v; + aircraft->m_groundspeedValid = true; aircraft->m_headingItem->setData(Qt::DisplayRole, std::round(aircraft->m_heading)); - aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_speed) : aircraft->m_speed); + aircraft->m_groundspeedItem->setData(Qt::DisplayRole,m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_groundspeed) : aircraft->m_groundspeed); aircraft->m_orientationDateTime = dateTime; } else { - // Airspeed + // 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) @@ -1557,11 +1510,20 @@ void ADSBDemodGUI::handleADSB( int as_t = (data[7] >> 7) & 1; // Airspeed type int as = ((data[7] & 0x7f) << 3) | ((data[8] >> 5) & 0x7); // Airspeed - aircraft->m_speed = as; - aircraft->m_speedType = as_t ? Aircraft::IAS : Aircraft::TAS; - aircraft->m_speedValid = true; - aircraft->m_speedItem->setData(Qt::DisplayRole, m_settings.m_siUnits ? Units::knotsToIntegerKPH(aircraft->m_speed) : aircraft->m_speed); + 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); + } } + //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); @@ -1573,20 +1535,15 @@ void ADSBDemodGUI::handleADSB( } else if (tc == 28) { - // Aircraft status + // Aircraft status - BDS 6,1 int st = data[4] & 0x7; // Subtype if (st == 1) { int es = (data[5] >> 5) & 0x7; // Emergency state int modeA = ((data[5] << 8) & 0x1f00) | (data[6] & 0xff); // Mode-A code (squawk) - aircraft->m_status = emergencyStatus[es]; + aircraft->m_status = m_emergencyStatus[es]; aircraft->m_statusItem->setText(aircraft->m_status); - int a, b, c, d; - c = ((modeA >> 12) & 1) | ((modeA >> (10-1)) & 0x2) | ((modeA >> (8-2)) & 0x4); - a = ((modeA >> 11) & 1) | ((modeA >> (9-1)) & 0x2) | ((modeA >> (7-2)) & 0x4); - b = ((modeA >> 5) & 1) | ((modeA >> (3-1)) & 0x2) | ((modeA << (1)) & 0x4); - d = ((modeA >> 4) & 1) | ((modeA >> (2-1)) & 0x2) | ((modeA << (2)) & 0x4); - aircraft->m_squawk = a*1000 + b*100 + c*10 + d; + aircraft->m_squawk = squawkDecode(modeA); if (modeA & 0x40) aircraft->m_squawkItem->setText(QString("%1 IDENT").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0'))); else @@ -1600,6 +1557,76 @@ void ADSBDemodGUI::handleADSB( else if (tc == 29) { // Target state and status + bool selAltitudeType = (data[5] >> 7) & 0x1; + int selAltitudeFix = ((data[5] & 0x7f) << 4) | ((data[6] >> 4) & 0xf); + if (selAltitudeFix != 0) + { + int selAltitude = (selAltitudeFix - 1) * 32; // Ft + aircraft->m_selAltitude = selAltitude; + aircraft->m_selAltitudeValid = true; + if (m_settings.m_siUnits) { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, Units::feetToIntegerMetres(aircraft->m_selAltitude)); + } else { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, roundTo50Feet(aircraft->m_selAltitude)); + } + } + int baroFix = ((data[6] & 0xf) << 5) | ((data[7] >> 3) & 0x1f); + if (baroFix != 0) + { + float baro = (baroFix - 1) * 0.8f + 800.0f; // mb + aircraft->m_baro = baro; + aircraft->m_baroValid = true; + aircraft->m_baroItem->setData(Qt::DisplayRole, std::round(aircraft->m_baro)); + } + bool selHeadingValid = (data[7] >> 2) & 0x1; + if (selHeadingValid) + { + int selHeadingFix = ((data[7] & 0x3) << 7) | ((data[8] >> 1) & 0x7f); + selHeadingFix = (selHeadingFix << 23) >> 23; + float selHeading = selHeadingFix * 180.0f / 256.0f; + if (selHeading < 0.0f) { + selHeading += 360.0f; + } + aircraft->m_selHeading = selHeading; + aircraft->m_selHeadingValid = true; + aircraft->m_selHeadingItem->setData(Qt::DisplayRole, std::round(aircraft->m_selHeading)); + } + + bool modeValid = (data[9] >> 1) & 0x1; + if (modeValid) + { + bool autoPilot = data[9] & 0x1; + bool vnavMode = (data[10] >> 7) & 0x1; + bool altHoldMode = (data[10] >> 6) & 0x1; + bool approachMode = (data[10] >> 4) & 0x1; + bool lnavMode = (data[10] >> 2) & 0x1; + + aircraft->m_apItem->setText(autoPilot ? QChar(0x2713) : QChar(0x2717)); // Tick or cross + + QString vMode = ""; + if (vnavMode) { + vMode = vMode + "VNAV "; + } + if (altHoldMode) { + vMode = vMode + "HOLD "; + } + if (approachMode) { + vMode = vMode + "APP "; + } + vMode = vMode.trimmed(); + aircraft->m_vModeItem->setText(vMode); + + QString lMode = ""; + if (lnavMode) { + lMode = lMode + "LNAV "; + } + if (approachMode) { + lMode = lMode + "APP "; + } + lMode = lMode.trimmed(); + aircraft->m_lModeItem->setText(lMode); + } + } else if (tc == 31) { @@ -1633,8 +1660,17 @@ void ADSBDemodGUI::handleADSB( } else if (df == 18) { - // TIS-B - qDebug() << "TIS B message cf=" << ca << " icao: " << icao; + // TIS-B that doesn't match ADS-B formats, such as TIS-B management + qDebug() << "TIS B message cf=" << ca << " icao: " << QString::number(icao, 16); + } + else if ((df == 4) || (df == 5)) + { + decodeModeS(data, dateTime, df, aircraft); + } + else if ((df == 20) || (df == 21)) + { + decodeModeS(data, dateTime, df, aircraft); + decodeCommB(data, dateTime, df, aircraft, updatedCallsign); } // Check to see if we need to emit a notification about this aircraft @@ -1646,6 +1682,968 @@ void ADSBDemodGUI::handleADSB( } } +void ADSBDemodGUI::decodeModeS(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft) +{ + bool wasOnSurface = aircraft->m_onSurface; + bool takenOff = false; + + int flightStatus = data[0] & 0x7; + if ((flightStatus == 0) || (flightStatus == 2)) + { + takenOff = wasOnSurface; + aircraft->m_onSurface = false; + } + else if ((flightStatus == 1) || (flightStatus == 3)) + { + aircraft->m_onSurface = true; + } + if (wasOnSurface != aircraft->m_onSurface) + { + // Can't mix CPR values used on surface and those that are airbourne + 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); + + // 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 + if (takenOff) + { + aircraft->m_runwayAltitude = aircraft->m_altitude; + aircraft->m_runwayAltitudeValid = true; + } + } + else if ((df == 5) || (df == 21)) + { + // Squawk ident code + int identCode = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); + int squawk = squawkDecode(identCode); + + if (squawk != aircraft->m_squawk) + { + aircraft->m_squawk = squawk; + if (identCode & 0x40) { + aircraft->m_squawkItem->setText(QString("%1 IDENT").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0'))); + } else { + aircraft->m_squawkItem->setText(QString("%1").arg(aircraft->m_squawk, 4, 10, QLatin1Char('0'))); + } + } + } +} + +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 posibilities + // and then see which have legal values and values that are consistent with ADS-B data + + // All IFR aircraft should support ELS (Elementary Surveillance) which includes BDS 1,0 1,7 2,0 3,0 + // Large aircraft are also required to support EHS (Enhanced Surveillance) which adds BDS 4,0 5,0 6,0 + // There is also MRAR (Meteorological Routine Air Report) BDS 4,4 4,5, but only a small % of aircraft support this + // See: https://www.icao.int/APAC/Documents/edocs/Mode%20S%20DAPs%20Implementation%20and%20Operations%20Guidance%20Document3.0.pdf + + // I've implemented a few extra BDSes, but these are rarely seen. + // Figure 2 in "Mode S Transponder Comm-B Capabilities in Current Operational Aircraft" gives a breakdown of what capabilities are reported by BDS 1,7: + // https://www.researchgate.net/publication/346521949_Mode_S_Transponder_Comm-B_Capabilities_in_Current_Operational_Aircraft/link/5fc60b794585152e9be8571c/download + + // Skip messages that are all zeros + if (data[4] || data[5] || data[6] || data[7] || data[8] || data[9] || data[10]) + { + + const int maxWind = 250; // Maximum expected head/tail wind in knts + const int maxSpeedDiff = 50; // Maximum speed difference we allow before we assume message is inconsistent + const int maxAlt = 46000; // Maximum expected altitude for commercial jet + const float maxHeadingDiff = 20.0f; // Maximum difference in heading/track + + // BDS 1,0 - ELS + + bool bds_1_0 = (data[4] == 0x10) && ((data[5] & 0x7c) == 0x00); + + // BDS 1,7 - Common usage GICB capability report - ELS + + bool cap_0_5 = (data[4] >> 7) & 0x1; + bool cap_0_6 = (data[4] >> 6) & 0x1; + bool cap_0_7 = (data[4] >> 5) & 0x1; + bool cap_0_8 = (data[4] >> 4) & 0x1; + bool cap_0_9 = (data[4] >> 3) & 0x1; + bool cap_0_a = (data[4] >> 2) & 0x1; + bool cap_2_0 = (data[4] >> 1) & 0x1; + bool cap_2_1 = (data[4] >> 0) & 0x1; + + bool cap_4_0 = (data[5] >> 7) & 0x1; + bool cap_4_1 = (data[5] >> 6) & 0x1; + bool cap_4_2 = (data[5] >> 5) & 0x1; + bool cap_4_3 = (data[5] >> 4) & 0x1; + bool cap_4_4 = (data[5] >> 3) & 0x1; + bool cap_4_5 = (data[5] >> 2) & 0x1; + bool cap_4_8 = (data[5] >> 1) & 0x1; + bool cap_5_0 = (data[5] >> 0) & 0x1; + + bool cap_5_1 = (data[6] >> 7) & 0x1; + bool cap_5_2 = (data[6] >> 6) & 0x1; + bool cap_5_3 = (data[6] >> 5) & 0x1; + bool cap_5_4 = (data[6] >> 4) & 0x1; + bool cap_5_5 = (data[6] >> 3) & 0x1; + bool cap_5_6 = (data[6] >> 2) & 0x1; + bool cap_5_f = (data[6] >> 1) & 0x1; + bool cap_6_0 = (data[6] >> 0) & 0x1; + + QStringList caps; + if (cap_0_5) { + caps.append("0,5"); + } + if (cap_0_6) { + caps.append("0,6"); + } + if (cap_0_7) { + caps.append("0,7"); + } + if (cap_0_8) { + caps.append("0,8"); + } + if (cap_0_9) { + caps.append("0,9"); + } + if (cap_0_a) { + caps.append("0,A"); + } + if (cap_2_0) { + caps.append("2,0"); + } + if (cap_2_1) { + caps.append("2,1"); + } + if (cap_4_0) { + caps.append("4,0"); + } + if (cap_4_1) { + caps.append("4,1"); + } + if (cap_4_2) { + caps.append("4,2"); + } + if (cap_4_3) { + caps.append("4,3"); + } + if (cap_4_4) { + caps.append("4,4"); + } + if (cap_4_5) { + caps.append("4,5"); + } + if (cap_4_8) { + caps.append("4,8"); + } + if (cap_5_0) { + caps.append("5,0"); + } + if (cap_5_1) { + caps.append("5,1"); + } + if (cap_5_2) { + caps.append("5,2"); + } + if (cap_5_3) { + caps.append("5,3"); + } + if (cap_5_4) { + caps.append("5,4"); + } + if (cap_5_5) { + caps.append("5,5"); + } + if (cap_5_6) { + caps.append("5,6"); + } + if (cap_5_f) { + caps.append("5,F"); + } + if (cap_5_5) { + caps.append("6,0"); + } + + bool bds_1_7 = cap_2_0 && (data[7] == 0x0) && (data[8] == 0x0) && (data[9] == 0x0) && (data[10] == 0x0); + + // BDS 1,8 1,9 1,A 1,B 1,C 1,D 1,E 1,F Mode S specific services + + // Apart from 1,C and 1,F these can have any bits set/clear and specify BDS code capabilities + // Don't decode for now + + // BDS 2,0 - Aircraft identification - ELS + + // Flight/callsign - Extract 8 6-bit characters from 6 8-bit bytes, MSB first + unsigned char c[9]; + 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(); + bool invalidCallsign = QString(callsign).contains('#'); + bool bds_2_0 = (data[4] == 0x20) && !invalidCallsign; + + // BDS 2,1 - Aircraft and airline registration markings + + bool aircraftRegistrationStatus = (data[4] >> 7) & 0x1; + char aircraftRegistration[8]; + c[0] = (data[4] >> 1) & 0x3f; + c[1] = ((data[4] & 0x1) << 5) | ((data[5] >> 3) & 0x1f); + c[2] = ((data[5] & 0x7) << 3) | ((data[6] >> 5) & 0x7); + c[3] = ((data[6] & 0x1f) << 1) | ((data[7] >> 7) & 0x1); + c[4] = ((data[7] >> 1) & 0x1f); + c[5] = ((data[7] & 0x1) >> 3) | ((data[8] >> 3) & 0x1f); + c[6] = ((data[8] & 0x7) << 3) | ((data[9] >> 5) & 0x7); + // Map to ASCII + for (int i = 0; i < 7; i++) { + aircraftRegistration[i] = m_idMap[c[i]]; + } + aircraftRegistration[7] = '\0'; + QString aircraftRegistrationString = QString(aircraftRegistration).trimmed(); + bool aircraftRegistrationInvalid = QString(aircraftRegistrationString).contains('#'); + bool aircraftRegistrationInconsistent = !aircraftRegistrationStatus && (c[0] || c[1] || c[2] || c[3] || c[4] || c[5] || c[6]); + + bool airlineRegistrationStatus = (data[9] >> 4) & 0x1; + char airlineRegistration[3]; + c[0] = ((data[9] & 0xf) << 2) | ((data[10] >> 6) & 0x3); + c[1] = data[10] & 0x3f; + // Map to ASCII + for (int i = 0; i < 2; i++) { + airlineRegistration[i] = m_idMap[c[i]]; + } + airlineRegistration[2] = '\0'; + QString airlineRegistrationString = QString(airlineRegistration).trimmed(); + bool airlineRegistrationInvalid = QString(airlineRegistrationStatus).contains('#') || !QChar::isLetter(c[0]) || !QChar::isLetter(c[1]); + bool airlineRegistrationInconsistent = !airlineRegistrationStatus && (c[0] || c[1]); + + bool bds_2_1 = !aircraftRegistrationInvalid && !aircraftRegistrationInconsistent && !airlineRegistrationInvalid && !airlineRegistrationInconsistent; + + // BDS 3,0 - ACAS active resolution advisory - ELS + + int acas = data[6] & 0x7f; + int threatType = (data[7] >> 2) & 0x3; + bool bds_3_0 = (data[4] == 0x30) && (acas < 48) && (threatType != 3); + + // BDS 4,0 - Selected vertical information - EHS + + bool mcpSelectedAltStatus = (data[4] >> 7) & 0x1; + int mcpSelectedAltFix = ((data[4] & 0x7f) << 5) | ((data[5] >> 3) & 0x1f); + int mcpSelectedAlt = mcpSelectedAltFix * 16; // ft + 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 fmsSelectedAlt = fmsSelectedAltFix * 16; // ft + bool fmsSelectedAltInconsistent = (fmsSelectedAlt > maxAlt) || (!fmsSelectedAltStatus && (fmsSelectedAltFix != 0)); + + bool baroSettingStatus = (data[7] >> 5) & 0x1; + int baroSettingFix = ((data[7] & 0x1f) << 7) | ((data[8] >> 1) & 0x7f); + float baroSetting = baroSettingFix * 0.1f + 800.0f; // mb + bool baroSettingIncosistent = !baroSettingStatus && (baroSettingFix != 0); + + bool modeStatus = data[9] & 0x1; + bool vnavMode = (data[10] >> 7) & 0x1; + bool altHoldMode = (data[10] >> 6) & 0x1; + bool approachMode = (data[10] >> 5) & 0x1; + bool modeInconsistent = !modeStatus && (vnavMode || altHoldMode || approachMode); + + bool targetAltSourceStatus = (data[10] >> 2) & 0x1; + int targetAltSource = data[10] & 0x3; + bool targetAltSourceInconsistent = !targetAltSourceStatus && (targetAltSource != 0); + + bool bds_4_0 = ((data[8] & 0x01) == 0x0) && ((data[9] & 0xfe) == 0x0) && ((data[10] & 0x18) == 0x0) + && !mcpSelectedAltInconsistent && !fmsSelectedAltInconsistent && !baroSettingIncosistent && !modeInconsistent && !targetAltSourceInconsistent; + + // BDS 4,1 - Next waypoint + + bool waypointStatus = (data[4] >> 7) & 0x1; + char waypoint[10]; + c[0] = (data[4] >> 1) & 0x3f; + c[1] = ((data[4] & 0x1) << 5) | ((data[5] >> 3) & 0x1f); + c[2] = ((data[5] & 0x7) << 3) | ((data[6] >> 5) & 0x7); + c[3] = ((data[6] & 0x1f) << 1) | ((data[7] >> 7) & 0x1); + c[4] = ((data[7] >> 1) & 0x1f); + c[5] = ((data[7] & 0x1) >> 3) | ((data[8] >> 3) & 0x1f); + c[6] = ((data[8] & 0x7) << 3) | ((data[9] >> 5) & 0x7); + c[7] = ((data[9] & 0x1f) << 1) | ((data[10] >> 7) & 0x1); + c[8] = ((data[10] >> 1) & 0x3f); + // Map to ASCII + for (int i = 0; i < 9; i++) { + waypoint[i] = m_idMap[c[i]]; + } + waypoint[9] = '\0'; + QString waypointString = QString(waypoint).trimmed(); + bool waypointInvalid = QString(waypointString).contains('#'); + bool waypointInconsistent = (waypointString.size() != 5) // Most navaid waypoints are 5 characters, and this prevents some incorrect matches, but is it correct? Need examples. + || (!waypointStatus && (c[0] || c[1] || c[2] || c[3] || c[4] || c[5] || c[6] || c[7] || c[8])); + + bool bds_4_1 = !waypointInvalid && !waypointInconsistent; + + // BDS 4,4 - Meteorological routine air report - MRAR - (Appendix E suggests a slightly different format in the future) + + int fomSource = (data[4] >> 4) & 0xf; + bool fomSourceInconsistent = fomSource > 4; + + bool windSpeedStatus = (data[4] >> 3) & 0x1; + int windSpeedFix = ((data[4] & 0x7) << 6) | ((data[5] >> 2) & 0x3f); + int windSpeed = windSpeedFix; // knots + int windDirectionFix = ((data[5] & 0x3) << 6) | ((data[6] >> 2) & 0x3f); + int windDirection = windDirectionFix * 180.0f / 256.0f; // Degreees + bool windSpeedInconsistent = (windSpeed > 250.0f) || (!windSpeedStatus && ((windSpeedFix != 0) || (windDirectionFix != 0))); + + int staticAirTemperatureFix = ((data[6] & 0x1) << 10) | ((data[7] & 0xff) << 2) | ((data[8] >> 6) & 0x3); + staticAirTemperatureFix = (staticAirTemperatureFix << 21) >> 21; + float staticAirTemperature = staticAirTemperatureFix * 0.25f; + bool staticAirTemperatureInconsistent = (staticAirTemperature < -80.0f) || (staticAirTemperature > 60.0f); + + bool averageStaticPressureStatus = (data[8] >> 5) & 0x1; + int averageStaticPressureFix = ((data[8] & 0x1f) << 6) | ((data[9] >> 2) & 0x3f); + int averageStaticPressure = averageStaticPressureFix; // hPa + bool averageStaticPressureInconsistent = !averageStaticPressureStatus && (averageStaticPressureFix != 0); + + bool turbulenceStatus = (data[9] >> 1) & 0x1; + int turbulence = ((data[9] & 0x1) << 1) | ((data[10] >> 7) & 0x1); + bool turbulenceInconsistent = !turbulenceStatus && (turbulence != 0); + + bool humidityStatus = (data[10] >> 6) & 0x1; + int humidityFix = data[10] & 0x3f; + float humidity = humidityFix * 100.0f / 64.0f; // % + 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 bds_4_4 = !noMetData && !fomSourceInconsistent && !windSpeedInconsistent && !staticAirTemperatureInconsistent && !averageStaticPressureInconsistent && !turbulenceInconsistent && !humidityInconsistent; + + // BDS 4,5 - Meteorological hazard report - MRAR - (Appendix E suggests a slightly different format in the future) + + bool hazardTurbulenceStatus = (data[4] >> 7) & 0x1; + int hazardTurbulence = (data[4] >> 5) & 0x3; + bool hazardTurbulenceInconsistent = !hazardTurbulenceStatus && (hazardTurbulence != 0); + + bool hazardWindShearStatus = (data[4] >> 4) & 0x1; + int hazardWindShear = (data[4] >> 2) & 0x3; + bool hazardWindShearInconsistent = !hazardWindShearStatus && (hazardWindShear != 0); + + bool hazardMicroburstStatus = (data[4] >> 1) & 0x1; + int hazardMicroburst = ((data[4] & 0x1) << 1) || ((data[5] >> 7) & 0x1); + bool hazardMicroburstInconsistent = !hazardMicroburstStatus && (hazardMicroburst != 0); + + bool hazardIcingStatus = (data[5] >> 6) & 0x1; + int hazardIcing = ((data[5] >> 4) & 0x3); + bool hazardIcingInconsistent = !hazardIcingStatus && (hazardIcing != 0); + + bool hazardWakeVortexStatus = (data[5] >> 3) & 0x1; + int hazardWakeVortex = ((data[5] >> 1) & 0x3); + bool hazardWakeVortexInconsistent = !hazardWakeVortexStatus && (hazardWakeVortex != 0); + + bool hazardStaticAirTemperatureStatus = data[5] & 0x1; + int hazardStaticAirTemperatureFix = ((data[6] & 0xff) << 2) | ((data[7] >> 6) & 0x3); + hazardStaticAirTemperatureFix = (hazardStaticAirTemperatureFix << 22) >> 22; + float hazardStaticAirTemperature = hazardStaticAirTemperatureFix * 0.25f; // deg C + bool hazardStaticAirTemperatureInconsistent = (hazardStaticAirTemperature < -80.0f) || (hazardStaticAirTemperature > 60.0f) || (!hazardStaticAirTemperatureStatus && (hazardStaticAirTemperatureFix != 0)); + + bool hazardAverageStaticPressureStatus = (data[7] >> 5) & 0x1; + int hazardAverageStaticPressureFix = ((data[7] & 0x1f) << 6) | ((data[8] >> 2) & 0x3f); + int hazardAverageStaticPressure = hazardAverageStaticPressureFix; // hPa + bool hazardAverageStaticPressureInconsistent = !hazardAverageStaticPressureStatus && (hazardAverageStaticPressureFix != 0); + + bool hazardRadioHeightStatus = (data[8] >> 1) & 0x1; + int hazardRadioHeightFix = ((data[8] & 0x1) << 11) | ((data[9] & 0xff) << 3) | ((data[10] >> 5) & 0x7); + int hazardRadioHeight = hazardRadioHeightFix * 16; // Ft + bool hazardRadioHeightInconsistent = (hazardRadioHeight > maxAlt) || (!aircraft->m_onSurface && (hazardRadioHeight == 0)) || (!hazardRadioHeightStatus && (hazardRadioHeightFix != 0)); + + bool hazardReserveredInconsistent = (data[10] & 0x1f) != 0; + + bool harzardIcingTempInconsistent = hazardIcingStatus && hazardStaticAirTemperatureStatus && (hazardIcing != 0) && ((hazardStaticAirTemperature >= 20.0f) || (hazardStaticAirTemperature <= -40.0f)); + + bool bds_4_5 = !hazardTurbulenceInconsistent && !hazardWindShearInconsistent && !hazardMicroburstInconsistent && !hazardIcingInconsistent + && !hazardWakeVortexInconsistent && !hazardStaticAirTemperatureInconsistent && !hazardAverageStaticPressureInconsistent + && !hazardRadioHeightInconsistent && !hazardReserveredInconsistent && !harzardIcingTempInconsistent; + + // BDS 5,0 - Track and turn report - EHS + + bool rollAngleStatus = (data[4] >> 7) & 0x1; + int rollAngleFix = ((data[4] & 0x7f) << 3) | ((data[5] >> 5) & 0x7); + rollAngleFix = (rollAngleFix << 22) >> 22; + float rollAngle = rollAngleFix * (45.0f/256.0f); + bool rollAngleInvalid = (rollAngle < -50.0f) || (rollAngle > 50.0f); // More than 50 deg bank unlikely for airliners + bool rollAngleInconsistent = ((abs(rollAngle) >= 1.0f) && aircraft->m_onSurface) || (!rollAngleStatus && (rollAngleFix != 0)); + + bool trueTrackAngleStatus = (data[5] >> 4) & 0x1; + int trueTrackAngleFix = ((data[5] & 0xf) << 7) | ((data[6] >> 1) & 0x7f); + trueTrackAngleFix = (trueTrackAngleFix << 21) >> 21; + float trueTrackAngle = trueTrackAngleFix * (90.0f/512.0f); + if (trueTrackAngle < 0.0f) { + trueTrackAngle += 360.0f; + } + bool trueTrackAngleInconsistent = (aircraft->m_headingValid && (abs(trueTrackAngle - aircraft->m_heading) > maxHeadingDiff)) + || (!trueTrackAngleStatus && (trueTrackAngleFix != 0)); + + bool groundSpeedStatus = data[6] & 0x1; + int groundSpeedFix = ((data[7] & 0xff) << 2) | ((data[8] >> 6) & 0x3); + int groundSpeed = groundSpeedFix * 2; + bool groundSpeedInconsistent = ((groundSpeed > 800) && (aircraft->m_emitterCategory != "High performance")) + || (aircraft->m_groundspeedValid && (abs(aircraft->m_groundspeed - groundSpeed) > maxSpeedDiff)) + || (!groundSpeedStatus && (groundSpeedFix != 0)); + + bool trackAngleRateStatus = (data[8] >> 5) & 0x1; + int trackAngleRateFix = ((data[8] & 0x1f) << 5) | ((data[9] >> 3) & 0x1f); + trackAngleRateFix = (trackAngleRateFix << 22) >> 22; + float trackAngleRate = trackAngleRateFix * (8.0f/256.0f); + bool trackAngleRateInconsistent = !trackAngleRateStatus && (trackAngleRateFix != 0); + + bool trueAirspeedStatus = (data[9] >> 2) & 0x1; + int trueAirspeedFix = ((data[9] & 0x3) << 8) | (data[10] & 0xff); + int trueAirspeed = trueAirspeedFix * 2; + bool trueAirspeedInconsistent = ((trueAirspeed > 575) && (aircraft->m_emitterCategory != "High performance")) + || (aircraft->m_groundspeedValid && (abs(aircraft->m_groundspeed - trueAirspeed) > maxWind+maxSpeedDiff)) + || (!trueAirspeedStatus && (trueAirspeedFix != 0)); + + bool headwindStatus = groundSpeedStatus && trueAirspeedStatus; + int headwind = trueAirspeed - groundSpeed; + bool headwindInconsistent = abs(headwind) > maxWind; + + bool bds_5_0 = !rollAngleInvalid && (!rollAngleInconsistent && !trueTrackAngleInconsistent && !groundSpeedInconsistent && !trackAngleRateInconsistent && !trueAirspeedInconsistent && !headwindInconsistent); + + // BDS 5,1 - Position report coarse + + bool positionValid = (data[4] >> 7) & 0x1; + + 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); + + 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); + + bool positionInconsistent = !aircraft->m_positionValid + || (positionValid && aircraft->m_positionValid && ((abs(latitude - aircraft->m_latitude > 2.0f)) || (abs(longitude - aircraft->m_longitude) > 2.0f))) + || (!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)) + || (!positionValid && (pressureAltFix != 0)); + + bool bds_5_1 = !positionInconsistent && !pressureAltInconsistent; + + // BDS 5,3 - Air-referenced state vector + + bool arMagHeadingStatus = (data[4] >> 7) & 1; + int arMagHeadingFix = ((data[4] & 0x7f) << 4) | ((data[5] >> 4) & 0xf); + float arMagHeading = arMagHeadingFix * 90.0f / 512.0f; + bool arMagHeadingInconsistent = (aircraft->m_headingValid && arMagHeadingStatus && (abs(aircraft->m_heading - arMagHeading) > maxHeadingDiff)) + || (!arMagHeadingStatus && (arMagHeadingFix != 0)); + + bool arIndicatedAirspeedStatus = (data[5] >> 3) & 0x1; + int arIndicatedAirspeedFix = ((data[5] & 0x7) << 7) | ((data[6] >> 1) & 0x7f); + int arIndicatedAirspeed = arIndicatedAirspeedFix; // knots + bool arIndicatedAirspeedInconsistent = ((arIndicatedAirspeed > 550) && (aircraft->m_emitterCategory != "High performance")) + || (aircraft->m_groundspeedValid && (abs(aircraft->m_groundspeed - arIndicatedAirspeed) > maxWind+maxSpeedDiff)) + || (aircraft->m_indicatedAirspeedValid && (abs(aircraft->m_indicatedAirspeed - arIndicatedAirspeed) > maxSpeedDiff)) + || (arIndicatedAirspeedStatus && (arIndicatedAirspeed < 100) && !aircraft->m_onSurface && (aircraft->m_emitterCategory != "Light")) + || (!arIndicatedAirspeedStatus && (arIndicatedAirspeedFix != 0)); + + bool arMachStatus = data[6] & 0x1; + int arMachFix = ((data[7] & 0xff) << 1) | ((data[8] >> 7) & 0x1); + float arMach = arMachFix * 0.008f; + bool arMachInconsistent = ((arMach >= 1.0f) && (aircraft->m_emitterCategory != "High performance")) + || (!arMachStatus && (arMachFix != 0)); + + bool arTrueAirspeedStatus = (data[8] >> 6) & 0x1; + int arTureAirspeedFix = ((data[8] & 0x3f) << 6) | ((data[9] >> 2) & 0x3f); + float arTrueAirspeed = arTureAirspeedFix * 0.5f; // knots + bool arTrueAirspeedInconsistent = ((arTrueAirspeed > 550) && (aircraft->m_emitterCategory != "High performance")) + || (aircraft->m_groundspeedValid && (abs(aircraft->m_groundspeed - arTrueAirspeed) > maxWind+maxSpeedDiff)) + || (aircraft->m_trueAirspeedValid && (abs(aircraft->m_trueAirspeed - arTrueAirspeed) > maxSpeedDiff)) + || (arTrueAirspeedStatus && (arTureAirspeedFix < 100) && !aircraft->m_onSurface && (aircraft->m_emitterCategory != "Light")) + || (!arTrueAirspeedStatus && (arTureAirspeedFix != 0)); + + bool arAltitudeRateStatus = (data[9] >> 1) & 0x1; + int arAltitudeRateFix = ((data[9] & 0x1) << 8) | (data[10] & 0xff); + int arAltitudeRate = arAltitudeRateFix * 64; // Ft/min + bool arAltitudeRateInconsistent = (abs(arAltitudeRate) > 6000) || (!arAltitudeRateStatus && (arAltitudeRateFix != 0)); + + bool bds_5_3 = !arMagHeadingInconsistent && !arIndicatedAirspeedInconsistent && !arMachInconsistent && !arTrueAirspeedInconsistent; + + // BDS 6,0 - Heading and speed report - EHS + + bool magHeadingStatus = (data[4] >> 7) & 1; + int magHeadingFix = ((data[4] & 0x7f) << 4) | ((data[6] >> 4) & 0xf); + magHeadingFix = (magHeadingFix << 21) >> 21; + float magHeading = magHeadingFix * (90.0f / 512.0f); + if (magHeading < 0.0f) { + magHeading += 360.f; + } + bool magHeadingInconsistent = (aircraft->m_headingValid && magHeadingStatus && (abs(aircraft->m_heading - magHeading) > maxHeadingDiff)) + || (!magHeadingStatus && (magHeadingFix != 0)); + + bool indicatedAirspeedStatus = (data[5] >> 3) & 0x1; + int indicatedAirspeedFix = ((data[5] & 0x7) << 7) | ((data[6] >> 1) & 0x7f); + int indicatedAirspeed = indicatedAirspeedFix; + bool indicatedAirspeedInconsistent = ((indicatedAirspeed > 550) && (aircraft->m_emitterCategory != "High performance")) + || (aircraft->m_groundspeedValid && (abs(aircraft->m_groundspeed - indicatedAirspeed) > maxWind+maxSpeedDiff)) + || (aircraft->m_indicatedAirspeedValid && (abs(aircraft->m_indicatedAirspeed - indicatedAirspeed) > maxSpeedDiff)) + || (indicatedAirspeedStatus && (indicatedAirspeed < 100) && !aircraft->m_onSurface && (aircraft->m_emitterCategory != "Light")) + || (!indicatedAirspeedStatus && (indicatedAirspeedFix != 0)); + + bool machStatus = data[6] & 0x1; + int machFix = ((data[7] & 0xff) << 2) | ((data[8] >> 6) & 0x3); + float mach = machFix * 2.048f / 512.0f; + bool machInconsistent = ((mach >= 1.0f) && (aircraft->m_emitterCategory != "High performance")) + || (!machStatus && (machFix != 0)); + + bool baroAltRateStatus = (data[8] >> 5) & 0x1; + int baroAltRateFix = ((data[8] & 0x1f) << 5) | ((data[9] >> 3) & 0x1f); + baroAltRateFix = (baroAltRateFix << 22) >> 22; + int baroAltRate = baroAltRateFix * 32; // ft/min + bool baroAltRateInconsistent = (abs(baroAltRate) > 6000) || (aircraft->m_verticalRateValid && abs(baroAltRate - aircraft->m_verticalRate) > 2000) + || (!baroAltRateStatus && (baroAltRateFix != 0)); + + bool verticalVelStatus = (data[9] >> 2) & 0x1; + int verticalVelFix = ((data[9] & 0x3) << 8) | (data[10] & 0xff); + verticalVelFix = (verticalVelFix << 22) >> 22; + int verticalVel = verticalVelFix * 32; // ft/min + bool verticalVelInconsistent = (abs(verticalVel) > 6000) || (aircraft->m_verticalRateValid && abs(verticalVel - aircraft->m_verticalRate) > 2000) + || (!verticalVelStatus && (verticalVelFix != 0)); + + 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; + + if (possibleMatches == 1) + { + if (bds_1_7) + { + // Some of these bits are dynamic, so can't assume that because a bit isn't set, + // that message is not ever supported + aircraft->m_bdsCapabilitiesValid = true; + aircraft->m_bdsCapabilities[0][5] |= cap_0_5; + aircraft->m_bdsCapabilities[0][6] |= cap_0_6; + aircraft->m_bdsCapabilities[0][7] |= cap_0_7; + aircraft->m_bdsCapabilities[0][8] |= cap_0_8; + aircraft->m_bdsCapabilities[0][9] |= cap_0_9; + aircraft->m_bdsCapabilities[0][10] |= cap_0_a; + aircraft->m_bdsCapabilities[2][0] |= cap_2_0; + aircraft->m_bdsCapabilities[2][1] |= cap_2_1; + aircraft->m_bdsCapabilities[4][0] |= cap_4_0; + aircraft->m_bdsCapabilities[4][1] |= cap_4_1; + aircraft->m_bdsCapabilities[4][2] |= cap_4_2; + aircraft->m_bdsCapabilities[4][3] |= cap_4_3; + aircraft->m_bdsCapabilities[4][4] |= cap_4_4; + aircraft->m_bdsCapabilities[4][8] |= cap_4_8; + aircraft->m_bdsCapabilities[5][0] |= cap_5_0; + aircraft->m_bdsCapabilities[5][1] |= cap_5_1; + aircraft->m_bdsCapabilities[5][2] |= cap_5_2; + aircraft->m_bdsCapabilities[5][3] |= cap_5_4; + aircraft->m_bdsCapabilities[5][4] |= cap_5_5; + aircraft->m_bdsCapabilities[5][5] |= cap_5_6; + aircraft->m_bdsCapabilities[5][6] |= cap_5_6; + aircraft->m_bdsCapabilities[5][15] |= cap_5_f; + aircraft->m_bdsCapabilities[6][0] |= cap_6_0; + } + if (bds_2_0) + { + updatedCallsign = aircraft->m_callsign != callsignTrimmed; + if (updatedCallsign) + { + aircraft->m_callsign = callsignTrimmed; + aircraft->m_callsignItem->setText(aircraft->m_callsign); + callsignToFlight(aircraft); + } + } + if (bds_2_1) + { + qDebug() << "BDS 2,1 - " + << "ICAO:" << aircraft->m_icaoHex + << "aircraftRegistration:" << aircraftRegistrationString + << "airlineRegistration:" << airlineRegistrationString + << "m_bdsCapabilities[2][1]: " << aircraft->m_bdsCapabilities[2][1] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + + if (!aircraftRegistrationString.isEmpty()) { + aircraft->m_registrationItem->setText(aircraftRegistrationString); + } + } + if (bds_4_0) + { + // Could use targetAltSource here, but yet to see it set to anything other than unknown + if (mcpSelectedAltStatus) + { + aircraft->m_selAltitude = mcpSelectedAlt; + aircraft->m_selAltitudeValid = true; + if (m_settings.m_siUnits) { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, Units::feetToIntegerMetres(aircraft->m_selAltitude)); + } else { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, roundTo50Feet(aircraft->m_selAltitude)); + } + } + else if (fmsSelectedAltStatus) + { + aircraft->m_selAltitude = fmsSelectedAlt; + aircraft->m_selAltitudeValid = true; + if (m_settings.m_siUnits) { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, Units::feetToIntegerMetres(aircraft->m_selAltitude)); + } else { + aircraft->m_selAltitudeItem->setData(Qt::DisplayRole, roundTo50Feet(aircraft->m_selAltitude)); + } + } + else + { + aircraft->m_selAltitude = 0; + aircraft->m_selAltitudeValid = false; + aircraft->m_selAltitudeItem->setText(""); + } + + if (baroSettingStatus) + { + aircraft->m_baro = baroSetting; + aircraft->m_baroValid = true; + aircraft->m_baroItem->setData(Qt::DisplayRole, std::round(aircraft->m_baro)); + } + + if (modeStatus) + { + QString mode = ""; + if (vnavMode) { + mode = mode + "VNAV "; + } + if (altHoldMode) { + mode = mode + "HOLD "; + } + if (approachMode) { + mode = mode + "APP "; + } + mode = mode.trimmed(); + aircraft->m_vModeItem->setText(mode); + } + } + if (bds_4_1) + { + qDebug() << "BDS 4,1 - " + << "ICAO:" << aircraft->m_icaoHex + << "waypoint:" << waypointString + << "m_bdsCapabilities[4][1]: " << aircraft->m_bdsCapabilities[4][1] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + } + if (bds_4_4) + { + if (fomSource != 0) // Ignore FOM "invalid" + { + if (windSpeedStatus) + { + if (m_settings.m_siUnits) { + aircraft->m_groundspeedItem->setData(Qt::DisplayRole, Units::knotsToIntegerKPH(aircraft->m_groundspeed)); + } else { + aircraft->m_windSpeedItem->setData(Qt::DisplayRole, windSpeed); + } + aircraft->m_windDirItem->setData(Qt::DisplayRole, windDirection); + // Not clear if air temp depends on the previous status bit + aircraft->m_staticAirTempItem->setData(Qt::DisplayRole, (int)std::round(staticAirTemperature)); + } + if (averageStaticPressureStatus) { + aircraft->m_staticPressureItem->setData(Qt::DisplayRole, averageStaticPressure); + } + if (humidityStatus) { + aircraft->m_humidityItem->setData(Qt::DisplayRole, (int)std::round(humidity)); + } + } + } + if (bds_4_5) + { + qDebug() << "BDS 4,5 - " + << "ICAO:" << aircraft->m_icaoHex + << "hazardTurbulence:" << m_hazardSeverity[hazardTurbulence] + << "hazardWindShear:" << m_hazardSeverity[hazardWindShear] + << "hazardMicroburst:" << m_hazardSeverity[hazardMicroburst] + << "hazardIcing:" << m_hazardSeverity[hazardIcing] + << "hazardWakeVortex:" << m_hazardSeverity[hazardWakeVortex] + << "hazardStaticAirTemperature:" << hazardStaticAirTemperature << "C" + << "hazardAverageStaticPressure:" << hazardAverageStaticPressure << "hPA" + << "hazardRadioHeight:" << hazardRadioHeight << "ft" + << "(Aircraft Alt: " << aircraft->m_altitude << "ft)" + << "m_bdsCapabilities[4][5]: " << aircraft->m_bdsCapabilities[4][5] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + ; + } + if (bds_5_0) + { + if (rollAngleStatus) + { + aircraft->m_roll = rollAngle; + aircraft->m_rollValid = true; + aircraft->m_rollItem->setData(Qt::DisplayRole, std::round(aircraft->m_roll)); + } + 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; + } + 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); + } + } + if (trackAngleRateStatus) + { + aircraft->m_turnRate = trackAngleRate; + aircraft->m_turnRateValid = true; + aircraft->m_turnRateItem->setData(Qt::DisplayRole, std::round(aircraft->m_turnRate*10.0f)/10.0f); + } + if (trueAirspeedStatus) + { + aircraft->m_trueAirspeed = trueAirspeed; + aircraft->m_trueAirspeedValid = true; + if (m_settings.m_siUnits) { + aircraft->m_trueAirspeedItem->setData(Qt::DisplayRole, Units::knotsToIntegerKPH(aircraft->m_trueAirspeed)); + } else { + aircraft->m_trueAirspeedItem->setData(Qt::DisplayRole, aircraft->m_trueAirspeed); + } + } + if (headwindStatus) + { + if (m_settings.m_siUnits) { + aircraft->m_headwindItem->setData(Qt::DisplayRole, Units::knotsToIntegerKPH(headwind)); + } else { + aircraft->m_headwindItem->setData(Qt::DisplayRole, headwind); + } + } + } + if (bds_5_1) + { + // Position is specified as "coarse" - is it worth using? + qDebug() << "BDS 5,1 - " + << "ICAO:" << aircraft->m_icaoHex + << "latitude:" << latitude << "(ADS-B:" << aircraft->m_latitude << ") " + << "longitude:" << longitude << "(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) + { + qDebug() << "BDS 5,3 - " + << "ICAO:" << aircraft->m_icaoHex + << "arMagHeading:" << arMagHeading << "(ADS-B:" << aircraft->m_heading << ") " + << "arIndicatedAirspeed:" << arIndicatedAirspeed << "knts" << "(ADS-B GS: " << aircraft->m_groundspeed << ") " + << "arMach:" << arMach + << "arTrueAirspeed:" << arTrueAirspeed << "knts" + << "arAltitudeRate:" << arAltitudeRate << "ft/min" + << "m_bdsCapabilities[5][3]: " << aircraft->m_bdsCapabilities[5][3] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + ; + } + if (bds_6_0) + { + if (indicatedAirspeedStatus) + { + aircraft->m_indicatedAirspeed = indicatedAirspeed; + aircraft->m_indicatedAirspeedValid = true; + if (m_settings.m_siUnits) { + aircraft->m_indicatedAirspeedItem->setData(Qt::DisplayRole, Units::knotsToIntegerKPH(aircraft->m_indicatedAirspeed)); + } else { + aircraft->m_indicatedAirspeedItem->setData(Qt::DisplayRole, aircraft->m_indicatedAirspeed); + } + } + if (machStatus) + { + aircraft->m_mach = mach; + 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 (bds_5_0 || bds_6_0) { + calcAirTemp(aircraft); + } + } + else if (false) // Enable to debug multiple matching frames + { + qDebug() << "DF" << df + << "matches" << possibleMatches + << "bds_1_0" << bds_1_0 + << "bds_1_7" << bds_1_7 + << "bds_2_0" << bds_2_0 + << "bds_2_1" << bds_2_1 + << "bds_3_0" << bds_3_0 + << "bds_4_0" << bds_4_0 + << "bds_4_1" << bds_4_1 + << "bds_4_4" << bds_4_4 + << "bds_4_5" << bds_4_5 + << "bds_5_0" << bds_5_0 + << "bds_5_1" << bds_5_1 + << "bds_5_3" << bds_5_3 + << "bds_6_0" << bds_6_0 + ; + + qDebug() << data.toHex(); + + qDebug() << (bds_1_0 ? "+" : "-") + << "BDS 1,0 - "; + qDebug() << (bds_1_7 ? "+" : "-") + << "BDS 1,7 - " + << "ICAO:" << aircraft->m_icaoHex + << "BDS:" << caps.join(" "); + qDebug() << (bds_2_0 ? "+" : "-") + << "BDS 2,0 - " + << "ICAO:" << aircraft->m_icaoHex + << "Callsign:" << callsignTrimmed; + qDebug() << (bds_2_1 ? "+" : "-") + << "BDS 2,1 - " + << "ICAO:" << aircraft->m_icaoHex + << "aircraftRegistration:" << aircraftRegistrationString + << "airlineRegistration:" << airlineRegistrationString + << "m_bdsCapabilities[2][1]: " << aircraft->m_bdsCapabilities[2][1] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_3_0 ? "+" : "-") + << "BDS 3,0 - "; + qDebug() << (bds_4_0 ? "+" : "-") + << "BDS 4,0 - " + << "ICAO:" << aircraft->m_icaoHex + << "mcpSelectedAlt:" << mcpSelectedAlt << "ft" + << "fmsSelectedAlt:" << fmsSelectedAlt << "ft" + << "baroSetting:" << (baroSettingStatus ? QString::number(baroSetting) : "invalid") << (baroSettingStatus ? "mb" : "") + << "vnav: " << vnavMode + << "altHold: " << altHoldMode + << "approach: " << approachMode + << "targetAltSource:" << targetAltSource + << "m_bdsCapabilities[4][0]: " << aircraft->m_bdsCapabilities[4][0] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_4_1 ? "+" : "-") + << "BDS 4,1 - " + << "ICAO:" << aircraft->m_icaoHex + << "waypoint:" << waypointString + << "m_bdsCapabilities[4][1]: " << aircraft->m_bdsCapabilities[4][1] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_4_4 ? "+" : "-") + << "BDS 4,4 - " + << "ICAO:" << aircraft->m_icaoHex + << "fomSource:" << m_fomSources[fomSource] + << "windSpeed:" << windSpeed << "knts" + << "windDirection:" << windDirection << "deg" + << "staticAirTemperature:" << staticAirTemperature << "C" + << "averageStaticPressure:" << averageStaticPressure << "hPa" + << "turbulence:" << turbulence + << "humidity:" << humidity << "%" + << "m_bdsCapabilities[4][4]: " << aircraft->m_bdsCapabilities[4][4] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_4_5 ? "+" : "-") + << "BDS 4,5 - " + << "ICAO:" << aircraft->m_icaoHex + << "hazardTurbulence:" << m_hazardSeverity[hazardTurbulence] + << "hazardWindShear:" << m_hazardSeverity[hazardWindShear] + << "hazardMicroburst:" << m_hazardSeverity[hazardMicroburst] + << "hazardIcing:" << m_hazardSeverity[hazardIcing] + << "hazardWakeVortex:" << m_hazardSeverity[hazardWakeVortex] + << "hazardStaticAirTemperature:" << hazardStaticAirTemperature << "C" + << "hazardAverageStaticPressure:" << hazardAverageStaticPressure << "hPA" + << "hazardRadioHeight:" << hazardRadioHeight << "ft" + << "m_bdsCapabilities[4][5]: " << aircraft->m_bdsCapabilities[4][5] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_5_0 ? "+" : "-") + << "BDS 5,0 - " + << "ICAO:" << aircraft->m_icaoHex + << "rollAngle:" << rollAngle + << "trueTrackAngle:" << trueTrackAngle + << "trackAngleRate: " << trackAngleRate + << "groundSpeed:" << groundSpeed + << "trueAirspeed:" << trueAirspeed + << "headwind:" << headwind + << "(ADS-B GS: " << aircraft->m_groundspeed << ")" + << "(ADS-B TAS: " << aircraft->m_trueAirspeed << ")" + << "(ADS-B heading:" << aircraft->m_heading << ")" + << "m_bdsCapabilities[5][0]: " << aircraft->m_bdsCapabilities[5][0] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + 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 << ") " + << "pressureAlt:" << pressureAlt << "(ADS-B:" << aircraft->m_altitude << ")" + << "m_bdsCapabilities[5][1]: " << aircraft->m_bdsCapabilities[5][1] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_5_3 ? "+" : "-") + << "BDS 5,3 - " + << "ICAO:" << aircraft->m_icaoHex + << "arMagHeading:" << arMagHeading << "(ADS-B:" << aircraft->m_heading << ") " + << "arIndicatedAirspeed:" << arIndicatedAirspeed << "knts" << "(ADS-B GS: " << aircraft->m_groundspeed << ") " + << "arMach:" << arMach + << "arTrueAirspeed:" << arTrueAirspeed << "knts" + << "arAltitudeRate:" << arAltitudeRate << "ft/min" + << "arMagHeadingInconsistent" << arMagHeadingInconsistent << "arIndicatedAirspeedInconsistent" << arIndicatedAirspeedInconsistent << "arMachInconsistent" << arMachInconsistent << "arTrueAirspeedInconsistent" << arTrueAirspeedInconsistent + << "m_bdsCapabilities[5][3]: " << aircraft->m_bdsCapabilities[5][3] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + qDebug() << (bds_6_0 ? "+" : "-") + << "BDS 6,0 - " + << "ICAO:" << aircraft->m_icaoHex + << "magHeading:" << magHeading << "(ADS-B:" << aircraft->m_heading << ") " + << "indicatedAirspeed:" << indicatedAirspeed << "knts" << "(ADS-B GS: " << aircraft->m_groundspeed << ") " + << "mach:" << mach + << "baroAltRate:" << baroAltRate << "ft/min" + << "verticalVel:" << verticalVel << "ft/min" + << "magHeadingInconsistent " << magHeadingInconsistent << "indicatedAirspeedInconsistent" << indicatedAirspeedInconsistent << "machInconsistent" << machInconsistent << "baroAltRateInconsistent" << baroAltRateInconsistent << "verticalVelInconsistent" << verticalVelInconsistent + << "m_bdsCapabilities[6][0]: " << aircraft->m_bdsCapabilities[6][0] + << "m_bdsCapabilitiesValid: " << aircraft->m_bdsCapabilitiesValid; + } + } +} + QList * ADSBDemodGUI::animate(QDateTime dateTime, Aircraft *aircraft) { QList *animations = new QList(); @@ -1664,8 +2662,8 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime // during take-off if ( aircraft->m_onSurface && !aircraft->m_gearDown - && ( (aircraft->m_speedValid && (aircraft->m_speed < 80)) - || !aircraft->m_speedValid + && ( (aircraft->m_groundspeedValid && (aircraft->m_groundspeed < 80)) + || !aircraft->m_groundspeedValid ) ) { @@ -1677,9 +2675,9 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime } // Flaps when on the surface - if (aircraft->m_onSurface && aircraft->m_speedValid) + if (aircraft->m_onSurface && aircraft->m_groundspeedValid) { - if ((aircraft->m_speed <= 20) && (aircraft->m_flaps != 0.0)) + if ((aircraft->m_groundspeed <= 20) && (aircraft->m_flaps != 0.0)) { // No flaps when stationary / taxiing if (debug) { @@ -1689,7 +2687,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime animations->append(slatsAnimation(dateTime, true)); aircraft->m_flaps = 0.0; } - else if ((aircraft->m_speed >= 30) && (aircraft->m_flaps < 0.25)) + else if ((aircraft->m_groundspeed >= 30) && (aircraft->m_flaps < 0.25)) { // Flaps for takeoff if (debug) { @@ -1712,7 +2710,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime if (debug) { qDebug() << "Pitch up " << aircraft->m_icaoHex; } - aircraft->m_pitch = 5.0; + aircraft->m_pitchEst = 5.0; } // Retract landing gear after take-off @@ -1729,7 +2727,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime << " Alt " << (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude > (aircraft->m_runwayAltitude + gearUpAltitude))) << "m_altitude " << aircraft->m_altitude << " aircraft->m_runwayAltitude " << aircraft->m_runwayAltitude; } - aircraft->m_pitch = 10.0; + aircraft->m_pitchEst = 10.0; animations->append(gearAnimation(dateTime.addSecs(2), true)); aircraft->m_gearDown = false; } @@ -1739,7 +2737,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime && (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude > (aircraft->m_runwayAltitude + accelerationHeight))) ) { - aircraft->m_pitch = 5.0; + aircraft->m_pitchEst = 5.0; } // Retract flaps/slats after take-off @@ -1747,13 +2745,13 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime // And before max speed for flaps is reached (215knt for A320, 255KIAS for 777) if ( (aircraft->m_flaps > 0.0) && ( (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude > (aircraft->m_runwayAltitude + flapsRetractAltitude))) - || (aircraft->m_speedValid && (aircraft->m_speed > flapsCleanSpeed)) + || (aircraft->m_groundspeedValid && (aircraft->m_groundspeed > flapsCleanSpeed)) ) ) { if (debug) { qDebug() << "Retract flaps " << aircraft->m_icaoHex - << " Spd " << (aircraft->m_speedValid && (aircraft->m_speed > flapsCleanSpeed)) + << " Spd " << (aircraft->m_groundspeedValid && (aircraft->m_groundspeed > flapsCleanSpeed)) << " Alt " << (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude > (aircraft->m_runwayAltitude + flapsRetractAltitude))); } animations->append(flapsAnimation(dateTime, aircraft->m_flaps, 0.0)); @@ -1770,10 +2768,10 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime if (!aircraft->m_onSurface && !aircraft->m_runwayAltitudeValid && (aircraft->m_verticalRateValid && (aircraft->m_verticalRate < 0)) - && aircraft->m_speedValid + && aircraft->m_groundspeedValid ) { - if ((aircraft->m_speed < flapsCleanSpeed) && (aircraft->m_flaps < 0.25)) + if ((aircraft->m_groundspeed < flapsCleanSpeed) && (aircraft->m_flaps < 0.25)) { // Extend flaps for approach if (debug) { @@ -1782,7 +2780,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime animations->append(flapsAnimation(dateTime, aircraft->m_flaps, 0.25)); //animations->append(slatsAnimation(dateTime, false)); aircraft->m_flaps = 0.25; - aircraft->m_pitch = 1.0; + aircraft->m_pitchEst = 1.0; } } @@ -1792,7 +2790,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime && !aircraft->m_onSurface && !aircraft->m_runwayAltitudeValid && (aircraft->m_verticalRateValid && (aircraft->m_verticalRate < 0)) - && (aircraft->m_speedValid && (aircraft->m_speed < gearDownSpeed)) + && (aircraft->m_groundspeedValid && (aircraft->m_groundspeed < gearDownSpeed)) ) { if (debug) { @@ -1804,7 +2802,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime animations->append(flapsAnimation(dateTime.addSecs(16), 0.5, 1.0)); aircraft->m_gearDown = true; aircraft->m_flaps = 1.0; - aircraft->m_pitch = 3.0; + aircraft->m_pitchEst = 3.0; } // Engine control @@ -1832,7 +2830,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime else { // Propellors - if (!aircraft->m_engineStarted && aircraft->m_speedValid && (aircraft->m_speed > 0)) + if (!aircraft->m_engineStarted && aircraft->m_groundspeedValid && (aircraft->m_groundspeed > 0)) { if (debug) { qDebug() << "Start engines " << aircraft->m_icaoHex; @@ -1841,7 +2839,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime animations->append(engineAnimation(dateTime, 2, false)); aircraft->m_engineStarted = true; } - else if (aircraft->m_engineStarted && aircraft->m_speedValid && (aircraft->m_speed == 0)) + else if (aircraft->m_engineStarted && aircraft->m_groundspeedValid && (aircraft->m_groundspeed == 0)) { if (debug) { qDebug() << "Stop engines " << aircraft->m_icaoHex; @@ -1857,26 +2855,26 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime { // Check speed so we don't set pitch to 0 immediately on touch-down // Should probably record time of touch-down and reduce over time - if (aircraft->m_speedValid) + if (aircraft->m_groundspeedValid) { - if (aircraft->m_speed < 80) { - aircraft->m_pitch = 0.0; - } else if ((aircraft->m_speed < 130) && (aircraft->m_pitch >= 2)) { - aircraft->m_pitch = 1; + if (aircraft->m_groundspeed < 80) { + aircraft->m_pitchEst = 0.0; + } else if ((aircraft->m_groundspeed < 130) && (aircraft->m_pitchEst >= 2)) { + aircraft->m_pitchEst = 1; } } } else if ((aircraft->m_flaps < 0.25) && aircraft->m_verticalRateValid) { // In climb/descent - aircraft->m_pitch = std::abs(aircraft->m_verticalRate / 400.0); + aircraft->m_pitchEst = std::abs(aircraft->m_verticalRate / 400.0); } // Estimate some roll if (aircraft->m_onSurface || (aircraft->m_runwayAltitudeValid && (aircraft->m_altitude < (aircraft->m_runwayAltitude + accelerationHeight)))) { - aircraft->m_roll = 0.0; + aircraft->m_rollEst = 0.0; } else if (aircraft->m_headingValid) { @@ -1891,7 +2889,7 @@ QList * ADSBDemodGUI::animate(QDateTime dateTime //qDebug() << "Heading Diff " << headingDiff << " msecs " << msecs << " roll " << roll; roll = std::min(roll, 15.0f); roll = std::max(roll, -15.0f); - aircraft->m_roll = roll; + aircraft->m_rollEst = roll; } } aircraft->m_prevHeadingDateTime = aircraft->m_headingDateTime; @@ -2033,7 +3031,7 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft) { if ( (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_CALLSIGN) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_ALTITUDE) - || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_SPEED) + || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_GROUND_SPEED) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_RANGE) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_CATEGORY) || (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_STATUS) @@ -2049,8 +3047,8 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft) case ADSB_COL_ALTITUDE: match = aircraft->m_altitudeItem->data(Qt::DisplayRole).toString(); break; - case ADSB_COL_SPEED: - match = aircraft->m_speedItem->data(Qt::DisplayRole).toString(); + case ADSB_COL_GROUND_SPEED: + match = aircraft->m_groundspeedItem->data(Qt::DisplayRole).toString(); break; case ADSB_COL_RANGE: match = aircraft->m_rangeItem->data(Qt::DisplayRole).toString(); @@ -2118,11 +3116,18 @@ 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("${latitude}", aircraft->m_latitudeItem->data(Qt::DisplayRole).toString()); - s = s.replace("${longitude}", aircraft->m_longitudeItem->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()); + s = s.replace("${ias}", aircraft->m_indicatedAirspeedItem->data(Qt::DisplayRole).toString()); + s = s.replace("${mach}", aircraft->m_machItem->data(Qt::DisplayRole).toString()); + s = s.replace("${selAltitude}", aircraft->m_selAltitudeItem->data(Qt::DisplayRole).toString()); s = s.replace("${altitude}", aircraft->m_altitudeItem->data(Qt::DisplayRole).toString()); - s = s.replace("${speed}", aircraft->m_speedItem->data(Qt::DisplayRole).toString()); + 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("${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()); s = s.replace("${azel}", aircraft->m_azElItem->data(Qt::DisplayRole).toString()); s = s.replace("${category}", aircraft->m_emitterCategoryItem->data(Qt::DisplayRole).toString()); @@ -2132,6 +3137,18 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin s = s.replace("${manufacturer}", aircraft->m_manufacturerNameItem->data(Qt::DisplayRole).toString()); s = s.replace("${owner}", aircraft->m_ownerItem->data(Qt::DisplayRole).toString()); s = s.replace("${operator}", aircraft->m_operatorICAOItem->data(Qt::DisplayRole).toString()); + 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("${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()); + s = s.replace("${windDirection}", aircraft->m_windDirItem->data(Qt::DisplayRole).toString()); + s = s.replace("${staticPressure}", aircraft->m_staticPressureItem->data(Qt::DisplayRole).toString()); + s = s.replace("${staticAirTemperature}", aircraft->m_staticAirTempItem->data(Qt::DisplayRole).toString()); + 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("${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()); @@ -2174,6 +3191,7 @@ bool ADSBDemodGUI::handleMessage(const Message& message) report.getDateTime(), report.getPreambleCorrelation(), report.getCorrelationOnes(), + report.getCRC(), true); return true; } @@ -2565,7 +3583,7 @@ void ADSBDemodGUI::on_getOSNDB_clicked() // Don't try to download while already in progress if (m_progressDialog == nullptr) { - QString osnDBFilename = getOSNDBZipFilename(); + QString osnDBFilename = AircraftInformation::getOSNDBZipFilename(); if (confirmDownload(osnDBFilename)) { // Download Opensky network database to a file @@ -2640,21 +3658,6 @@ QString ADSBDemodGUI::getAirportFrequenciesDBFilename() return getDataDir() + "/airportFrequenciesDatabase.csv"; } -QString ADSBDemodGUI::getOSNDBZipFilename() -{ - return getDataDir() + "/aircraftDatabase.zip"; -} - -QString ADSBDemodGUI::getOSNDBFilename() -{ - return getDataDir() + "/aircraftDatabase.csv"; -} - -QString ADSBDemodGUI::getFastDBFilename() -{ - return getDataDir() + "/aircraftDatabaseFast.csv"; -} - qint64 ADSBDemodGUI::fileAgeInDays(QString filename) { QFile file(filename); @@ -2713,14 +3716,14 @@ void ADSBDemodGUI::downloadFinished(const QString& filename, bool success) bool closeDialog = true; if (success) { - if (filename == getOSNDBZipFilename()) + if (filename == AircraftInformation::getOSNDBZipFilename()) { // Extract .csv file from .zip file QZipReader reader(filename); QByteArray database = reader.fileData("media/data/samples/metadata/aircraftDatabase.csv"); if (database.size() > 0) { - QFile file(getOSNDBFilename()); + QFile file(AircraftInformation::getOSNDBFilename()); if (file.open(QIODevice::WriteOnly)) { file.write(database); @@ -2738,10 +3741,10 @@ void ADSBDemodGUI::downloadFinished(const QString& filename, bool success) qWarning() << "ADSBDemodGUI::downloadFinished - Failed to extract files from " << filename; } } - readOSNDB(getOSNDBFilename()); + readOSNDB(AircraftInformation::getOSNDBFilename()); // Convert to condensed format for faster loading later m_progressDialog->setLabelText("Processing."); - AircraftInformation::writeFastDB(getFastDBFilename(), m_aircraftInfo); + AircraftInformation::writeFastDB(AircraftInformation::getFastDBFilename(), m_aircraftInfo); } else if (filename == getAirportDBFilename()) { @@ -2974,12 +3977,12 @@ void ADSBDemodGUI::get3DModelBasedOnCategory(Aircraft *aircraft) if (!aircraft->m_emitterCategory.compare("Heavy")) { - QStringList heavy = {"B744", "B77W", "B788", "A388"}; + static const QStringList heavy = {"B744", "B77W", "B788", "A388"}; aircraftType = heavy[m_random.bounded(heavy.size())]; } else if (!aircraft->m_emitterCategory.compare("Large")) { - QStringList large = {"A319", "A320", "A321", "B737", "B738", "B739"}; + static const QStringList large = {"A319", "A320", "A321", "B737", "B738", "B739"}; aircraftType = large[m_random.bounded(large.size())]; } else if (!aircraft->m_emitterCategory.compare("Small")) @@ -3064,7 +4067,7 @@ void ADSBDemodGUI::update3DModels() { // Look for all aircraft gltfs in 3d directory QString modelDir = getDataDir() + "/3d"; - QStringList subDirs = {"BB_Airbus_png", "BB_Boeing_png", "BB_Jets_png", "BB_Props_png", "BB_GA_png", "BB_Mil_png", "BB_Heli_png"}; + static const QStringList subDirs = {"BB_Airbus_png", "BB_Boeing_png", "BB_Jets_png", "BB_Props_png", "BB_GA_png", "BB_Mil_png", "BB_Heli_png"}; for (auto subDir : subDirs) { @@ -3825,20 +4828,18 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb ui->flightDetails->setVisible(false); ui->aircraftDetails->setVisible(false); + AircraftInformation::init(); + // Read aircraft information database, if it has previously been downloaded - if (!readFastDB(getFastDBFilename())) + if (!readFastDB(AircraftInformation::getFastDBFilename())) { - if (readOSNDB(getOSNDBFilename())) - AircraftInformation::writeFastDB(getFastDBFilename(), m_aircraftInfo); + if (readOSNDB(AircraftInformation::getOSNDBFilename())) + AircraftInformation::writeFastDB(AircraftInformation::getFastDBFilename(), m_aircraftInfo); } // Read airport information database, if it has previously been downloaded m_airportInfo = AirportInformation::readAirportsDB(getAirportDBFilename()); if (m_airportInfo != nullptr) AirportInformation::readFrequenciesDB(getAirportFrequenciesDBFilename(), m_airportInfo); - // Read registration prefix to country map - m_prefixMap = CSV::hash(":/flags/regprefixmap.csv"); - // Read operator air force to military map - m_militaryMap = CSV::hash(":/flags/militarymap.csv"); connect(&m_openAIP, &OpenAIP::downloadingURL, this, &ADSBDemodGUI::downloadingURL); connect(&m_openAIP, &OpenAIP::downloadError, this, &ADSBDemodGUI::downloadError); @@ -3930,8 +4931,6 @@ ADSBDemodGUI::~ADSBDemodGUI() if (m_aircraftInfo) { qDeleteAll(*m_aircraftInfo); } - qDeleteAll(m_airlineIcons); - qDeleteAll(m_flagIcons); if (m_flightInformation) { disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated); @@ -4016,14 +5015,24 @@ void ADSBDemodGUI::displaySettings() if (m_settings.m_siUnits) { ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (m)"); - ui->adsbData->horizontalHeaderItem(ADSB_COL_SPEED)->setText("Spd (kph)"); ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (m/s)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (m)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kph)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kph)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kph)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kph)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kph)"); } else { ui->adsbData->horizontalHeaderItem(ADSB_COL_ALTITUDE)->setText("Alt (ft)"); - ui->adsbData->horizontalHeaderItem(ADSB_COL_SPEED)->setText("Spd (kn)"); ui->adsbData->horizontalHeaderItem(ADSB_COL_VERTICALRATE)->setText("VR (ft/m)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_SEL_ALTITUDE)->setText("Sel Alt (ft)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_GROUND_SPEED)->setText("GS (kn)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_TRUE_AIRSPEED)->setText("TAS (kn)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_INDICATED_AIRSPEED)->setText("IAS (kn)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_HEADWIND)->setText("H Wnd (kn)"); + ui->adsbData->horizontalHeaderItem(ADSB_COL_WIND_SPEED)->setText("Wnd (kn)"); } // Order and size columns @@ -4156,11 +5165,10 @@ 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("Callsign-")); ui->adsbData->setItem(row, ADSB_COL_MODEL, new QTableWidgetItem("Aircraft12345")); 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_SPEED, new QTableWidgetItem("Spd (kn)")); 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)")); @@ -4189,6 +5197,23 @@ void ADSBDemodGUI::resizeTable() 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_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_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)")); + ui->adsbData->setItem(row, ADSB_COL_WIND_DIR, new QTableWidgetItem("Wnd (o)")); + 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->resizeColumnsToContents(); ui->adsbData->setRowCount(row); } @@ -4279,7 +5304,7 @@ void ADSBDemodGUI::aircraftPhoto(const PlaneSpottersPhoto *photo) { // Make sure the photo is for the currently highlighted aircraft, as it may // have taken a while to download - if (!photo->m_pixmap.isNull() && m_highlightAircraft && (m_highlightAircraft->m_icaoItem->text() == photo->m_icao)) + if (!photo->m_pixmap.isNull() && m_highlightAircraft && (m_highlightAircraft->m_icaoItem->text() == photo->m_id)) { ui->photo->setPixmap(photo->m_pixmap); ui->photo->setToolTip(QString("Photographer: %1").arg(photo->m_photographer)); // Required by terms of use @@ -4359,8 +5384,10 @@ void ADSBDemodGUI::on_logOpen_clicked() dialog.show(); QApplication::processEvents(); int count = 0; + int countOtherDF = 0; bool cancelled = false; QStringList cols; + crcadsb crc; while (!cancelled && CSV::readRow(in, &cols)) { if (cols.size() > maxCol) @@ -4368,16 +5395,32 @@ void ADSBDemodGUI::on_logOpen_clicked() QDateTime dateTime = QDateTime::currentDateTime(); // So they aren't removed immediately as too old QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1()); float correlation = cols[correlationCol].toFloat(); - handleADSB(bytes, dateTime, correlation, correlation, false); - if ((count > 0) && (count % 100000 == 0)) + int df = (bytes[0] >> 3) & ADS_B_DF_MASK; // Downlink format + if ((df == 4) || (df == 5) || (df == 17) || (df == 18) || (df == 20) || (df == 21)) { - dialog.setText(QString("Reading ADS-B data\n%1").arg(count)); - QApplication::processEvents(); - if (dialog.clickedButton()) { - cancelled = true; + int crcCalc = 0; + if ((df == 4) || (df == 5) || (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.szie " << bytes.size() << " crc " << Qt::hex << crcCalc; + handleADSB(bytes, dateTime, correlation, correlation, crcCalc, false); + if ((count > 0) && (count % 10000 == 0)) + { + dialog.setText(QString("Reading ADS-B data\n%1 (Skipped %2)").arg(count).arg(countOtherDF)); + QApplication::processEvents(); + if (dialog.clickedButton()) { + cancelled = true; + } + } + count++; + } + else + { + countOtherDF++; } - count++; } } m_aircraftModel.allAircraftUpdated(); @@ -4441,6 +5484,44 @@ void ADSBDemodGUI::downloadNavAidsFinished() } } +int ADSBDemodGUI::squawkDecode(int modeA) const +{ + int a, b, c, d; + c = ((modeA >> 12) & 1) | ((modeA >> (10-1)) & 0x2) | ((modeA >> (8-2)) & 0x4); + a = ((modeA >> 11) & 1) | ((modeA >> (9-1)) & 0x2) | ((modeA >> (7-2)) & 0x4); + b = ((modeA >> 5) & 1) | ((modeA >> (3-1)) & 0x2) | ((modeA << (1)) & 0x4); + d = ((modeA >> 4) & 1) | ((modeA >> (2-1)) & 0x2) | ((modeA << (2)) & 0x4); + return a*1000 + b*100 + c*10 + d; +} + +// https://en.wikipedia.org/wiki/Gillham_code +int ADSBDemodGUI::gillhamToFeet(int n) const +{ + 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; +} + int ADSBDemodGUI::grayToBinary(int gray, int bits) const { int binary = 0; @@ -4605,15 +5686,19 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) aircraft->m_altitudeGNSS = false; aircraft->m_altitudeItem->setData(Qt::DisplayRole, aircraft->m_altitude); } + if (!state[5].isNull() && !state[6].isNull() && !state[7].isNull()) + { + QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); + aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); + } aircraft->m_positionDateTime = timePosition; } aircraft->m_onSurface = state[8].toBool(false); if (!state[9].isNull()) { - aircraft->m_speed = (int)state[9].toDouble(); - aircraft->m_speedItem->setData(Qt::DisplayRole, aircraft->m_speed); - aircraft->m_speedValid = true; - aircraft->m_speedType = Aircraft::GS; + aircraft->m_groundspeed = (int)state[9].toDouble(); + aircraft->m_groundspeedItem->setData(Qt::DisplayRole, aircraft->m_groundspeed); + aircraft->m_groundspeedValid = true; } if (!state[10].isNull()) { diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.h b/plugins/channelrx/demodadsb/adsbdemodgui.h index 4f90ae4db..e7bbd9b00 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.h +++ b/plugins/channelrx/demodadsb/adsbdemodgui.h @@ -48,7 +48,7 @@ #include "adsbdemodsettings.h" #include "ourairportsdb.h" -#include "osndb.h" +#include "util/osndb.h" class PluginAPI; class DeviceUISet; @@ -85,7 +85,7 @@ public: } }; -// Data about an aircraft extracted from an ADS-B frames +// Data about an aircraft extracted from ADS-B and Mode-S frames struct Aircraft { int m_icao; // 24-bit ICAO aircraft address QString m_icaoHex; @@ -96,14 +96,7 @@ struct Aircraft { int m_altitude; // Altitude in feet bool m_onSurface; // Indicates if on surface or airbourne bool m_altitudeGNSS; // Altitude is GNSS HAE (Height above WGS-84 ellipsoid) rather than barometric alitute (relative to 29.92 Hg) - int m_speed; // Speed in knots - enum SpeedType { - GS, // Ground speed - TAS, // True air speed - IAS // Indicated air speed - } m_speedType; - static const char *m_speedTypeNames[]; - float m_heading; // Heading in degrees + float m_heading; // Heading or track in degrees int m_verticalRate; // Vertical climb rate in ft/min QString m_emitterCategory; // Aircraft type QString m_status; // Aircraft status @@ -113,11 +106,32 @@ struct Aircraft { Real m_elevation; // Elevation from station to aicraft QDateTime m_time; // When last updated + 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_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_bdsCapabilities[16][16]; // BDS capabilities are indicaited by BDS 1.7 + bool m_positionValid; // Indicates if we have valid data for the above fields bool m_altitudeValid; - bool m_speedValid; bool m_headingValid; bool m_verticalRateValid; + bool m_selAltitudeValid; + bool m_selHeadingValid; + bool m_baroValid; + bool m_rollValid; + bool m_groundspeedValid; + bool m_turnRateValid; + bool m_trueAirspeedValid; + bool m_indicatedAirspeedValid; + bool m_machValid; + bool m_bdsCapabilitiesValid; // State for calculating position using two CPR frames bool m_cprValid[2]; @@ -126,6 +140,7 @@ struct Aircraft { QDateTime m_cprTime[2]; int m_adsbFrameCount; // Number of ADS-B frames for this aircraft + int m_tisBFrameCount; float m_minCorrelation; float m_maxCorrelation; float m_correlation; @@ -158,8 +173,8 @@ struct Aircraft { QDateTime m_headingDateTime; QDateTime m_prevHeadingDateTime; int m_prevHeading; - float m_pitch; // Estimated pitch based on vertical rate - float m_roll; // Estimated roll based on rate of change in heading + float m_pitchEst; // Estimated pitch based on vertical rate + float m_rollEst; // Estimated roll based on rate of change in heading bool m_notified; // Set when a notification has been made for this aircraft, so we don't repeat it @@ -171,7 +186,6 @@ struct Aircraft { QTableWidgetItem *m_latitudeItem; QTableWidgetItem *m_longitudeItem; QTableWidgetItem *m_altitudeItem; - QTableWidgetItem *m_speedItem; QTableWidgetItem *m_headingItem; QTableWidgetItem *m_verticalRateItem; CustomDoubleTableWidgetItem *m_rangeItem; @@ -198,6 +212,26 @@ struct Aircraft { QTableWidgetItem *m_staItem; QTableWidgetItem *m_etaItem; QTableWidgetItem *m_ataItem; + QTableWidgetItem *m_selAltitudeItem; + QTableWidgetItem *m_selHeadingItem; + QTableWidgetItem *m_baroItem; + QTableWidgetItem *m_apItem; + QTableWidgetItem *m_vModeItem; + QTableWidgetItem *m_lModeItem; + QTableWidgetItem *m_rollItem; + QTableWidgetItem *m_groundspeedItem; + QTableWidgetItem *m_turnRateItem; + QTableWidgetItem *m_trueAirspeedItem; + QTableWidgetItem *m_indicatedAirspeedItem; + QTableWidgetItem *m_machItem; + QTableWidgetItem *m_headwindItem; + QTableWidgetItem *m_estAirTempItem; + QTableWidgetItem *m_windSpeedItem; + QTableWidgetItem *m_windDirItem; + QTableWidgetItem *m_staticPressureItem; + QTableWidgetItem *m_staticAirTempItem; + QTableWidgetItem *m_humidityItem; + QTableWidgetItem *m_tisBItem; Aircraft(ADSBDemodGUI *gui) : m_icao(0), @@ -206,18 +240,35 @@ struct Aircraft { m_altitude(0), m_onSurface(false), m_altitudeGNSS(false), - m_speed(0), - m_speedType(GS), m_heading(0), m_verticalRate(0), m_azimuth(0), m_elevation(0), + m_selAltitude(0), + m_selHeading(0), + m_baro(0), + m_roll(0.0f), + m_groundspeed(0), + m_turnRate(0.0f), + m_trueAirspeed(0), + m_indicatedAirspeed(0), + m_mach(0.0f), m_positionValid(false), m_altitudeValid(false), - m_speedValid(false), m_headingValid(false), m_verticalRateValid(false), + m_selAltitudeValid(false), + m_selHeadingValid(false), + m_baroValid(false), + m_rollValid(false), + m_groundspeedValid(false), + m_turnRateValid(false), + m_trueAirspeedValid(false), + m_indicatedAirspeedValid(false), + m_machValid(false), + m_bdsCapabilitiesValid(false), m_adsbFrameCount(0), + m_tisBFrameCount(0), m_minCorrelation(INFINITY), m_maxCorrelation(-INFINITY), m_correlation(0.0f), @@ -234,21 +285,24 @@ struct Aircraft { m_flaps(0.0), m_rotorStarted(false), m_engineStarted(false), - m_pitch(0.0), - m_roll(0.0), + m_pitchEst(0.0), + m_rollEst(0.0), m_notified(false) { - for (int i = 0; i < 2; i++) - { + for (int i = 0; i < 2; i++) { m_cprValid[i] = false; } + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + m_bdsCapabilities[i][j] = false; + } + } // These are deleted by QTableWidget m_icaoItem = new QTableWidgetItem(); m_callsignItem = new QTableWidgetItem(); m_modelItem = new QTableWidgetItem(); m_airlineItem = new QTableWidgetItem(); m_altitudeItem = new QTableWidgetItem(); - m_speedItem = new QTableWidgetItem(); m_headingItem = new QTableWidgetItem(); m_verticalRateItem = new QTableWidgetItem(); m_rangeItem = new CustomDoubleTableWidgetItem(); @@ -277,6 +331,26 @@ struct Aircraft { m_staItem = new QTableWidgetItem(); m_etaItem = new QTableWidgetItem(); m_ataItem = new QTableWidgetItem(); + m_selAltitudeItem = new QTableWidgetItem(); + m_selHeadingItem = new QTableWidgetItem(); + m_baroItem = new QTableWidgetItem(); + m_apItem = new QTableWidgetItem(); + m_vModeItem = new QTableWidgetItem(); + m_lModeItem = new QTableWidgetItem(); + m_rollItem = new QTableWidgetItem(); + m_groundspeedItem = new QTableWidgetItem(); + m_turnRateItem = new QTableWidgetItem(); + m_trueAirspeedItem = new QTableWidgetItem(); + m_indicatedAirspeedItem = new QTableWidgetItem(); + m_machItem = new QTableWidgetItem(); + m_headwindItem = new QTableWidgetItem(); + m_estAirTempItem = new QTableWidgetItem(); + m_windSpeedItem = new QTableWidgetItem(); + m_windDirItem = new QTableWidgetItem(); + m_staticPressureItem = new QTableWidgetItem(); + m_staticAirTempItem = new QTableWidgetItem(); + m_humidityItem = new QTableWidgetItem(); + m_tisBItem = new QTableWidgetItem(); } QString getImage() const; @@ -826,11 +900,6 @@ private: AirportModel m_airportModel; AirspaceModel m_airspaceModel; NavAidModel m_navAidModel; - QHash m_airlineIcons; // Hashed on airline ICAO - QHash m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for - QHash m_flagIcons; // Hashed on country - QHash *m_prefixMap; // Registration to country (flag name) - QHash *m_militaryMap; // Operator airforce to military (flag name) QList m_airspaces; QList m_navAids; @@ -867,6 +936,15 @@ private: QTimer m_redrawMapTimer; QNetworkAccessManager *m_networkManager; + static const char m_idMap[]; + static const QString m_categorySetA[]; + static const QString m_categorySetB[]; + static const QString m_categorySetC[]; + static const QString m_emergencyStatus[]; + static const QString m_flightStatuses[]; + static const QString m_hazardSeverity[]; + static const QString m_fomSources[]; + explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~ADSBDemodGUI(); @@ -881,12 +959,18 @@ private: bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition); void sendToMap(Aircraft *aircraft, QList *animations); Aircraft *getAircraft(int icao, bool &newAircraft); + void callsignToFlight(Aircraft *aircraft); + int roundTo50Feet(int alt); + bool calcAirTemp(Aircraft *aircraft); void handleADSB( const QByteArray data, const QDateTime dateTime, float correlation, float correlationOnes, + unsigned crc, bool updateModel); + 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 *flapsAnimation(QDateTime startDateTime, float currentFlaps, float flaps); @@ -915,10 +999,6 @@ private: void updateAirports(); void updateAirspaces(); void updateNavAids(); - QString getAirlineIconPath(const QString &operatorICAO); - QIcon *getAirlineIcon(const QString &operatorICAO); - QString getFlagIconPath(const QString &country); - QIcon *getFlagIcon(const QString &country); void updateDeviceSetList(); QAction *createCheckableItem(QString& text, int idx, bool checked); Aircraft* findAircraftByFlight(const QString& flight); @@ -929,6 +1009,8 @@ private: void updatePhotoText(Aircraft *aircraft); void updatePhotoFlightInformation(Aircraft *aircraft); void findOnChannelMap(Aircraft *aircraft); + int squawkDecode(int code) const; + int gillhamToFeet(int n) const; int grayToBinary(int gray, int bits) const; void redrawMap(); void applyImportSettings(); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.ui b/plugins/channelrx/demodadsb/adsbdemodgui.ui index a0ea5dc91..d94013ea2 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.ui +++ b/plugins/channelrx/demodadsb/adsbdemodgui.ui @@ -873,6 +873,54 @@ 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) @@ -883,10 +931,18 @@ - Spd (kn) + VR (ft/m) - Speed in knots or kilometres per hour + 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) @@ -894,15 +950,23 @@ Hd (°) - Aircraft heading in degrees + Aircraft heading / track in degrees - VR (ft/m) + TR (°/s) - Vertical climb rate in feet per minute or metres per second + Turn rate in degrees per second + + + + + Roll (°) + + + Roll angle in degrees @@ -921,22 +985,6 @@ Azimuth and elevation to aircraft from My Position. Double click to set as target. - - - Lat (°) - - - Latitude in degrees postive towards the North - - - - - Lon (°) - - - Longitude in degrees. Positive towards the East - - Cat @@ -969,14 +1017,6 @@ Aircraft registration - - - Country - - - Country of registration - - Registered @@ -1009,6 +1049,110 @@ 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 Celsuis 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 Celsuis + + + + + U (%) + + + Humidity in percent + + + + + Lat (°) + + + Latitude in degrees postive towards the North + + + + + Lon (°) + + + Longitude in degrees. Positive towards the East + + Updated @@ -1025,6 +1169,14 @@ Number of ADS-B frames received from this aircraft + + + TIS-B + + + Number of TIS-B frames received with this aircrafts ICAO + + Correlation diff --git a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp index a6affa5a9..99845f293 100644 --- a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp @@ -26,7 +26,7 @@ // Map main ADS-B table column numbers to combo box indicies std::vector ADSBDemodNotificationDialog::m_columnMap = { ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL, - ADSB_COL_ALTITUDE, ADSB_COL_SPEED, ADSB_COL_RANGE, + 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 }; @@ -118,7 +118,7 @@ void ADSBDemodNotificationDialog::addRow(ADSBDemodSettings::NotificationSettings match->addItem("Callsign"); match->addItem("Aircraft"); match->addItem("Alt (ft)"); - match->addItem("Spd (kn)"); + match->addItem("GS (kn)"); match->addItem("D (km)"); match->addItem("Cat"); match->addItem("Status"); diff --git a/plugins/channelrx/demodadsb/adsbdemodreport.h b/plugins/channelrx/demodadsb/adsbdemodreport.h index 1b313be7f..9885b18ab 100644 --- a/plugins/channelrx/demodadsb/adsbdemodreport.h +++ b/plugins/channelrx/demodadsb/adsbdemodreport.h @@ -39,15 +39,17 @@ public: QDateTime getDateTime() const { return m_dateTime; } float getPreambleCorrelation() const { return m_preambleCorrelation; } float getCorrelationOnes() const { return m_correlationOnes; } + unsigned getCRC() const { return m_crc; } static MsgReportADSB* create( QByteArray data, float preambleCorrelation, float correlationOnes, - QDateTime dateTime + QDateTime dateTime, + unsigned crc ) { - return new MsgReportADSB(data, preambleCorrelation, correlationOnes, dateTime); + return new MsgReportADSB(data, preambleCorrelation, correlationOnes, dateTime, crc); } private: @@ -55,18 +57,21 @@ public: QDateTime m_dateTime; float m_preambleCorrelation; float m_correlationOnes; + unsigned m_crc; MsgReportADSB( QByteArray data, float preambleCorrelation, float correlationOnes, - QDateTime dateTime + QDateTime dateTime, + unsigned crc ) : Message(), m_data(data), m_dateTime(dateTime), m_preambleCorrelation(preambleCorrelation), - m_correlationOnes(correlationOnes) + m_correlationOnes(correlationOnes), + m_crc(crc) { } }; diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp index e2ee3c3ab..5561296c3 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp @@ -76,7 +76,7 @@ void ADSBDemodSettings::resetToDefaults() m_tableFontSize = 9; m_displayDemodStats = false; m_correlateFullPreamble = true; - m_demodModeS = false; + m_demodModeS = true; m_deviceIndex = -1; m_autoResizeTableColumns = false; m_interpolatorPhaseSteps = 4; // Higher than these two values will struggle to run in real-time @@ -264,7 +264,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data) d.readS32(26, &m_tableFontSize, 9); d.readBool(27, &m_displayDemodStats, false); d.readBool(28, &m_correlateFullPreamble, true); - d.readBool(29, &m_demodModeS, false); + d.readBool(29, &m_demodModeS, true); d.readBool(30, &m_autoResizeTableColumns, false); d.readS32(31, &m_interpolatorPhaseSteps, 4); d.readFloat(32, &m_interpolatorTapsPerPhase, 3.5f); diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.h b/plugins/channelrx/demodadsb/adsbdemodsettings.h index dc9fa0a65..c8f0b7650 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.h +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.h @@ -29,43 +29,62 @@ class Serializable; // Number of columns in the table -#define ADSBDEMOD_COLUMNS 34 +#define ADSBDEMOD_COLUMNS 53 // ADS-B table columns -#define ADSB_COL_ICAO 0 -#define ADSB_COL_CALLSIGN 1 -#define ADSB_COL_MODEL 2 -#define ADSB_COL_AIRLINE 3 -#define ADSB_COL_ALTITUDE 4 -#define ADSB_COL_SPEED 5 -#define ADSB_COL_HEADING 6 -#define ADSB_COL_VERTICALRATE 7 -#define ADSB_COL_RANGE 8 -#define ADSB_COL_AZEL 9 -#define ADSB_COL_LATITUDE 10 -#define ADSB_COL_LONGITUDE 11 -#define ADSB_COL_CATEGORY 12 -#define ADSB_COL_STATUS 13 -#define ADSB_COL_SQUAWK 14 -#define ADSB_COL_REGISTRATION 15 -#define ADSB_COL_COUNTRY 16 -#define ADSB_COL_REGISTERED 17 -#define ADSB_COL_MANUFACTURER 18 -#define ADSB_COL_OWNER 19 -#define ADSB_COL_OPERATOR_ICAO 20 -#define ADSB_COL_TIME 21 -#define ADSB_COL_FRAMECOUNT 22 -#define ADSB_COL_CORRELATION 23 -#define ADSB_COL_RSSI 24 -#define ADSB_COL_FLIGHT_STATUS 25 -#define ADSB_COL_DEP 26 -#define ADSB_COL_ARR 27 -#define ADSB_COL_STD 28 -#define ADSB_COL_ETD 29 -#define ADSB_COL_ATD 30 -#define ADSB_COL_STA 31 -#define ADSB_COL_ETA 32 -#define ADSB_COL_ATA 33 +#define ADSB_COL_ICAO 0 +#define ADSB_COL_CALLSIGN 1 +#define ADSB_COL_MODEL 2 +#define ADSB_COL_AIRLINE 3 +#define ADSB_COL_COUNTRY 4 +#define ADSB_COL_GROUND_SPEED 5 +#define ADSB_COL_TRUE_AIRSPEED 6 +#define ADSB_COL_INDICATED_AIRSPEED 7 +#define ADSB_COL_MACH 8 +#define ADSB_COL_SEL_ALTITUDE 9 +#define ADSB_COL_ALTITUDE 10 +#define ADSB_COL_VERTICALRATE 11 +#define ADSB_COL_SEL_HEADING 12 +#define ADSB_COL_HEADING 13 +#define ADSB_COL_TURNRATE 14 +#define ADSB_COL_ROLL 15 +#define ADSB_COL_RANGE 16 +#define ADSB_COL_AZEL 17 +#define ADSB_COL_CATEGORY 18 +#define ADSB_COL_STATUS 19 +#define ADSB_COL_SQUAWK 20 +#define ADSB_COL_REGISTRATION 21 +#define ADSB_COL_REGISTERED 22 +#define ADSB_COL_MANUFACTURER 23 +#define ADSB_COL_OWNER 24 +#define ADSB_COL_OPERATOR_ICAO 25 +#define ADSB_COL_AP 26 +#define ADSB_COL_V_MODE 27 +#define ADSB_COL_L_MODE 28 +#define ADSB_COL_BARO 29 +#define ADSB_COL_HEADWIND 30 +#define ADSB_COL_EST_AIR_TEMP 31 +#define ADSB_COL_WIND_SPEED 32 +#define ADSB_COL_WIND_DIR 33 +#define ADSB_COL_STATIC_PRESSURE 34 +#define ADSB_COL_STATIC_AIR_TEMP 35 +#define ADSB_COL_HUMIDITY 36 +#define ADSB_COL_LATITUDE 37 +#define ADSB_COL_LONGITUDE 38 +#define ADSB_COL_TIME 39 +#define ADSB_COL_FRAMECOUNT 40 +#define ADSB_COL_TIS_B 41 +#define ADSB_COL_CORRELATION 42 +#define ADSB_COL_RSSI 43 +#define ADSB_COL_FLIGHT_STATUS 44 +#define ADSB_COL_DEP 45 +#define ADSB_COL_ARR 46 +#define ADSB_COL_STD 47 +#define ADSB_COL_ETD 48 +#define ADSB_COL_ATD 49 +#define ADSB_COL_STA 50 +#define ADSB_COL_ETA 51 +#define ADSB_COL_ATA 52 struct ADSBDemodSettings { diff --git a/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp b/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp index c39dd37fa..f3bf5f09e 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsinkworker.cpp @@ -205,7 +205,8 @@ void ADSBDemodSinkWorker::run() QByteArray((char*)data, sizeof(data)), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer)); + rxDateTime(firstIdx, readBuffer), + m_crc.get()); m_sink->getMessageQueueToGUI()->push(msg); } // Pass to worker to feed to other servers @@ -215,7 +216,8 @@ void ADSBDemodSinkWorker::run() QByteArray((char*)data, sizeof(data)), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer)); + rxDateTime(firstIdx, readBuffer), + m_crc.get()); m_sink->getMessageQueueToWorker()->push(msg); } } @@ -240,22 +242,37 @@ void ADSBDemodSinkWorker::run() int crc = m_crc.get(); // For DF11, the last 7 bits may have an address/interogration indentifier (II) // XORed in, so we ignore those bits - if ((parity == crc) || ((df == 11) && (parity & 0xffff80) == (crc & 0xffff80))) + // DF4 / DF5 / DF20 / DF21 have full address XORed in to parity. GUI will check if valid + if ((parity == crc) || ((df == 11) && (parity & 0xffff80) == (crc & 0xffff80)) || (df == 4) || (df == 5) || (df == 20) || (df == 21)) { m_demodStats.m_modesFrames++; - // Pass to worker to feed to other servers - if (m_sink->getMessageQueueToWorker()) + // Pass to GUI + if (m_sink->getMessageQueueToGUI()) { ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( QByteArray((char*)data, sizeof(data)), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, - rxDateTime(firstIdx, readBuffer)); + rxDateTime(firstIdx, readBuffer), + m_crc.get()); + m_sink->getMessageQueueToGUI()->push(msg); + } + // Pass to worker to feed to other servers + if (m_sink->getMessageQueueToWorker() && (parity == crc)) + { + 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); } } else + { m_demodStats.m_crcFails++; + } } else m_demodStats.m_typeFails++; diff --git a/plugins/channelrx/demodadsb/readme.md b/plugins/channelrx/demodadsb/readme.md index 1b52d390b..196fb7313 100644 --- a/plugins/channelrx/demodadsb/readme.md +++ b/plugins/channelrx/demodadsb/readme.md @@ -2,11 +2,9 @@

Introduction

-The top and bottom bars of the device window are described [here](../../../sdrgui/device/readme.md) +The ADS-B demodulator plugin can be used to receive and display ADS-B and Mode-S frames broadcast by aircraft. These contain information about an aircraft, such as position, altitude, heading and speed, broadcast by aircraft on 1090MHz. ADS-B and Mode-S frames have a chip rate of 2Mchip/s, so the baseband sample rate should be set to 2MSa/s or greater. -The ADS-B demodulator plugin can be used to receive and display ADS-B aircraft information. This is information about an aircraft, such as position, altitude, heading and speed, broadcast by aircraft on 1090MHz, in the 1090ES (Extended Squitter) format. 1090ES frames have a chip rate of 2Mchip/s, so the baseband sample rate should be set to be greater than 2MSa/s. - -As well as displaying information received via ADS-B, the plugin can also combine information from a number of databases to display more information about the aircraft and flight, airports and weather. +As well as displaying information received via ADS-B and Mode-S, the plugin can also combine information from a number of databases to display more information about the aircraft and flight, airports and weather. ![ADS-B Demodulator plugin GUI](../../../doc/img/ADSBDemod_plugin.png) @@ -42,13 +40,11 @@ This is the bandwidth in MHz of the channel signal before demodulation. This specifies the channel sample rate the demodulator uses. Values of 2M-12MSa/s are supported, 2MSa/s steps. Ideally, this should be set to the same value as the baseband sample rate (the sample rate received from the radio). If it is different from the baseband sample rate, interpolation or decimation will be performed as needed to match the rates. However, interpolation currently requires a significant amount of processing power and may result in dropped samples. -2 MSa/s should give decent decodes. Higher rates may be experienced with if your hardware allows it (radio device and processing power). However the higher the rate the more processing power required. +2 MSa/s should give decent decodes. Higher rates may be experimented with if your hardware allows it (radio device and processing power). However the higher the rate the more processing power required. -Higher channel sample rates may help decode more frames, but will require more processing power. +

6: S - Demodulate Mode-S frames

-

6: S - Demodulate all Mode-S frames

- -Checking the S button will enable demodulation of all Mode-S frames, not just ADS-B. Mode-S frames will not effect the data displayed in the table or map, but can be feed to aggregators. +Checking the S button will enable demodulation of Mode-S ELS (Elementary Surveillance), EHS (Enhanced Surveillance) and MRAR (Meteorological Routine Air Report) frames.

7: FP - Correlate Against Full Preamable

@@ -182,11 +178,18 @@ In the Speech and Command strings, variables can be used to substitute in data f * ${icao}, * ${callsign} * ${aircraft} -* ${latitude} -* ${longitude} +* ${verticalRate} +* ${gs} +* ${tas} +* ${is} +* ${mach} +* ${selAltitude} * ${altitude} -* ${speed} +* $(verticalRate} +* ${selHeading} * ${heading} +* ${turnRate} +* ${roll} * ${range} * ${azel} * ${category} @@ -196,6 +199,18 @@ In the Speech and Command strings, variables can be used to substitute in data f * ${manufacturer} * ${owner} * ${operator} +* ${ap} +* ${vMode} +* ${lMode} +* ${baro} +* ${headwind} +* ${windSpeed} +* ${windDirection} +* ${staticPressure} +* ${staticAirTemperature} +* ${humidity} +* ${latitude} +* ${longitude} * ${rssi} * ${flightstatus} * ${departure} @@ -225,32 +240,52 @@ Specify the SDRangel device set that will be have its centre frequency set when

ADS-B Data

-The table displays the decoded ADS-B data for each aircraft along side data available for the aircraft from the Opensky Network database (DB) and aviationstack (API). The data is not all able to be transmitted in a single ADS-B frame, so the table displays an amalgamation of the latest received data of each type. +The table displays the decoded ADS-B and Mode-S data for each aircraft along side data available for the aircraft from the Opensky Network database (DB) and aviationstack (API). The data is not all able to be transmitted in a single ADS-B frame, so the table displays an amalgamation of the latest received data of each type. ![ADS-B Demodulator Data](../../../doc/img/ADSBDemod_plugin_table.png) * ICAO ID - 24-bit hexadecimal ICAO aircraft address. This is unique for each aircraft. (ADS-B) -* Callsign - Aircraft callsign (which is sometimes also the flight number). (ADS-B) +* Callsign - Aircraft callsign (which is sometimes also the flight number). (ADS-B / Mode-S) * Aircraft - The aircraft model. (DB) * Airline - The logo of the operator of the aircraft (or owner if no operator known). (DB) -* Altitude (Alt) - Altitude in feet or metres. (ADS-B) -* Speed (Spd) - Speed (either ground speed, indicated airspeed, or true airspeed) in knots or kph. (ADS-B) -* Heading (Hd) - The direction the aircraft is heading, in degrees. (ADS-B) -* Vertical rate (VR) - The vertical climb rate (or descent rate if negative) in feet/minute or m/s. (ADS-B) -* Distance (D) - The distance to the aircraft from the receiving antenna in kilometres. The location of the receiving antenna is set under the Preferences > My Position menu. +* Country - The flag of the country the aircraft is registered in. (DB) +* GS - Groundspeed in knots or kilometers per hour. (Mode-S) +* TAS - True airspeed in knots or kilometers per hour. (Mode-S) +* IAS - Indicated airspeed in knots or kilometers per hour. (Mode-S) +* Mach - Mach number. (Mode-S) +* Sel Alt - Selected altitude (as set on MCP/FCU or by FMS) in feet or metres. (ADS-B / Mode-S) +* Alt - Altitude in feet or metres. (ADS-B / Mode-S) +* VR - The vertical climb rate (or descent rate if negative) in feet/minute or m/s. (ADS-B / Mode-S) +* Sel Hd - Selected heading (as set on MCP/FCU or by FMS) in degrees. (ADS-B / Mode-S) +* Hd - The aircraft heading or track, in degrees. (ADS-B / Mode-S) +* TR - Turn rate in degrees per second. (Mode-S) +* Roll - Roll angle in degrees. Positive is right wing down. (Mode-S) +* D - The distance to the aircraft from the receiving antenna in kilometres. The location of the receiving antenna is set under the Preferences > My Position menu. * Az/El - The azimuth and elevation angles to the aircraft from the receiving antenna in degrees. These values can be sent to a rotator controller Feature plugin to track the aircraft. -* Latitude (Lat) - Vertical position coordinate, in decimal degrees. (ADS-B) -* Longitude (Lon) - Horizontal position coordinate, in decimal degrees. (ADS-B) * Category (Cat) - The vehicle category, such as Light, Large, Heavy or Rotorcraft. (ADS-B) * Status - The status of the flight, including if there is an emergency. (ADS-B) -* Squawk - The squawk code (Mode-A transponder code). (ADS-B) +* Squawk - The squawk code (Mode-A transponder code). (ADS-B / Mode-S) * Registration (Reg) - The registration number of the aircraft. (DB) -* Country - The flag of the country the aircraft is registered in. (DB) * Registered - The date when the aircraft was registered. (DB) * Manufacturer - The manufacturer of the aircraft. (DB) * Owner - The owner of the aircraft. (DB) -* Updated - The local time at which the last ADS-B message was received. -* RX Frames - A count of the number of ADS-B frames received from this aircraft. +* Operator - The operator ICAO. (DB) +* AP - Whether autopilot is enabled. (ADS-B) +* V Mode - Vertical navigation mode. This may be VNAV (Vertical navigation), HOLD (Altitude hold) or APP (Approach). (ADS-B / Mode-S) +* L Mode - Lateral navigation mode. This may be LNAV (Lateral navigation) or APP (Approach). (ADS-B) +* Baro - Baro setting in mb. (ADS-B / Mode-S) +* H Wnd - Headwind in knots or kilometers per hour. Negative values indicate a tailwind. (Derived from Mode-S) +* OAT - Outside air temperature in degrees Celsuis, estimated from Mach and TAS. (Derived from Mode-S) +* Wnd - Wind speed in knots or kilometers per hour. (Mode-S) +* Wnd - Wind direction in degrees. (Mode-S) +* P - Average static air pressure in hectopascals. (Mode-S) +* T - Static air temperature in degrees Celsuis. (Mode-S) +* U - Humidity in percent. (Mode-S) +* Latitude (Lat) - Vertical position coordinate, in decimal degrees. North positive. (ADS-B) +* Longitude (Lon) - Horizontal position coordinate, in decimal degrees. East positive. (ADS-B) +* Updated - The local time at which the last message was received. (ADS-B / Mode-S) +* RX Frames - A count of the number of frames received from this aircraft. (ADS-B / Mode-S) +* TIS-B - A count of the number of TIS-B frames for this aircraft. (ADS-B) * Correlation - Displays the minimum, average and maximum of the preamable correlation in dB for each received frame. These values can be used to help select a threshold setting. This correlation value is the ratio between the presence and absence of the signal corresponding to the "ones" and the "zeros" of the sync word adjusted by the bits ratio. It can be interpreted as a SNR estimation. * RSSI - This Received Signal Strength Indicator is based on the signal power during correlation estimation. This is the power sum during the expected presence of the signal i.e. the "ones" of the sync word. * Flight status - scheduled, active, landed, cancelled, incident or diverted. (API) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index c28073864..ddcb346ee 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -191,6 +191,7 @@ set(sdrbase_SOURCES util/messagequeue.cpp util/morse.cpp util/openaip.cpp + util/osndb.cpp util/planespotters.cpp util/png.cpp util/prettyprint.cpp @@ -409,6 +410,7 @@ set(sdrbase_HEADERS util/morse.h util/movingaverage.h util/openaip.h + util/osndb.h util/planespotters.h util/png.h util/prettyprint.h diff --git a/sdrbase/util/osndb.cpp b/sdrbase/util/osndb.cpp new file mode 100644 index 000000000..a8ac992cd --- /dev/null +++ b/sdrbase/util/osndb.cpp @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "util/osndb.h" + +QHash AircraftInformation::m_airlineIcons; +QHash AircraftInformation::m_airlineMissingIcons; +QHash AircraftInformation::m_flagIcons; +QHash *AircraftInformation::m_prefixMap; +QHash *AircraftInformation::m_militaryMap; +QMutex AircraftInformation::m_mutex; diff --git a/plugins/channelrx/demodadsb/osndb.h b/sdrbase/util/osndb.h similarity index 67% rename from plugins/channelrx/demodadsb/osndb.h rename to sdrbase/util/osndb.h index 87533550c..67486b60c 100644 --- a/plugins/channelrx/demodadsb/osndb.h +++ b/sdrbase/util/osndb.h @@ -24,15 +24,20 @@ #include #include #include +#include +#include +#include +#include #include #include #include "util/csv.h" +#include "export.h" #define OSNDB_URL "https://opensky-network.org/datasets/metadata/aircraftDatabase.zip" -struct AircraftInformation { +struct SDRBASE_API AircraftInformation { int m_icao; QString m_registration; @@ -43,6 +48,23 @@ struct AircraftInformation { QString m_operatorICAO; QString m_registered; + static QHash m_airlineIcons; // Hashed on airline ICAO + static QHash m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for + static QHash m_flagIcons; // Hashed on country + static QHash *m_prefixMap; // Registration to country (flag name) + static QHash *m_militaryMap; // Operator airforce to military (flag name) + static QMutex m_mutex; + + static void init() + { + QMutexLocker locker(&m_mutex); + + // Read registration prefix to country map + m_prefixMap = CSV::hash(":/flags/regprefixmap.csv"); + // Read operator air force to military map + m_militaryMap = CSV::hash(":/flags/militarymap.csv"); + } + // Read OpenSky Network CSV file // This is large and contains lots of data we don't want, so we convert to // a smaller version to speed up loading time @@ -219,6 +241,20 @@ struct AircraftInformation { return aircraftInfo; } + // Create hash table using registration as key + static QHash *registrationHash(const QHash *in) + { + QHash *out = new QHash(); + QHashIterator i(*in); + while (i.hasNext()) + { + i.next(); + AircraftInformation *info = i.value(); + out->insert(info->m_registration, info); + } + return out; + } + // Write a reduced size and validated version of the DB, so it loads quicker static bool writeFastDB(const QString &filename, QHash *aircraftInfo) { @@ -313,6 +349,169 @@ struct AircraftInformation { return aircraftInfo; } + // Get flag based on registration + QString getFlag() const + { + QString flag; + if (m_prefixMap) + { + int idx = m_registration.indexOf('-'); + if (idx >= 0) + { + QString prefix; + + // Some countries use AA-A - try these first as first letters are common + prefix = m_registration.left(idx + 2); + if (m_prefixMap->contains(prefix)) + { + flag = m_prefixMap->value(prefix); + } + else + { + // Try letters before '-' + prefix = m_registration.left(idx); + if (m_prefixMap->contains(prefix)) { + flag = m_prefixMap->value(prefix); + } + } + } + else + { + // No '-' Could be one of a few countries or military. + // See: https://en.wikipedia.org/wiki/List_of_aircraft_registration_prefixes + if (m_registration.startsWith("N")) { + flag = m_prefixMap->value("N"); // US + } else if (m_registration.startsWith("JA")) { + flag = m_prefixMap->value("JA"); // Japan + } else if (m_registration.startsWith("HL")) { + flag = m_prefixMap->value("HL"); // Korea + } else if (m_registration.startsWith("YV")) { + flag = m_prefixMap->value("YV"); // Venezuela + } else if ((m_militaryMap != nullptr) && (m_militaryMap->contains(m_operator))) { + flag = m_militaryMap->value(m_operator); + } + } + } + return flag; + } + + static QString getOSNDBZipFilename() + { + return getDataDir() + "/aircraftDatabase.zip"; + } + + static QString getOSNDBFilename() + { + return getDataDir() + "/aircraftDatabase.csv"; + } + + static QString getFastDBFilename() + { + return getDataDir() + "/aircraftDatabaseFast.csv"; + } + + static QString getDataDir() + { + // Get directory to store app data in (aircraft & airport databases and user-definable icons) + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + // First dir is writable + return locations[0]; + } + + static QString getAirlineIconPath(const QString &operatorICAO) + { + QString endPath = QString("/airlinelogos/%1.bmp").arg(operatorICAO); + // Try in user directory first, so they can customise + QString userIconPath = getDataDir() + endPath; + QFile file(userIconPath); + if (file.exists()) + { + return userIconPath; + } + else + { + // Try in resources + QString resourceIconPath = ":" + endPath; + QResource resource(resourceIconPath); + if (resource.isValid()) + { + return resourceIconPath; + } + } + return QString(); + } + + // Try to find an airline logo based on ICAO + static QIcon *getAirlineIcon(const QString &operatorICAO) + { + if (m_airlineIcons.contains(operatorICAO)) + { + return m_airlineIcons.value(operatorICAO); + } + else + { + QIcon *icon = nullptr; + QString path = getAirlineIconPath(operatorICAO); + if (!path.isEmpty()) + { + icon = new QIcon(path); + m_airlineIcons.insert(operatorICAO, icon); + } + else + { + if (!m_airlineMissingIcons.contains(operatorICAO)) + { + qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO; + m_airlineMissingIcons.insert(operatorICAO, true); + } + } + return icon; + } + } + + static QString getFlagIconPath(const QString &country) + { + QString endPath = QString("/flags/%1.bmp").arg(country); + // Try in user directory first, so they can customise + QString userIconPath = getDataDir() + endPath; + QFile file(userIconPath); + if (file.exists()) + { + return userIconPath; + } + else + { + // Try in resources + QString resourceIconPath = ":" + endPath; + QResource resource(resourceIconPath); + if (resource.isValid()) + { + return resourceIconPath; + } + } + return QString(); + } + + // Try to find an flag logo based on a country + static QIcon *getFlagIcon(const QString &country) + { + if (m_flagIcons.contains(country)) + { + return m_flagIcons.value(country); + } + else + { + QIcon *icon = nullptr; + QString path = getFlagIconPath(country); + if (!path.isEmpty()) + { + icon = new QIcon(path); + m_flagIcons.insert(country, icon); + } + return icon; + } + } + }; #endif diff --git a/sdrbase/util/planespotters.cpp b/sdrbase/util/planespotters.cpp index b9aa24467..39600a5d5 100644 --- a/sdrbase/util/planespotters.cpp +++ b/sdrbase/util/planespotters.cpp @@ -57,7 +57,7 @@ void PlaneSpotters::getAircraftPhoto(const QString& icao) { // Create a new photo hash table entry PlaneSpottersPhoto *photo = new PlaneSpottersPhoto(); - photo->m_icao = icao; + photo->m_id = icao; m_photos.insert(icao, photo); // Fetch from network @@ -69,13 +69,35 @@ void PlaneSpotters::getAircraftPhoto(const QString& icao) } } +void PlaneSpotters::getAircraftPhotoByRegistration(const QString& registration) +{ + if (m_photos.contains(registration)) + { + emit aircraftPhoto(m_photos[registration]); + } + else + { + // Create a new photo hash table entry + PlaneSpottersPhoto *photo = new PlaneSpottersPhoto(); + photo->m_id = registration; + m_photos.insert(registration, photo); + + // Fetch from network + QUrl url(QString("https://api.planespotters.net/pub/photos/reg/%1").arg(registration)); + QNetworkRequest request(url); + request.setRawHeader("User-Agent", "SDRangel/1.0"); // Get 403 error without this + request.setOriginatingObject(photo); + m_networkManager->get(request); + } +} + void PlaneSpotters::handleReply(QNetworkReply* reply) { if (reply) { if (!reply->error()) { - if (reply->url().path().startsWith("/pub/photos/hex")) { + if (reply->url().path().startsWith("/pub/photos/hex") || reply->url().path().startsWith("/pub/photos/reg")) { parseJson((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll()); } else { parsePhoto((PlaneSpottersPhoto *)reply->request().originatingObject(), reply->readAll()); @@ -95,7 +117,9 @@ void PlaneSpotters::handleReply(QNetworkReply* reply) void PlaneSpotters::parsePhoto(PlaneSpottersPhoto *photo, QByteArray bytes) { - photo->m_pixmap.loadFromData(bytes); + if (!photo->m_pixmap.loadFromData(bytes)) { + qDebug() << "PlaneSpotters::parsePhoto: Failed to loadFromData - " << bytes.size() << "bytes of data" ; + } emit aircraftPhoto(photo); } diff --git a/sdrbase/util/planespotters.h b/sdrbase/util/planespotters.h index 11175b486..f4f3d42bd 100644 --- a/sdrbase/util/planespotters.h +++ b/sdrbase/util/planespotters.h @@ -38,13 +38,11 @@ class SDRBASE_API PlaneSpottersPhoto : public QObject { }; public: - QString m_icao; QString m_id; Thumbnail m_thumbnail; Thumbnail m_largeThumbnail; QString m_link; - QString m_photographer -; + QString m_photographer; QPixmap m_pixmap; }; @@ -61,6 +59,7 @@ public: void getAircraftPhoto(const QString& icao); + void getAircraftPhotoByRegistration(const QString& registration); signals: void aircraftPhoto(const PlaneSpottersPhoto *photo); // Called when photo is available. diff --git a/sdrbase/util/units.h b/sdrbase/util/units.h index d82a1a0b7..a813651d8 100644 --- a/sdrbase/util/units.h +++ b/sdrbase/util/units.h @@ -46,6 +46,11 @@ public: return metres * 3.28084f; } + static inline int metresToIntegerFeet(float metres) + { + return (int)std::round(metresToFeet(metres)); + } + static inline float nauticalMilesToMetres(float nauticalMiles) { return nauticalMiles * 1855.0f; @@ -76,6 +81,11 @@ public: return (int)std::round(knotsToMPH(knots)); } + static float knotsToMetresPerSecond(float knots) + { + return knots * 0.514444f; + } + static float kmpsToKPH(float kps) { return kps * (60.0 * 60.0);