/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2020 Edouard Griffiths, F4EXB // // 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 . // /////////////////////////////////////////////////////////////////////////////////// #ifndef INCLUDE_ADSBDEMODGUI_H #define INCLUDE_ADSBDEMODGUI_H #include #include #include #include #include #include #include "channel/channelgui.h" #include "dsp/dsptypes.h" #include "dsp/channelmarker.h" #include "dsp/movingaverage.h" #include "util/messagequeue.h" #include "util/azel.h" #include "util/movingaverage.h" #include "util/httpdownloadmanager.h" #include "adsbdemodsettings.h" #include "ourairportsdb.h" #include "osndb.h" class PluginAPI; class DeviceUISet; class BasebandSampleSink; class ADSBDemod; class WebAPIAdapterInterface; class HttpDownloadManager; class ADSBDemodGUI; namespace Ui { class ADSBDemodGUI; } // Custom widget to allow formatted decimal numbers to be sorted numerically class CustomDoubleTableWidgetItem : public QTableWidgetItem { public: CustomDoubleTableWidgetItem(const QString text = QString("")) : QTableWidgetItem(text) { } bool operator <(const QTableWidgetItem& other) const { // Treat "" as less than 0 QString thisText = text(); QString otherText = other.text(); if (thisText == "") return true; if (otherText == "") return false; return thisText.toDouble() < otherText.toDouble(); } }; // 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 Real m_latitude; // Latitude in decimal degrees Real m_longitude; // Longitude in decimal degrees int m_altitude; // Altitude in feet 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[]; int m_heading; // Heading in degrees int m_verticalRate; // Vertical climb rate in ft/min QString m_emitterCategory; // Aircraft type QString m_status; // Aircraft status int m_squawk; // Mode-A code Real m_range; // Distance from station to aircraft Real m_azimuth; // Azimuth from station to aircraft Real m_elevation; // Elevation from station to aicraft; QDateTime m_time; // When last updated 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; // State for calculating position using two CPR frames bool m_cprValid[2]; Real m_cprLat[2]; Real m_cprLong[2]; QDateTime m_cprTime[2]; int m_adsbFrameCount; // Number of ADS-B frames for this aircraft float m_minCorrelation; float m_maxCorrelation; float m_correlation; MovingAverageUtil m_correlationAvg; bool m_isTarget; // Are we targetting this aircraft (sending az/el to rotator) bool m_isHighlighted; // Are we highlighting this aircraft in the table and map bool m_showAll; QVariantList m_coordinates; // Coordinates we've recorded the aircraft at AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database ADSBDemodGUI *m_gui; // GUI table items for above data QTableWidgetItem *m_icaoItem; QTableWidgetItem *m_flightItem; QTableWidgetItem *m_modelItem; QTableWidgetItem *m_airlineItem; QTableWidgetItem *m_latitudeItem; QTableWidgetItem *m_longitudeItem; QTableWidgetItem *m_altitudeItem; QTableWidgetItem *m_speedItem; QTableWidgetItem *m_headingItem; QTableWidgetItem *m_verticalRateItem; CustomDoubleTableWidgetItem *m_rangeItem; QTableWidgetItem *m_azElItem; QTableWidgetItem *m_emitterCategoryItem; QTableWidgetItem *m_statusItem; QTableWidgetItem *m_squawkItem; QTableWidgetItem *m_registrationItem; QTableWidgetItem *m_countryItem; QTableWidgetItem *m_registeredItem; QTableWidgetItem *m_manufacturerNameItem; QTableWidgetItem *m_ownerItem; QTableWidgetItem *m_operatorICAOItem; QTableWidgetItem *m_timeItem; QTableWidgetItem *m_adsbFrameCountItem; QTableWidgetItem *m_correlationItem; QTableWidgetItem *m_rssiItem; Aircraft(ADSBDemodGUI *gui) : m_icao(0), m_latitude(0), m_longitude(0), m_altitude(0), m_speed(0), m_heading(0), m_verticalRate(0), m_azimuth(0), m_elevation(0), m_positionValid(false), m_altitudeValid(false), m_speedValid(false), m_headingValid(false), m_verticalRateValid(false), m_adsbFrameCount(0), m_minCorrelation(INFINITY), m_maxCorrelation(-INFINITY), m_correlation(0.0f), m_isTarget(false), m_isHighlighted(false), m_showAll(false), m_aircraftInfo(nullptr), m_gui(gui) { for (int i = 0; i < 2; i++) { m_cprValid[i] = false; } // These are deleted by QTableWidget m_icaoItem = new QTableWidgetItem(); m_flightItem = 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(); m_azElItem = new QTableWidgetItem(); m_latitudeItem = new QTableWidgetItem(); m_longitudeItem = new QTableWidgetItem(); m_emitterCategoryItem = new QTableWidgetItem(); m_statusItem = new QTableWidgetItem(); m_squawkItem = new QTableWidgetItem(); m_registrationItem = new QTableWidgetItem(); m_countryItem = new QTableWidgetItem(); m_registeredItem = new QTableWidgetItem(); m_manufacturerNameItem = new QTableWidgetItem(); m_ownerItem = new QTableWidgetItem(); m_operatorICAOItem = new QTableWidgetItem(); m_timeItem = new QTableWidgetItem(); m_adsbFrameCountItem = new QTableWidgetItem(); m_correlationItem = new QTableWidgetItem(); m_rssiItem = new QTableWidgetItem(); } }; // Aircraft data model used by QML map item class AircraftModel : public QAbstractListModel { Q_OBJECT public: using QAbstractListModel::QAbstractListModel; enum MarkerRoles{ positionRole = Qt::UserRole + 1, headingRole = Qt::UserRole + 2, adsbDataRole = Qt::UserRole + 3, aircraftImageRole = Qt::UserRole + 4, bubbleColourRole = Qt::UserRole + 5, aircraftPathRole = Qt::UserRole + 6, showAllRole = Qt::UserRole + 7, highlightedRole = Qt::UserRole + 8, targetRole = Qt::UserRole + 9 }; Q_INVOKABLE void addAircraft(Aircraft *aircraft) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_aircrafts.append(aircraft); endInsertRows(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return m_aircrafts.count(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override { (void) index; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } void aircraftUpdated(Aircraft *aircraft) { int row = m_aircrafts.indexOf(aircraft); if (row >= 0) { QModelIndex idx = index(row); emit dataChanged(idx, idx); } } void allAircraftUpdated() { /* // Not sure why this doesn't work - it should be more efficient // than the following code emit dataChanged(index(0), index(rowCount())); */ for (int i = 0; i < m_aircrafts.count(); i++) { QModelIndex idx = index(i); emit dataChanged(idx, idx); } } void removeAircraft(Aircraft *aircraft) { int row = m_aircrafts.indexOf(aircraft); if (row >= 0) { beginRemoveRows(QModelIndex(), row, row); m_aircrafts.removeAt(row); endRemoveRows(); } } QHash roleNames() const { QHash roles; roles[positionRole] = "position"; roles[headingRole] = "heading"; roles[adsbDataRole] = "adsbData"; roles[aircraftImageRole] = "aircraftImage"; roles[bubbleColourRole] = "bubbleColour"; roles[aircraftPathRole] = "aircraftPath"; roles[showAllRole] = "showAll"; roles[highlightedRole] = "highlighted"; roles[targetRole] = "target"; return roles; } void setFlightPaths(bool flightPaths) { m_flightPaths = flightPaths; allAircraftUpdated(); } private: QList m_aircrafts; bool m_flightPaths; }; // Airport data model used by QML map item class AirportModel : public QAbstractListModel { Q_OBJECT public: using QAbstractListModel::QAbstractListModel; enum MarkerRoles { positionRole = Qt::UserRole + 1, airportDataRole = Qt::UserRole + 2, airportDataRowsRole = Qt::UserRole + 3, airportImageRole = Qt::UserRole + 4, bubbleColourRole = Qt::UserRole + 5, showFreqRole = Qt::UserRole + 6, selectedFreqRole = Qt::UserRole + 7 }; AirportModel(ADSBDemodGUI *gui) : m_gui(gui) { } Q_INVOKABLE void addAirport(AirportInformation *airport, float az, float el, float distance) { QString text; int rows; beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_airports.append(airport); airportFreq(airport, az, el, distance, text, rows); m_airportDataFreq.append(text); m_airportDataFreqRows.append(rows); m_showFreq.append(false); m_azimuth.append(az); m_elevation.append(el); endInsertRows(); } void removeAirport(AirportInformation *airport) { int row = m_airports.indexOf(airport); if (row >= 0) { beginRemoveRows(QModelIndex(), row, row); m_airports.removeAt(row); m_airportDataFreq.removeAt(row); m_airportDataFreqRows.removeAt(row); m_showFreq.removeAt(row); m_azimuth.removeAt(row); m_elevation.removeAt(row); endRemoveRows(); } } void removeAllAirports() { beginRemoveRows(QModelIndex(), 0, m_airports.count()); m_airports.clear(); m_airportDataFreq.clear(); m_airportDataFreqRows.clear(); m_showFreq.clear(); m_azimuth.clear(); m_elevation.clear(); endRemoveRows(); } int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return m_airports.count(); } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override { (void) index; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } void airportFreq(AirportInformation *airport, float az, float el, float distance, QString& text, int& rows) { // Create the text to go in the bubble next to the airport // Display name and frequencies QStringList list; list.append(QString("%1: %2").arg(airport->m_ident).arg(airport->m_name)); rows = 1; for (int i = 0; i < airport->m_frequencies.size(); i++) { AirportInformation::FrequencyInformation *frequencyInfo = airport->m_frequencies[i]; list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency)); rows++; } list.append(QString("Az/El: %1/%2").arg((int)std::round(az)).arg((int)std::round(el))); list.append(QString("Distance: %1 km").arg(distance/1000.0f, 0, 'f', 1)); rows += 2; text = list.join("\n"); } void airportUpdated(AirportInformation *airport) { int row = m_airports.indexOf(airport); if (row >= 0) { QModelIndex idx = index(row); emit dataChanged(idx, idx); } } QHash roleNames() const { QHash roles; roles[positionRole] = "position"; roles[airportDataRole] = "airportData"; roles[airportDataRowsRole] = "airportDataRows"; roles[airportImageRole] = "airportImage"; roles[bubbleColourRole] = "bubbleColour"; roles[showFreqRole] = "showFreq"; roles[selectedFreqRole] = "selectedFreq"; return roles; } private: ADSBDemodGUI *m_gui; QList m_airports; QList m_airportDataFreq; QList m_airportDataFreqRows; QList m_showFreq; QList m_azimuth; QList m_elevation; }; class ADSBDemodGUI : public ChannelGUI { Q_OBJECT public: static ADSBDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); virtual void destroy(); void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } void highlightAircraft(Aircraft *aircraft); void targetAircraft(Aircraft *aircraft); void target(float az, float el); bool setFrequency(float frequency); bool useSIUints() { return m_settings.m_siUnits; } public slots: void channelMarkerChangedByCursor(); void channelMarkerHighlightedByCursor(); private: Ui::ADSBDemodGUI* ui; PluginAPI* m_pluginAPI; DeviceUISet* m_deviceUISet; ChannelMarker m_channelMarker; ADSBDemodSettings m_settings; bool m_basicSettingsShown; bool m_doApplySettings; ADSBDemod* m_adsbDemod; uint32_t m_tickCount; MessageQueue m_inputMessageQueue; QHash m_aircraft; // Hashed on ICAO QHash *m_aircraftInfo; QHash *m_airportInfo; // Hashed on id AircraftModel m_aircraftModel; AirportModel m_airportModel; QHash m_airlineIcons; // Hashed on airline ICAO QHash m_flagIcons; // Hashed on country QHash *m_prefixMap; // Registration to country (flag name) QHash *m_militaryMap; // Operator airforce to military (flag name) AzEl m_azEl; // Position of station Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report MovingAverageUtil m_correlationAvg; MovingAverageUtil m_correlationOnesAvg; Aircraft *m_highlightAircraft; // Aircraft we want to highlight, when selected in table float m_currentAirportRange; // Current settings, so we only update if changed ADSBDemodSettings::AirportType m_currentAirportMinimumSize; bool m_currentDisplayHeliports; QMenu *menu; // Column select context menu WebAPIAdapterInterface *m_webAPIAdapterInterface; HttpDownloadManager m_dlm; QProgressDialog *m_progressDialog; explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~ADSBDemodGUI(); void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); void displayStreamIndex(); bool handleMessage(const Message& message); void updatePosition(Aircraft *aircraft); bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition); void handleADSB( const QByteArray data, const QDateTime dateTime, float correlation, float correlationOnes); void resizeTable(); QString getDataDir(); QString getAirportDBFilename(); QString getAirportFrequenciesDBFilename(); QString getOSNDBFilename(); QString getFastDBFilename(); qint64 fileAgeInDays(QString filename); bool confirmDownload(QString filename); void readAirportDB(const QString& filename); void readAirportFrequenciesDB(const QString& filename); bool readOSNDB(const QString& filename); bool readFastDB(const QString& filename); void updateAirports(); QIcon *getAirlineIcon(const QString &operatorICAO); QIcon *getFlagIcon(const QString &country); void updateDeviceSetList(); QAction *createCheckableItem(QString& text, int idx, bool checked); void leaveEvent(QEvent*); void enterEvent(QEvent*); private slots: void on_deltaFrequency_changed(qint64 value); void on_rfBW_valueChanged(int value); void on_threshold_valueChanged(int value); void on_phaseSteps_valueChanged(int value); void on_tapsPerPhase_valueChanged(int value); void on_adsbData_cellClicked(int row, int column); void on_adsbData_cellDoubleClicked(int row, int column); void adsbData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); void adsbData_sectionResized(int logicalIndex, int oldSize, int newSize); void columnSelectMenu(QPoint pos); void columnSelectMenuChecked(bool checked = false); void on_spb_currentIndexChanged(int value); void on_correlateFullPreamble_clicked(bool checked); void on_demodModeS_clicked(bool checked); void on_feed_clicked(bool checked); void on_getOSNDB_clicked(); void on_getAirportDB_clicked(); void on_flightPaths_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); void tick(); void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes); void downloadFinished(const QString& filename, bool success); void on_devicesRefresh_clicked(); void on_device_currentIndexChanged(int index); void feedSelect(); void on_displaySettings_clicked(); signals: void homePositionChanged(); }; #endif // INCLUDE_ADSBDEMODGUI_H