diff --git a/doc/img/ADSBDemod_plugin_notifications.png b/doc/img/ADSBDemod_plugin_notifications.png index 1c9a11816..2a16f3a72 100644 Binary files a/doc/img/ADSBDemod_plugin_notifications.png and b/doc/img/ADSBDemod_plugin_notifications.png differ diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp index 4b29c5b17..f93bbfa6b 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp @@ -23,7 +23,7 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog( int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize, bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats, - bool autoResizeTableColumns, QWidget* parent) : + bool autoResizeTableColumns, const QString& apiKey, QWidget* parent) : QDialog(parent), m_fontName(fontName), m_fontSize(fontSize), @@ -37,6 +37,7 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog( ui->units->setCurrentIndex((int)siUnits); ui->displayStats->setChecked(displayDemodStats); ui->autoResizeTableColumns->setChecked(autoResizeTableColumns); + ui->apiKey->setText(apiKey); } ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog() @@ -53,6 +54,7 @@ void ADSBDemodDisplayDialog::accept() m_siUnits = ui->units->currentIndex() == 0 ? false : true; m_displayDemodStats = ui->displayStats->isChecked(); m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked(); + m_apiKey = ui->apiKey->text(); QDialog::accept(); } diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.h b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.h index c420ff7c5..38820d0d2 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.h +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.h @@ -27,7 +27,7 @@ class ADSBDemodDisplayDialog : public QDialog { public: explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize, bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats, - bool autoResizeTableColumns, QWidget* parent = 0); + bool autoResizeTableColumns, const QString& apiKey, QWidget* parent = 0); ~ADSBDemodDisplayDialog(); int m_removeTimeout; @@ -39,6 +39,7 @@ public: int m_fontSize; bool m_displayDemodStats; bool m_autoResizeTableColumns; + QString m_apiKey; private slots: void accept(); diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui index 50690da43..57beefb6b 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui @@ -6,8 +6,8 @@ 0 0 - 351 - 289 + 417 + 287 @@ -23,17 +23,31 @@ - - - - Select a font for the table - + + - Select... + Display airports with size - + + + + The units to use for altitude, speed and climb rate + + + + ft, kn, ft/min + + + + + m, kph, m/s + + + + + Aircraft timeout (s) @@ -50,70 +64,6 @@ - - - - When checked, heliports are displayed on the map - - - Display heliports - - - - - - - How long in seconds after not receiving any frames will an aircraft be removed from the table and map - - - 1000000 - - - - - - - Display airports with size - - - - - - - Airport display distance (km) - - - - - - - Table font - - - - - - - - 0 - 0 - - - - Display demodulator statistics - - - Display demodulator statistics - - - - - - - Units - - - @@ -136,31 +86,129 @@ - + + + + Units + + + + + + + Select a font for the table + + + Select... + + + + + + + How long in seconds after not receiving any frames will an aircraft be removed from the table and map + + + 1000000 + + + + + + + Airport display distance (km) + + + + + + + Table font + + + + + + + avaitionstack API key + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + Resize the columns in the table after an aircraft is added to it + + + + + + + Resize columns after adding aircraft - - + + - The units to use for altitude, speed and climb rate + When checked, heliports are displayed on the map + + + + + + + + + + + 0 + 0 + + + + Display demodulator statistics + + + + + + + + + + Display heliports + + + + + + + Display demodulator statistics + + + + + + + aviationstack.com API key for accessing flight information - - - ft, kn, ft/min - - - - - m, kph, m/s - - diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index 73fbc2b82..596adaedd 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -160,9 +160,9 @@ QString Aircraft::getImage() QString Aircraft::getText(bool all) { QStringList list; - if (m_flight.length() > 0) + if (m_callsign.length() > 0) { - list.append(QString("Flight: %1").arg(m_flight)); + list.append(QString("Callsign: %1").arg(m_callsign)); } else { @@ -215,10 +215,54 @@ QString Aircraft::getText(bool all) desc = QString("Descending: %1 (%2)").arg(rate).arg(units); list.append(QString(desc)); } - if ((m_status.length() > 0) && m_status.compare("No emergency")) - { + if ((m_status.length() > 0) && m_status.compare("No emergency")) { list.append(m_status); } + + QString flightStatus = m_flightStatusItem->text(); + if (!flightStatus.isEmpty()) { + list.append(QString("Flight status: %1").arg(flightStatus)); + } + QString dep = m_depItem->text(); + if (!dep.isEmpty()) { + list.append(QString("Departed: %1").arg(dep)); + } + QString std = m_stdItem->text(); + if (!std.isEmpty()) { + list.append(QString("STD: %1").arg(std)); + } + QString atd = m_atdItem->text(); + if (!atd.isEmpty()) + { + list.append(QString("ATD: %1").arg(atd)); + } + else + { + QString etd = m_etdItem->text(); + if (!etd.isEmpty()) { + list.append(QString("ETD: %1").arg(etd)); + } + } + QString arr = m_arrItem->text(); + if (!arr.isEmpty()) { + list.append(QString("Arrival: %1").arg(arr)); + } + QString sta = m_staItem->text(); + if (!sta.isEmpty()) { + list.append(QString("STA: %1").arg(sta)); + } + QString ata = m_ataItem->text(); + if (!ata.isEmpty()) + { + list.append(QString("ATA: %1").arg(ata)); + } + else + { + QString eta = m_etaItem->text(); + if (!eta.isEmpty()) { + list.append(QString("ETA: %1").arg(eta)); + } + } } return list.join("\n"); } @@ -501,6 +545,8 @@ QIcon *ADSBDemodGUI::getAirlineIcon(const QString &operatorICAO) icon = new QIcon(":" + endPath); m_airlineIcons.insert(operatorICAO, icon); } + else + qDebug() << "ADSBDemodGUI: No airline logo for " << operatorICAO; } return icon; } @@ -611,7 +657,7 @@ void ADSBDemodGUI::handleADSB( int row = ui->adsbData->rowCount(); ui->adsbData->setRowCount(row + 1); ui->adsbData->setItem(row, ADSB_COL_ICAO, aircraft->m_icaoItem); - ui->adsbData->setItem(row, ADSB_COL_FLIGHT, aircraft->m_flightItem); + ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, aircraft->m_callsignItem); ui->adsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, aircraft->m_airlineItem); ui->adsbData->setItem(row, ADSB_COL_ALTITUDE, aircraft->m_altitudeItem); @@ -635,6 +681,15 @@ void ADSBDemodGUI::handleADSB( ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, aircraft->m_adsbFrameCountItem); ui->adsbData->setItem(row, ADSB_COL_CORRELATION, aircraft->m_correlationItem); ui->adsbData->setItem(row, ADSB_COL_RSSI, aircraft->m_rssiItem); + ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, aircraft->m_flightStatusItem); + ui->adsbData->setItem(row, ADSB_COL_DEP, aircraft->m_depItem); + ui->adsbData->setItem(row, ADSB_COL_ARR, aircraft->m_arrItem); + ui->adsbData->setItem(row, ADSB_COL_STD, aircraft->m_stdItem); + ui->adsbData->setItem(row, ADSB_COL_ETD, aircraft->m_etdItem); + ui->adsbData->setItem(row, ADSB_COL_ATD, aircraft->m_atdItem); + 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); // Look aircraft up in database if (m_aircraftInfo != nullptr) { @@ -764,8 +819,39 @@ void ADSBDemodGUI::handleADSB( callsign[i] = idMap[c[i]]; callsign[8] = '\0'; - aircraft->m_flight = QString(callsign); - aircraft->m_flightItem->setText(aircraft->m_flight); + aircraft->m_callsign = QString(callsign).trimmed(); + aircraft->m_callsignItem->setText(aircraft->m_callsign); + + // Attempt to map callsign to flight number + 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 = ""; + } } else if (((tc >= 5) && (tc <= 18)) || ((tc >= 20) && (tc <= 22))) { @@ -1136,11 +1222,11 @@ void ADSBDemodGUI::checkStaticNotification(Aircraft *aircraft) } if (!match.isEmpty()) { - //QRegularExpression regExp(m_settings.m_notificationSettings[i]->m_regExp); if (m_settings.m_notificationSettings[i]->m_regularExpression.isValid()) { if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch()) { + highlightAircraft(aircraft); if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) { speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech); } @@ -1160,7 +1246,7 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft) { for (int i = 0; i < m_settings.m_notificationSettings.size(); i++) { - if ( (m_settings.m_notificationSettings[i]->m_matchColumn == ADSB_COL_FLIGHT) + 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_RANGE) @@ -1172,8 +1258,8 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft) QString match; switch (m_settings.m_notificationSettings[i]->m_matchColumn) { - case ADSB_COL_FLIGHT: - match = aircraft->m_flightItem->data(Qt::DisplayRole).toString(); + case ADSB_COL_CALLSIGN: + match = aircraft->m_callsignItem->data(Qt::DisplayRole).toString(); break; case ADSB_COL_ALTITUDE: match = aircraft->m_altitudeItem->data(Qt::DisplayRole).toString(); @@ -1202,6 +1288,7 @@ void ADSBDemodGUI::checkDynamicNotification(Aircraft *aircraft) { if (m_settings.m_notificationSettings[i]->m_regularExpression.match(match).hasMatch()) { + highlightAircraft(aircraft); if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty()) { speechNotification(aircraft, m_settings.m_notificationSettings[i]->m_speech); } @@ -1231,7 +1318,7 @@ QString ADSBDemodGUI::subAircraftString(Aircraft *aircraft, const QString &strin { QString s = string; s = s.replace("${icao}", aircraft->m_icaoItem->data(Qt::DisplayRole).toString()); - s = s.replace("${flight}", aircraft->m_flightItem->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()); @@ -1246,6 +1333,15 @@ 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("${flightstatus}", aircraft->m_flightStatusItem->data(Qt::DisplayRole).toString()); + s = s.replace("${departure}", aircraft->m_depItem->data(Qt::DisplayRole).toString()); + s = s.replace("${arrival}", aircraft->m_arrItem->data(Qt::DisplayRole).toString()); + s = s.replace("${std}", aircraft->m_stdItem->data(Qt::DisplayRole).toString()); + s = s.replace("${etd}", aircraft->m_etdItem->data(Qt::DisplayRole).toString()); + s = s.replace("${atd}", aircraft->m_atdItem->data(Qt::DisplayRole).toString()); + s = s.replace("${sta}", aircraft->m_staItem->data(Qt::DisplayRole).toString()); + s = s.replace("${eta}", aircraft->m_etaItem->data(Qt::DisplayRole).toString()); + s = s.replace("${ata}", aircraft->m_ataItem->data(Qt::DisplayRole).toString()); return s; } @@ -1364,6 +1460,45 @@ void ADSBDemodGUI::on_notifications_clicked() } } +void ADSBDemodGUI::on_flightInfo_clicked() +{ + if (m_flightInformation) + { + // Selection mode is single, so only a single row should be returned + QModelIndexList indexList = ui->adsbData->selectionModel()->selectedRows(); + if (!indexList.isEmpty()) + { + int row = indexList.at(0).row(); + int icao = ui->adsbData->item(row, 0)->text().toInt(nullptr, 16); + if (m_aircraft.contains(icao)) + { + Aircraft *aircraft = m_aircraft.value(icao); + if (!aircraft->m_flight.isEmpty()) + { + // Download flight information + m_flightInformation->getFlightInformation(aircraft->m_flight); + } + else + { + qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No flight number for selected aircraft"; + } + } + else + { + qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No aircraft with icao " << icao; + } + } + else + { + qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No aircraft selected"; + } + } + else + { + qDebug() << "ADSBDemodGUI::on_flightInfo_clicked - No flight information service - have you set an API key?"; + } +} + void ADSBDemodGUI::on_adsbData_cellClicked(int row, int column) { (void) column; @@ -1387,12 +1522,12 @@ void ADSBDemodGUI::on_adsbData_cellDoubleClicked(int row, int column) { Aircraft *aircraft = m_aircraft.value(icao); - if (column == ADSB_COL_FLIGHT) + if (column == ADSB_COL_CALLSIGN) { - if (aircraft->m_flight.length() > 0) + if (!aircraft->m_callsign.isEmpty()) { // Search for flight on flightradar24 - QDesktopServices::openUrl(QUrl(QString("https://www.flightradar24.com/%1").arg(aircraft->m_flight.trimmed()))); + QDesktopServices::openUrl(QUrl(QString("https://www.flightradar24.com/%1").arg(aircraft->m_callsign))); } } else @@ -1876,7 +2011,8 @@ void ADSBDemodGUI::on_displaySettings_clicked() ADSBDemodDisplayDialog dialog(m_settings.m_removeTimeout, m_settings.m_airportRange, m_settings.m_airportMinimumSize, m_settings.m_displayHeliports, m_settings.m_siUnits, m_settings.m_tableFontName, m_settings.m_tableFontSize, - m_settings.m_displayDemodStats, m_settings.m_autoResizeTableColumns); + m_settings.m_displayDemodStats, m_settings.m_autoResizeTableColumns, + m_settings.m_apiKey); if (dialog.exec() == QDialog::Accepted) { bool unitsChanged = m_settings.m_siUnits != dialog.m_siUnits; @@ -1890,6 +2026,7 @@ void ADSBDemodGUI::on_displaySettings_clicked() m_settings.m_tableFontSize = dialog.m_fontSize; m_settings.m_displayDemodStats = dialog.m_displayDemodStats; m_settings.m_autoResizeTableColumns = dialog.m_autoResizeTableColumns; + m_settings.m_apiKey = dialog.m_apiKey; if (unitsChanged) m_aircraftModel.allAircraftUpdated(); @@ -2027,6 +2164,8 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb // Initialise text to speech engine m_speech = new QTextToSpeech(this); + m_flightInformation = nullptr; + updateDeviceSetList(); displaySettings(); applySettings(true); @@ -2042,6 +2181,11 @@ ADSBDemodGUI::~ADSBDemodGUI() delete a; ++i; } + if (m_flightInformation) + { + disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated); + delete m_flightInformation; + } } void ADSBDemodGUI::applySettings(bool force) @@ -2141,6 +2285,8 @@ void ADSBDemodGUI::displaySettings() if (!m_settings.m_displayDemodStats) ui->stats->setText(""); + initFlightInformation(); + blockApplySettings(false); } @@ -2242,7 +2388,7 @@ 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_FLIGHT, new QTableWidgetItem("Flight No.")); + 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)")); @@ -2266,31 +2412,96 @@ void ADSBDemodGUI::resizeTable() ui->adsbData->setItem(row, ADSB_COL_FRAMECOUNT, new QTableWidgetItem("Frames")); ui->adsbData->setItem(row, ADSB_COL_CORRELATION, new QTableWidgetItem("0.001/0.001/0.001")); ui->adsbData->setItem(row, ADSB_COL_RSSI, new QTableWidgetItem("-100.0")); + ui->adsbData->setItem(row, ADSB_COL_FLIGHT_STATUS, new QTableWidgetItem("scheduled")); + ui->adsbData->setItem(row, ADSB_COL_DEP, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_ARR, new QTableWidgetItem("WWWW")); + ui->adsbData->setItem(row, ADSB_COL_STD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_ETD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_ATD, new QTableWidgetItem("12:00 -1")); + ui->adsbData->setItem(row, ADSB_COL_STA, new QTableWidgetItem("12:00 +1")); + ui->adsbData->setItem(row, ADSB_COL_ETA, new QTableWidgetItem("12:00 +1")); + ui->adsbData->setItem(row, ADSB_COL_ATA, new QTableWidgetItem("12:00 +1")); ui->adsbData->resizeColumnsToContents(); - ui->adsbData->removeCellWidget(row, ADSB_COL_ICAO); - ui->adsbData->removeCellWidget(row, ADSB_COL_FLIGHT); - ui->adsbData->removeCellWidget(row, ADSB_COL_MODEL); - ui->adsbData->removeCellWidget(row, ADSB_COL_AIRLINE); - ui->adsbData->removeCellWidget(row, ADSB_COL_ALTITUDE); - ui->adsbData->removeCellWidget(row, ADSB_COL_SPEED); - ui->adsbData->removeCellWidget(row, ADSB_COL_HEADING); - ui->adsbData->removeCellWidget(row, ADSB_COL_VERTICALRATE); - ui->adsbData->removeCellWidget(row, ADSB_COL_RANGE); - ui->adsbData->removeCellWidget(row, ADSB_COL_AZEL); - ui->adsbData->removeCellWidget(row, ADSB_COL_LATITUDE); - ui->adsbData->removeCellWidget(row, ADSB_COL_LONGITUDE); - ui->adsbData->removeCellWidget(row, ADSB_COL_CATEGORY); - ui->adsbData->removeCellWidget(row, ADSB_COL_STATUS); - ui->adsbData->removeCellWidget(row, ADSB_COL_SQUAWK); - ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTRATION); - ui->adsbData->removeCellWidget(row, ADSB_COL_COUNTRY); - ui->adsbData->removeCellWidget(row, ADSB_COL_REGISTERED); - ui->adsbData->removeCellWidget(row, ADSB_COL_MANUFACTURER); - ui->adsbData->removeCellWidget(row, ADSB_COL_OWNER); - ui->adsbData->removeCellWidget(row, ADSB_COL_OPERATOR_ICAO); - ui->adsbData->removeCellWidget(row, ADSB_COL_TIME); - ui->adsbData->removeCellWidget(row, ADSB_COL_FRAMECOUNT); - ui->adsbData->removeCellWidget(row, ADSB_COL_CORRELATION); - ui->adsbData->removeCellWidget(row, ADSB_COL_RSSI); ui->adsbData->setRowCount(row); } + +Aircraft* ADSBDemodGUI::findAircraftByFlight(const QString& flight) +{ + QHash::iterator i = m_aircraft.begin(); + while (i != m_aircraft.end()) + { + Aircraft *aircraft = i.value(); + if (aircraft->m_flight == flight) { + return aircraft; + } + ++i; + } + return nullptr; +} + +// Convert to hh:mm (+/-days) +QString ADSBDemodGUI::dataTimeToShortString(QDateTime dt) +{ + if (dt.isValid()) + { + QDate currentDate = QDateTime::currentDateTimeUtc().date(); + if (dt.date() == currentDate) + { + return dt.time().toString("hh:mm"); + } + else + { + int days = currentDate.daysTo(dt.date()); + if (days >= 0) { + return QString("%1 +%2").arg(dt.time().toString("hh:mm")).arg(days); + } else { + return QString("%1 %2").arg(dt.time().toString("hh:mm")).arg(days); + } + } + } + else + { + return ""; + } +} + +void ADSBDemodGUI::initFlightInformation() +{ + if (m_flightInformation) + { + disconnect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated); + delete m_flightInformation; + m_flightInformation = nullptr; + } + if (!m_settings.m_apiKey.isEmpty()) + { + m_flightInformation = FlightInformation::create(m_settings.m_apiKey); + if (m_flightInformation) { + connect(m_flightInformation, &FlightInformation::flightUpdated, this, &ADSBDemodGUI::flightInformationUpdated); + } + } +} + +void ADSBDemodGUI::flightInformationUpdated(const FlightInformation::Flight& flight) +{ + Aircraft* aircraft = findAircraftByFlight(flight.m_flightICAO); + if (aircraft) + { + aircraft->m_flightStatusItem->setText(flight.m_flightStatus); + aircraft->m_depItem->setText(flight.m_departureICAO); + aircraft->m_arrItem->setText(flight.m_arrivalICAO); + aircraft->m_stdItem->setText(dataTimeToShortString(flight.m_departureScheduled)); + aircraft->m_etdItem->setText(dataTimeToShortString(flight.m_departureEstimated)); + aircraft->m_atdItem->setText(dataTimeToShortString(flight.m_departureActual)); + aircraft->m_staItem->setText(dataTimeToShortString(flight.m_arrivalScheduled)); + aircraft->m_etaItem->setText(dataTimeToShortString(flight.m_arrivalEstimated)); + aircraft->m_ataItem->setText(dataTimeToShortString(flight.m_arrivalActual)); + if (aircraft->m_positionValid) { + m_aircraftModel.aircraftUpdated(aircraft); + } + } + else + { + qDebug() << "ADSBDemodGUI::flightInformationUpdated - Flight not found in ADS-B table: " << flight.m_flightICAO; + } +} diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.h b/plugins/channelrx/demodadsb/adsbdemodgui.h index acc8bcb12..8fa03f597 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.h +++ b/plugins/channelrx/demodadsb/adsbdemodgui.h @@ -35,6 +35,7 @@ #include "util/azel.h" #include "util/movingaverage.h" #include "util/httpdownloadmanager.h" +#include "util/flightinformation.h" #include "maincore.h" #include "adsbdemodsettings.h" @@ -78,7 +79,8 @@ public: // Data about an aircraft extracted from an ADS-B frames struct Aircraft { int m_icao; // 24-bit ICAO aircraft address - QString m_flight; // Flight callsign + QString m_callsign; // Flight callsign + QString m_flight; // Guess at flight number Real m_latitude; // Latitude in decimal degrees Real m_longitude; // Longitude in decimal degrees int m_altitude; // Altitude in feet @@ -130,7 +132,7 @@ struct Aircraft { // GUI table items for above data QTableWidgetItem *m_icaoItem; - QTableWidgetItem *m_flightItem; + QTableWidgetItem *m_callsignItem; QTableWidgetItem *m_modelItem; QTableWidgetItem *m_airlineItem; QTableWidgetItem *m_latitudeItem; @@ -154,6 +156,15 @@ struct Aircraft { QTableWidgetItem *m_adsbFrameCountItem; QTableWidgetItem *m_correlationItem; QTableWidgetItem *m_rssiItem; + QTableWidgetItem *m_flightStatusItem; + QTableWidgetItem *m_depItem; + QTableWidgetItem *m_arrItem; + QTableWidgetItem *m_stdItem; + QTableWidgetItem *m_etdItem; + QTableWidgetItem *m_atdItem; + QTableWidgetItem *m_staItem; + QTableWidgetItem *m_etaItem; + QTableWidgetItem *m_ataItem; Aircraft(ADSBDemodGUI *gui) : m_icao(0), @@ -187,7 +198,7 @@ struct Aircraft { } // These are deleted by QTableWidget m_icaoItem = new QTableWidgetItem(); - m_flightItem = new QTableWidgetItem(); + m_callsignItem = new QTableWidgetItem(); m_modelItem = new QTableWidgetItem(); m_airlineItem = new QTableWidgetItem(); m_altitudeItem = new QTableWidgetItem(); @@ -211,6 +222,15 @@ struct Aircraft { m_adsbFrameCountItem = new QTableWidgetItem(); m_correlationItem = new QTableWidgetItem(); m_rssiItem = new QTableWidgetItem(); + m_flightStatusItem = new QTableWidgetItem(); + m_depItem = new QTableWidgetItem(); + m_arrItem = new QTableWidgetItem(); + m_stdItem = new QTableWidgetItem(); + m_etdItem = new QTableWidgetItem(); + m_atdItem = new QTableWidgetItem(); + m_staItem = new QTableWidgetItem(); + m_etaItem = new QTableWidgetItem(); + m_ataItem = new QTableWidgetItem(); } QString getImage(); @@ -219,8 +239,8 @@ struct Aircraft { // Name to use when selected as a target QString targetName() { - if (!m_flight.isEmpty()) - return QString("Flight: %1").arg(m_flight); + if (!m_callsign.isEmpty()) + return QString("Callsign: %1").arg(m_callsign); else return QString("ICAO: %1").arg(m_icao, 0, 16); } @@ -480,6 +500,7 @@ public: public slots: void channelMarkerChangedByCursor(); void channelMarkerHighlightedByCursor(); + void flightInformationUpdated(const FlightInformation::Flight& flight); private: Ui::ADSBDemodGUI* ui; @@ -516,7 +537,7 @@ private: QTextToSpeech *m_speech; QMenu *menu; // Column select context menu - + FlightInformation *m_flightInformation; WebAPIAdapterInterface *m_webAPIAdapterInterface; HttpDownloadManager m_dlm; QProgressDialog *m_progressDialog; @@ -558,6 +579,9 @@ private: QIcon *getFlagIcon(const QString &country); void updateDeviceSetList(); QAction *createCheckableItem(QString& text, int idx, bool checked); + Aircraft* findAircraftByFlight(const QString& flight); + QString dataTimeToShortString(QDateTime dt); + void initFlightInformation(); void leaveEvent(QEvent*); void enterEvent(QEvent*); @@ -579,6 +603,7 @@ private slots: void on_demodModeS_clicked(bool checked); void on_feed_clicked(bool checked); void on_notifications_clicked(); + void on_flightInfo_clicked(); void on_getOSNDB_clicked(); void on_getAirportDB_clicked(); void on_flightPaths_clicked(bool checked); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.ui b/plugins/channelrx/demodadsb/adsbdemodgui.ui index 88ae88ebd..3e4bc15e1 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.ui +++ b/plugins/channelrx/demodadsb/adsbdemodgui.ui @@ -613,7 +613,24 @@ :/mono.png:/mono.png - true + false + + + + + + + Download flight information for selected aircraft + + + ... + + + + :/info.png:/info.png + + + false @@ -721,10 +738,10 @@ - Flight No. + Callsign - Commercial flight number. Links to www.flightradar24.com + Callsign. Links to www.flightradar24.com @@ -908,6 +925,78 @@ RSSI + + + Flight Status + + + Status of flight + + + + + Dep + + + Departure airport + + + + + Arr + + + Arrival airport + + + + + STD + + + Scheduled time of departure + + + + + ETD + + + Estimated time of departure + + + + + ATD + + + Actual time of departure + + + + + STA + + + Scheduled time of arrival + + + + + ETA + + + Estimated time of arrival + + + + + ATA + + + Actual time of arrival + + diff --git a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp index 366006c7f..cab727736 100644 --- a/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodnotificationdialog.cpp @@ -25,7 +25,7 @@ // Map main ADS-B table column numbers to combo box indicies std::vector ADSBDemodNotificationDialog::m_columnMap = { - ADSB_COL_ICAO, ADSB_COL_FLIGHT, ADSB_COL_MODEL, + ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL, ADSB_COL_ALTITUDE, ADSB_COL_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 @@ -111,7 +111,7 @@ void ADSBDemodNotificationDialog::addRow(ADSBDemodSettings::NotificationSettings matchWidget->setLayout(pLayout); match->addItem("ICAO ID"); - match->addItem("Flight No."); + match->addItem("Callsign"); match->addItem("Aircraft"); match->addItem("Alt (ft)"); match->addItem("Spd (kn)"); diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp index 199c4723f..03e9ee3d8 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp @@ -66,6 +66,7 @@ void ADSBDemodSettings::resetToDefaults() m_autoResizeTableColumns = false; m_interpolatorPhaseSteps = 4; // Higher than these two values will struggle to run in real-time m_interpolatorTapsPerPhase = 3.5f; // without gaining much improvement in PER + m_apiKey = ""; for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) { m_columnIndexes[i] = i; @@ -115,6 +116,7 @@ QByteArray ADSBDemodSettings::serialize() const s.writeBool(33, m_allFlightPaths); s.writeBlob(34, serializeNotificationSettings(m_notificationSettings)); + s.writeString(35, m_apiKey); for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) s.writeS32(100 + i, m_columnIndexes[i]); @@ -199,6 +201,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data) d.readBlob(34, &blob); deserializeNotificationSettings(blob, m_notificationSettings); + d.readString(35, &m_apiKey, ""); for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) d.readS32(100 + i, &m_columnIndexes[i], i); @@ -259,7 +262,7 @@ void ADSBDemodSettings::NotificationSettings::updateRegularExpression() { m_regularExpression.setPattern(m_regExp); m_regularExpression.optimize(); - if (m_regularExpression.isValid()) { + if (!m_regularExpression.isValid()) { qDebug() << "ADSBDemod: Regular expression is not valid: " << m_regExp; } } diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.h b/plugins/channelrx/demodadsb/adsbdemodsettings.h index 72c919583..8adf1a20d 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.h +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.h @@ -29,11 +29,11 @@ class Serializable; // Number of columns in the table -#define ADSBDEMOD_COLUMNS 25 +#define ADSBDEMOD_COLUMNS 34 // ADS-B table columns #define ADSB_COL_ICAO 0 -#define ADSB_COL_FLIGHT 1 +#define ADSB_COL_CALLSIGN 1 #define ADSB_COL_MODEL 2 #define ADSB_COL_AIRLINE 3 #define ADSB_COL_ALTITUDE 4 @@ -57,6 +57,15 @@ class Serializable; #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 struct ADSBDemodSettings { @@ -119,6 +128,7 @@ struct ADSBDemodSettings float m_interpolatorTapsPerPhase; QList m_notificationSettings; + QString m_apiKey; //!< aviationstack.com API key ADSBDemodSettings(); void resetToDefaults(); diff --git a/plugins/channelrx/demodadsb/readme.md b/plugins/channelrx/demodadsb/readme.md index db7828cb7..be015a42c 100644 --- a/plugins/channelrx/demodadsb/readme.md +++ b/plugins/channelrx/demodadsb/readme.md @@ -67,6 +67,8 @@ Clicking the Display Settings button will open the Display Settings dialog, whic * Whether demodulator statistics are displayed (primarily an option for developers). * Whether the columns in the table are automatically resized after an aircraft is added to it. If unchecked, columns can be resized manually and should be saved with presets. +You can also enter an [avaiationstack](https://aviationstack.com/product) API key, needed to download flight information (such as departure and arrival airports and times). +

12: Display Flight Paths

Checking this button draws a line on the map showing aircraft's flight paths, as determined from received ADS-B frames. @@ -125,10 +127,10 @@ Emergency status are: * Unlawful interference * Downed aircraft -In the Speech and Command strings, variables can be used to substitute in ADS-B data for the aircraft: +In the Speech and Command strings, variables can be used to substitute in data from the ADS-B table for the aircraft: * ${icao}, -* ${flight} +* ${callsign} * ${aircraft} * ${latitude} * ${longitude} @@ -143,6 +145,23 @@ In the Speech and Command strings, variables can be used to substitute in ADS-B * ${manufacturer} * ${owner} * ${operator} +* ${flightstatus} +* ${departure} +* ${arrival} +* ${std} +* ${etd} +* ${atd} +* ${sta} +* ${eta} +* ${ata} + +

Download flight information for selected flight

+ +When clicked, flight information (departure and arrival airport and times) is downloaded for the aircraft highlighted in the ADS-B data table using the aviationstack.com API. +To be able to use this, a callsign for the highlighted aircraft must have been received. Also, the callsign must be mappable to a flight number, which is not always possible (this is tpyically +the case for callsigns that end in two characters, as for these, some digits from the flight number will have been omitted). + +To use this feature, an aviationstack API Key must be entered in the Display Settings dialog (11). A free key giving 500 API calls per month is available from: https://aviationstack.com/product

14: Refresh list of devices

@@ -154,12 +173,12 @@ 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. 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 data for each aircraft along side data available for the aircraft from the Opensky Network database 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 hexidecimal ICAO aircraft address. This is unique for each aircraft. (ADS-B) -* Flight No. - Airline flight number or callsign. (ADS-B) +* Callsign - Aircraft callsign (which is sometimes also the flight number). (ADS-B) * 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) @@ -182,6 +201,15 @@ The table displays the decoded ADS-B data for each aircraft along side data avai * RX Frames - A count of the number of ADS-B frames received from this aircraft. * Correlation - Displays the minimun, average and maximum of the preamable correlation in dB for each recevied 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) +* Dep - Departure airport. (API) +* Arr - Arrival airport. (API) +* STD - Scheduled time of departure. (API) +* ETD - Estimated time of departure. (API) +* ATD - Actual time of departure. (API) +* STA - Scheduled time of arrival. (API) +* ETA - Estimated time of arrival. (API) +* ATA - Actual time of arrival. (API) If an ADS-B frame has not been received from an aircraft for 60 seconds, the aircraft is removed from the table and map. This timeout can be adjusted in the Display Settings dialog. @@ -190,7 +218,7 @@ If an ADS-B frame has not been received from an aircraft for 60 seconds, the air * To reorder the columns, left click and drag left or right a column header. * Left click on a header to sort the table by the data in that column. * Double clicking in an ICAO ID cell will open a Web browser and search for the corresponding aircraft on https://www.planespotters.net/ -* Double clicking in an Flight No cell will open a Web browser and search for the corresponding flight on https://www.flightradar24.com/ +* Double clicking in an Callsign cell will open a Web browser and search for the corresponding flight on https://www.flightradar24.com/ * Double clicking in an Az/El cell will set the aircraft as the active target. The azimuth and elevation to the aicraft will be sent to a rotator controller plugin. The aircraft information box will be coloured green, rather than blue, on the map. * Double clicking on any other cell in the table will centre the map on the corresponding aircraft. diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index fece45fb0..67b8e2c59 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -190,6 +190,7 @@ set(sdrbase_SOURCES util/db.cpp util/fixedtraits.cpp util/fits.cpp + util/flightinformation.cpp util/golay2312.cpp util/httpdownloadmanager.cpp util/interpolation.cpp @@ -397,6 +398,7 @@ set(sdrbase_HEADERS util/doublebuffermultiple.h util/fixedtraits.h util/fits.h + util/flightinformation.h util/golay2312.h util/httpdownloadmanager.h util/incrementalarray.h diff --git a/sdrbase/util/flightinformation.cpp b/sdrbase/util/flightinformation.cpp new file mode 100644 index 000000000..e93e0dcd7 --- /dev/null +++ b/sdrbase/util/flightinformation.cpp @@ -0,0 +1,164 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 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 "flightinformation.h" + +#include +#include +#include +#include +#include +#include +#include + +FlightInformation::FlightInformation() +{ +} + +FlightInformation* FlightInformation::create(const QString& apiKey, const QString& service) +{ + if (service == "aviationstack.com") + { + if (!apiKey.isEmpty()) + { + return new AviationStack(apiKey); + } + else + { + qDebug() << "FlightInformation::create: An API key is required for: " << service; + return nullptr; + } + } + else + { + qDebug() << "FlightInformation::create: Unsupported service: " << service; + return nullptr; + } +} + +AviationStack::AviationStack(const QString& apiKey) : + m_apiKey(apiKey) +{ + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*))); +} + +AviationStack::~AviationStack() +{ + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(handleReply(QNetworkReply*))); + delete m_networkManager; +} + +void AviationStack::getFlightInformation(const QString& flight) +{ + QUrl url(QString("http://api.aviationstack.com/v1/flights")); + QUrlQuery query; + query.addQueryItem("flight_icao",flight); + query.addQueryItem("access_key", m_apiKey); + url.setQuery(query); + + m_networkManager->get(QNetworkRequest(url)); + /*QFile file("flight.json"); + if (file.open(QIODevice::ReadOnly)) + { + parseJson(file.readAll()); + }*/ +} + +void AviationStack::handleReply(QNetworkReply* reply) +{ + if (reply) + { + if (!reply->error()) + { + parseJson(reply->readAll()); + } + else + { + qDebug() << "AviationStack::handleReply: error: " << reply->error(); + } + reply->deleteLater(); + } + else + { + qDebug() << "AviationStack::handleReply: reply is null"; + } +} + +void AviationStack::parseJson(QByteArray bytes) +{ + QJsonDocument document = QJsonDocument::fromJson(bytes); + if (document.isObject()) + { + QJsonObject obj = document.object(); + if (obj.contains(QStringLiteral("data"))) + { + QJsonArray data = obj.value(QStringLiteral("data")).toArray(); + if (data.size() > 0) + { + QJsonObject flightObj = data[0].toObject(); + + Flight flight; + + if (flightObj.contains(QStringLiteral("flight_status"))) { + flight.m_flightStatus = flightObj.value(QStringLiteral("flight_status")).toString(); + } + if (flightObj.contains(QStringLiteral("departure"))) + { + QJsonObject departure = flightObj.value(QStringLiteral("departure")).toObject(); + flight.m_departureAirport = departure.value(QStringLiteral("airport")).toString(); + flight.m_departureICAO = departure.value(QStringLiteral("icao")).toString(); + flight.m_departureTerminal = departure.value(QStringLiteral("terminal")).toString(); + flight.m_departureGate = departure.value(QStringLiteral("gate")).toString(); + flight.m_departureScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate); + flight.m_departureEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate); + flight.m_departureActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate); + } + if (flightObj.contains(QStringLiteral("arrival"))) + { + QJsonObject departure = flightObj.value(QStringLiteral("arrival")).toObject(); + flight.m_arrivalAirport = departure.value(QStringLiteral("airport")).toString(); + flight.m_arrivalICAO = departure.value(QStringLiteral("icao")).toString(); + flight.m_arrivalTerminal = departure.value(QStringLiteral("terminal")).toString(); + flight.m_arrivalGate = departure.value(QStringLiteral("gate")).toString(); + flight.m_arrivalScheduled = QDateTime::fromString(departure.value(QStringLiteral("scheduled")).toString(), Qt::ISODate); + flight.m_arrivalEstimated = QDateTime::fromString(departure.value(QStringLiteral("estimated")).toString(), Qt::ISODate); + flight.m_arrivalActual = QDateTime::fromString(departure.value(QStringLiteral("actual")).toString(), Qt::ISODate); + } + if (flightObj.contains(QStringLiteral("flight"))) + { + QJsonObject flightNo = flightObj.value(QStringLiteral("flight")).toObject(); + flight.m_flightICAO = flightNo.value(QStringLiteral("icao")).toString(); + flight.m_flightIATA = flightNo.value(QStringLiteral("iata")).toString(); + } + emit flightUpdated(flight); + } + else + { + qDebug() << "AviationStack::handleReply: data array is empty"; + } + } + else + { + qDebug() << "AviationStack::handleReply: Object doesn't contain data: " << obj; + } + } + else + { + qDebug() << "AviationStack::handleReply: Document is not an object: " << document; + } +} diff --git a/sdrbase/util/flightinformation.h b/sdrbase/util/flightinformation.h new file mode 100644 index 000000000..6bc89b526 --- /dev/null +++ b/sdrbase/util/flightinformation.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FLIGHTINFORMATION_H +#define INCLUDE_FLIGHTINFORMATION_H + +#include +#include + +#include "export.h" + +class QNetworkAccessManager; +class QNetworkReply; + +// Flight information API wrapper +// Allows searching for departure/arrival airports and status of a flight +// Currently supports aviationstack.com +class SDRBASE_API FlightInformation : public QObject +{ + Q_OBJECT + +protected: + FlightInformation(); + +public: + struct Flight { + QString m_flightICAO; + QString m_flightIATA; + QString m_flightStatus; // active, landed... + QString m_departureAirport; + QString m_departureICAO; + QString m_departureTerminal; + QString m_departureGate; + QDateTime m_departureScheduled; + QDateTime m_departureEstimated; + QDateTime m_departureActual; + QString m_arrivalAirport; + QString m_arrivalICAO; + QString m_arrivalTerminal; + QString m_arrivalGate; + QDateTime m_arrivalScheduled; + QDateTime m_arrivalEstimated; + QDateTime m_arrivalActual; + }; + + static FlightInformation* create(const QString& apiKey, const QString& service="aviationstack.com"); + + virtual void getFlightInformation(const QString& flight) = 0; + +signals: + void flightUpdated(const Flight& flight); // Called when new data available. + +private: + +}; + +class SDRBASE_API AviationStack : public FlightInformation { + Q_OBJECT +public: + + AviationStack(const QString& apiKey); + ~AviationStack(); + virtual void getFlightInformation(const QString& flight) override; + +private: + void parseJson(QByteArray bytes); + + QString m_apiKey; + QNetworkAccessManager *m_networkManager; + +public slots: + void handleReply(QNetworkReply* reply); + +}; + +#endif /* INCLUDE_FLIGHTINFORMATION_H */ diff --git a/sdrbase/util/weather.cpp b/sdrbase/util/weather.cpp index 20c4aa8d0..d92f6d281 100644 --- a/sdrbase/util/weather.cpp +++ b/sdrbase/util/weather.cpp @@ -39,13 +39,13 @@ Weather* Weather::create(const QString& apiKey, const QString& service) } else { - qDebug() << "Weather::connect: An API key is required for: " << service; + qDebug() << "Weather::create: An API key is required for: " << service; return nullptr; } } else { - qDebug() << "Weather::connect: Unsupported service: " << service; + qDebug() << "Weather::create: Unsupported service: " << service; return nullptr; } }