diff --git a/doc/img/ADSBDemod_plugin.png b/doc/img/ADSBDemod_plugin.png index 1525d5ea6..b52eb9223 100644 Binary files a/doc/img/ADSBDemod_plugin.png and b/doc/img/ADSBDemod_plugin.png differ diff --git a/doc/img/ADSBDemod_plugin_displaysettings.png b/doc/img/ADSBDemod_plugin_displaysettings.png index 40148fb4b..e1d2d59e9 100644 Binary files a/doc/img/ADSBDemod_plugin_displaysettings.png and b/doc/img/ADSBDemod_plugin_displaysettings.png differ diff --git a/plugins/channelrx/demodadsb/adsbdemod.cpp b/plugins/channelrx/demodadsb/adsbdemod.cpp index f8126e9bd..f6f0853fd 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.cpp +++ b/plugins/channelrx/demodadsb/adsbdemod.cpp @@ -49,6 +49,7 @@ #include "adsbdemodworker.h" MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgConfigureADSBDemod, Message) +MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgAircraftReport, Message) const char* const ADSBDemod::m_channelIdURI = "sdrangel.channel.adsbdemod"; const char* const ADSBDemod::m_channelId = "ADSBDemod"; @@ -186,6 +187,12 @@ bool ADSBDemod::handleMessage(const Message& cmd) return true; } + else if (MsgAircraftReport::match(cmd)) + { + MsgAircraftReport& msg = (MsgAircraftReport&) cmd; + m_aircraftReport = msg.getReport(); + return true; + } else { return false; @@ -636,6 +643,19 @@ void ADSBDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respons response.getAdsbDemodReport()->setTargetElevation(m_targetElevation); response.getAdsbDemodReport()->setTargetRange(m_targetRange); } + + QList *list = response.getAdsbDemodReport()->getAircraftState(); + for (const auto& report : m_aircraftReport) + { + SWGSDRangel::SWGADSBDemodAircraftState *aircraftState = new SWGSDRangel::SWGADSBDemodAircraftState(); + //aircraftState->setIcao(new QString(report.m_icao)); + aircraftState->setCallsign(new QString(report.m_callsign)); + aircraftState->setLatitude(report.m_latitude); + aircraftState->setLongitude(report.m_longitude); + aircraftState->setAltitude(report.m_altitude); + aircraftState->setGroundSpeed(report.m_groundSpeed); + list->append(aircraftState); + } } void ADSBDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const ADSBDemodSettings& settings, bool force) diff --git a/plugins/channelrx/demodadsb/adsbdemod.h b/plugins/channelrx/demodadsb/adsbdemod.h index c7e7b5c8a..2a3ab8f7e 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.h +++ b/plugins/channelrx/demodadsb/adsbdemod.h @@ -62,6 +62,35 @@ public: { } }; + class MsgAircraftReport : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + struct AircraftReport { + QString m_icao; + QString m_callsign; + float m_latitude; + float m_longitude; + int m_altitude; + int m_groundSpeed; + }; + + QList& getReport() { return m_report; } + + static MsgAircraftReport* create() + { + return new MsgAircraftReport(); + } + + private: + QList m_report; + + MsgAircraftReport() : + Message() + { } + }; + ADSBDemod(DeviceAPI *deviceAPI); virtual ~ADSBDemod(); virtual void destroy() { delete this; } @@ -148,6 +177,7 @@ private: float m_targetElevation; float m_targetRange; QString m_targetName; + QList m_aircraftReport; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp index 028dd712c..0c170fdfe 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.cpp @@ -49,9 +49,11 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWid ui->mapProvider->setCurrentText(settings->m_mapProvider); ui->mapType->setCurrentIndex((int)settings->m_mapType); ui->navAids->setChecked(settings->m_displayNavAids); + ui->atcCallsigns->setChecked(settings->m_atcCallsigns); ui->photos->setChecked(settings->m_displayPhotos); ui->verboseModelMatching->setChecked(settings->m_verboseModelMatching); ui->airfieldElevation->setValue(settings->m_airfieldElevation); + ui->transitionAltitude->setValue(settings->m_transitionAlt); } ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog() @@ -83,9 +85,11 @@ void ADSBDemodDisplayDialog::accept() m_settings->m_mapProvider = ui->mapProvider->currentText(); m_settings->m_mapType = (ADSBDemodSettings::MapType)ui->mapType->currentIndex(); m_settings->m_displayNavAids = ui->navAids->isChecked(); + m_settings->m_atcCallsigns = ui->atcCallsigns->isChecked(); m_settings->m_displayPhotos = ui->photos->isChecked(); m_settings->m_verboseModelMatching = ui->verboseModelMatching->isChecked(); m_settings->m_airfieldElevation = ui->airfieldElevation->value(); + m_settings->m_transitionAlt = ui->transitionAltitude->value(); m_settings->m_tableFontName = m_fontName; m_settings->m_tableFontSize = m_fontSize; QDialog::accept(); diff --git a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui index c18ea946b..a7cfd040e 100644 --- a/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui +++ b/plugins/channelrx/demodadsb/adsbdemoddisplaydialog.ui @@ -22,7 +22,14 @@ - + + + + Display NAVAIDs + + + + Barometric altitude reported by aircraft when on airfield surface @@ -38,34 +45,161 @@ - - + + - Display NAVAIDs + Airport display distance (km) - - + + + + Display heliports + + + + + - Download and display photos of highlighted aircraft + How long in seconds after not receiving any frames will an aircraft be removed from the table and map + + + 1000000 + + + + + + + When checked, heliports are displayed on the map - - + + - Resize the columns in the table after an aircraft is added to it + Transition altitude in feet + + 2500 + + + 20000 + + + + + + + Sets the minimum airport size that will be displayed on the map + + + + Small + + + + + Medium + + + + + Large + + + + + + + + The units to use for altitude, speed and climb rate + + + + ft, kn, ft/min + + + + + m, kph, m/s + + + + + + - + Map type + + + + + + + Resize columns after adding aircraft + + + + 0 + 0 + + + + Display demodulator statistics + + + + + + + + + + Log 3D model matching information + + + + + + + Select a font for the table + + + Select... + + + + + + + CheckWX API key + + + + + + + Display aircraft photos + + + + + + + Airspaces to display + + + + Log information about how aircraft are matched to 3D models @@ -75,6 +209,13 @@ + + + + Transition altitude (ft) + + + @@ -85,37 +226,6 @@ - - - - Display demodulator statistics - - - - - - - Display NAVAIDs such as VORs and NDBs - - - - - - - - - - Map type - - - - - - - Airport display distance (km) - - - @@ -143,145 +253,37 @@ - - - - Display airports with size - - - - - - - Aircraft timeout (s) - - - - - - - CheckWX API key - - - - - - - Display heliports - - - - - + + - How long in seconds after not receiving any frames will an aircraft be removed from the table and map - - - 1000000 + checkwxapi.com API key for accessing airport weather (METARs) - - - - Select a font for the table - - - Select... - - - - - - - Log 3D model matching information - - - - - - - Resize columns after adding aircraft - - - - - - - The units to use for altitude, speed and climb rate - - - - ft, kn, ft/min - - - - - m, kph, m/s - - - - - - - - Sets the minimum airport size that will be displayed on the map - - - - Small - - - - - Medium - - - - - Large - - - - - - - - Mapping service - - - - osm - - - - - mapboxgl - - - - - - - - Airspace display distance (km) - - - - + aviationstack.com API key for accessing flight information - + avaitionstack API key + + + + Download and display photos of highlighted aircraft + + + + + + @@ -289,53 +291,23 @@ - - - - Units - - - - - - - - 0 - 0 - - + + - Display demodulator statistics - - - - - - - - - - Displays airports within the specified distance in kilometres from My Position + When map zoom (0 min zoom - 15 max zoom) is higher than this value, aircraft icon size will be scaled - 20000 + 15 - + Table font - - - - Display aircraft photos - - - @@ -493,34 +465,92 @@ - - - - checkwxapi.com API key for accessing airport weather (METARs) + + + + Airspace display distance (km) - - + + + + Units + + + + + - When checked, heliports are displayed on the map + Display NAVAIDs such as VORs and NDBs - + + + + Display airports with size + + + + + + + Displays airports within the specified distance in kilometres from My Position + + + 20000 + + + + + + + Mapping service + + + + osm + + + + + mapboxgl + + + + + + + + Resize the columns in the table after an aircraft is added to it + + + + + + + + + + Aircraft timeout (s) + + + + Airfield barometric altitude (ft) - - + + - Airspaces to display + Display demodulator statistics @@ -531,13 +561,20 @@ - - - - When map zoom (0 min zoom - 15 max zoom) is higher than this value, aircraft icon size will be scaled + + + + Use ATC callsigns on map - - 15 + + + + + + Use ATC callsigns (SPEEDBIRD) rather than ICAO (BAW) for aircraft labels on map + + + diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index 0887b38ea..161a571ff 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -40,6 +40,7 @@ #include "channel/channelwebapiutils.h" #include "feature/featurewebapiutils.h" #include "plugin/pluginapi.h" +#include "util/airlines.h" #include "util/crc.h" #include "util/simpleserializer.h" #include "util/db.h" @@ -157,7 +158,7 @@ bool ADSBDemodGUI::deserialize(const QByteArray& data) { if(m_settings.deserialize(data)) { - updateDeviceSetList(); + updateChannelList(); displaySettings(); applySettings(true); return true; @@ -244,7 +245,7 @@ QString Aircraft::getImage() const } } -QString Aircraft::getText(bool all) const +QString Aircraft::getText(const ADSBDemodSettings *settings, bool all) const { QStringList list; if (m_showAll || all) @@ -265,6 +266,10 @@ QString Aircraft::getText(bool all) const if (!m_callsign.isEmpty()) { list.append(QString("Callsign: %1").arg(m_callsign)); } + QString atcCallsign = m_atcCallsignItem->text(); + if (!atcCallsign.isEmpty()) { + list.append(QString("ATC Callsign: %1").arg(atcCallsign)); + } if (m_aircraftInfo != nullptr) { if (!m_aircraftInfo->m_model.isEmpty()) { @@ -372,15 +377,81 @@ QString Aircraft::getText(bool all) const } } } - else if (!m_callsign.isEmpty()) + else { - list.append(m_callsign); + list.append(getLabel(settings)); + } + return list.join("
"); +} + +// See https://nats.aero/blog/2017/07/drone-disruption-gatwick/ for example of UK ATC display +QString Aircraft::getLabel(const ADSBDemodSettings *settings) const +{ + QString id; + if (!m_callsign.isEmpty()) + { + QString atcCallsign = m_atcCallsignItem->text(); + if (settings->m_atcCallsigns && !atcCallsign.isEmpty()) { + id = QString("%1 %2").arg(atcCallsign).arg(m_callsign.mid(3)); + } else { + id = m_callsign; + } } else { - list.append(m_icaoHex); + id = m_icaoHex; } - return list.join("
"); + QStringList strings; + strings.append(id); + if (settings->m_atcLabels) + { + if (m_altitudeValid) + { + QStringList row1; + int transitionAlt = 6500; + QChar c = m_altitude >= settings->m_transitionAlt ? 'F' : 'A'; + // Convert altitude to flight level + int fl = m_altitude / 100; + row1.append(QString("%1%2").arg(c).arg(fl)); + // Indicate whether climbing or descending + if (m_verticalRateValid && ((m_verticalRate != 0) || (m_selAltitudeValid && (m_altitude != m_selAltitude)))) + { + QChar dir = m_verticalRate == 0 ? QChar('-') : (m_verticalRate > 0 ? QChar(0x2191) : QChar(0x2193)); + row1.append(dir); + } + if (m_selAltitudeValid && (m_altitude != m_selAltitude)) + { + int selfl = m_selAltitude / 100; + row1.append(QString::number(selfl)); + } + strings.append(row1.join(" ")); + } + QStringList row2; + // Display speed + if (m_groundspeedValid) { + row2.append(QString("G%2").arg(m_groundspeed)); + } else if (m_indicatedAirspeedValid) { + row2.append(QString("I%1").arg(m_indicatedAirspeed)); + } + // Aircraft type + if (m_aircraftInfo && !m_aircraftInfo->m_model.isEmpty()) + { + QString name = m_aircraftInfo->m_model; + int idx; + idx = name.indexOf(' '); + if (idx >= 0) { + name = name.left(idx); + } + idx = name.indexOf('-'); + if (idx >= 0) { + name = name.left(idx); + } + row2.append(name); + } + strings.append(row2.join(" ")); + // FIXME: Add ATC altitude and waypoint from ATC feature + } + return strings.join("
"); } QVariant AircraftModel::data(const QModelIndex &index, int role) const @@ -405,7 +476,7 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const else if (role == AircraftModel::adsbDataRole) { // Create the text to go in the bubble next to the aircraft - return QVariant::fromValue(m_aircrafts[row]->getText()); + return QVariant::fromValue(m_aircrafts[row]->getText(m_settings)); } else if (role == AircraftModel::aircraftImageRole) { @@ -426,10 +497,28 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const } else if (role == AircraftModel::aircraftPathRole) { - if ((m_flightPaths && m_aircrafts[row]->m_isHighlighted) || m_allFlightPaths) - return m_aircrafts[row]->m_coordinates; - else - return QVariantList(); + if ((m_flightPaths && m_aircrafts[row]->m_isHighlighted) || m_allFlightPaths) + { + return m_aircrafts[row]->m_coordinates; + } + else if (m_settings->m_atcLabels) + { + // Display last 20 seconds of coordinates + QDateTime cutoff = QDateTime::currentDateTime().addSecs(-20); + QVariantList coordinates; + for (int i = m_aircrafts[row]->m_coordinateDateTimes.size() - 1; i >= 0; i--) + { + if (m_aircrafts[row]->m_coordinateDateTimes[i] < cutoff) { + break; + } + coordinates.push_front(m_aircrafts[row]->m_coordinates[i]); + } + return coordinates; + } + else + { + return QVariantList(); + } } else if (role == AircraftModel::showAllRole) return QVariant::fromValue(m_aircrafts[row]->m_showAll); @@ -486,6 +575,53 @@ void AircraftModel::findOnMap(int index) FeatureWebAPIUtils::mapFind(m_aircrafts[index]->m_icaoHex); } +// Get list of frequeny scanners to use in menu +QStringList AirportModel::getFreqScanners() const +{ + QStringList list; + std::vector channels = MainCore::instance()->getChannels("sdrangel.channel.freqscanner"); + for (const auto channel : channels) { + list.append(QString("R%1:%2").arg(channel->getDeviceSetIndex()).arg(channel->getIndexInDeviceSet())); + } + return list; +} + +// Send airport frequencies to frequency scanner with given id (Rn:n) +void AirportModel::sendToFreqScanner(int index, const QString& id) +{ + if ((index < 0) || (index >= m_airports.count())) { + return; + } + const AirportInformation *airport = m_airports[index]; + + const QRegularExpression re("R([0-9]+):([0-9]+)"); + QRegularExpressionMatch match = re.match(id); + if (match.hasMatch()) + { + int deviceSet = match.capturedTexts()[1].toInt(); + int channelIndex = match.capturedTexts()[2].toInt(); + + QJsonArray array; + for (const auto airportFrequency : airport->m_frequencies) + { + QJsonObject obj; + QJsonValue frequency(airportFrequency->m_frequency * 1000000); + QJsonValue enabled(1); // true doesn't work + QJsonValue notes(QString("%1 %2").arg(airport->m_ident).arg(airportFrequency->m_description)); + obj.insert("frequency", frequency); + obj.insert("enabled", enabled); + obj.insert("notes", notes); + QJsonValue value(obj); + array.append(value); + } + ChannelWebAPIUtils::patchChannelSetting(deviceSet, channelIndex, "frequencies", array); + } + else + { + qDebug() << "AirportModel::sendToFreqScanner: Malformed id: " << id; + } +} + QVariant AirportModel::data(const QModelIndex &index, int role) const { int row = index.row(); @@ -565,7 +701,12 @@ bool AirportModel::setData(const QModelIndex &index, const QVariant& value, int { int idx = value.toInt(); if ((idx >= 0) && (idx < m_airports[row]->m_frequencies.size())) - m_gui->setFrequency(m_airports[row]->m_frequencies[idx]->m_frequency * 1000000); + { + // Do in two steps to avoid rounding errors + qint64 freqkHz = (qint64) std::round(m_airports[row]->m_frequencies[idx]->m_frequency*1000.0); + qint64 freqHz = freqkHz * 1000; + m_gui->setFrequency(freqHz); + } else if (idx == m_airports[row]->m_frequencies.size()) { // Set airport as target @@ -724,10 +865,56 @@ bool NavAidModel::setData(const QModelIndex &index, const QVariant& value, int r return true; } -// Set selected device to the given centre frequency (used to tune to ATC selected from airports on map) -bool ADSBDemodGUI::setFrequency(float targetFrequencyHz) +// Set selected AM Demod to the given frequency (used to tune to ATC selected from airports on map) +bool ADSBDemodGUI::setFrequency(qint64 targetFrequencyHz) { - return ChannelWebAPIUtils::setCenterFrequency(m_settings.m_deviceIndex, targetFrequencyHz); + const QRegularExpression re("R([0-9]+):([0-9]+)"); + QRegularExpressionMatch match = re.match(m_settings.m_amDemod); + if (match.hasMatch()) + { + int deviceSet = match.capturedTexts()[1].toInt(); + int channelIndex = match.capturedTexts()[2].toInt(); + const int halfChannelBW = 20000/2; + int dcOffset = halfChannelBW; + + double centerFrequency; + int sampleRate; + + if (ChannelWebAPIUtils::getCenterFrequency(deviceSet, centerFrequency)) + { + if (ChannelWebAPIUtils::getDevSampleRate(deviceSet, sampleRate)) + { + sampleRate *= 0.75; // Don't use guard bands + + // Adjust device center frequency if not in range + if ( ((targetFrequencyHz - halfChannelBW) < (centerFrequency - sampleRate / 2)) + || ((targetFrequencyHz + halfChannelBW) >= (centerFrequency + sampleRate / 2)) + ) + { + ChannelWebAPIUtils::setCenterFrequency(deviceSet, targetFrequencyHz - dcOffset); + ChannelWebAPIUtils::setFrequencyOffset(deviceSet, channelIndex, dcOffset); + } + else + { + qint64 offset = targetFrequencyHz - centerFrequency; + // Also adjust center frequency if channel would cross DC + if (std::abs(offset) < halfChannelBW) + { + ChannelWebAPIUtils::setCenterFrequency(deviceSet, targetFrequencyHz - dcOffset); + ChannelWebAPIUtils::setFrequencyOffset(deviceSet, channelIndex, dcOffset); + } + else + { + // Just tune channel + ChannelWebAPIUtils::setFrequencyOffset(deviceSet, channelIndex, offset); + } + } + + return true; + } + } + } + return false; } // Called when we have both lat & long @@ -825,7 +1012,7 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetAvailableUntil(new QString(aircraft->m_positionDateTime.addSecs(60).toString(Qt::ISODateWithMs))); swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage()))); swgMapItem->setImageRotation(aircraft->m_heading); - swgMapItem->setText(new QString(aircraft->getText(true))); + swgMapItem->setText(new QString(aircraft->getText(&m_settings, true))); if (!aircraft->m_aircraft3DModel.isEmpty()) { swgMapItem->setModel(new QString(aircraft->m_aircraft3DModel)); @@ -833,7 +1020,7 @@ void ADSBDemodGUI::sendToMap(Aircraft *aircraft, QListsetModel(new QString(aircraft->m_aircraftCat3DModel)); } - swgMapItem->setLabel(new QString(aircraft->m_callsign)); + swgMapItem->setLabel(new QString(aircraft->getLabel(&m_settings))); if (aircraft->m_headingValid) { @@ -883,6 +1070,7 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) 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_ATC_CALLSIGN, aircraft->m_atcCallsignItem); ui->adsbData->setItem(row, ADSB_COL_CALLSIGN, aircraft->m_callsignItem); ui->adsbData->setItem(row, ADSB_COL_MODEL, aircraft->m_modelItem); ui->adsbData->setItem(row, ADSB_COL_AIRLINE, aircraft->m_airlineItem); @@ -1012,6 +1200,45 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft) return aircraft; } +void ADSBDemodGUI::atcCallsign(Aircraft *aircraft) +{ + QString icao = aircraft->m_callsign.left(3); + const Airline *airline = Airline::getByICAO(icao); + if (airline) + { + aircraft->m_atcCallsignItem->setText(airline->m_callsign); + // Create icons using data from Airline class, if it doesn't exist in database + if (!aircraft->m_aircraftInfo) + { + // Airline logo + QIcon *icon = nullptr; + aircraft->m_airlineIconURL = AircraftInformation::getFlagIconURL(airline->m_icao); + icon = AircraftInformation::getAirlineIcon(airline->m_icao); + if (icon != nullptr) + { + aircraft->m_airlineItem->setSizeHint(QSize(85, 20)); + aircraft->m_airlineItem->setIcon(*icon); + } + else + { + aircraft->m_airlineItem->setText(airline->m_name); + } + // Flag + QString flag = airline->m_country.toLower().replace(" ", "_"); + aircraft->m_flagIconURL = AircraftInformation::getFlagIconPath(flag); + if (aircraft->m_flagIconURL.startsWith(':')) { + aircraft->m_flagIconURL = "qrc://" + aircraft->m_flagIconURL.mid(1); + } + icon = AircraftInformation::getFlagIcon(flag); + if (icon != nullptr) + { + aircraft->m_countryItem->setSizeHint(QSize(40, 20)); + aircraft->m_countryItem->setIcon(*icon); + } + } + } +} + // Try to map callsign to flight number void ADSBDemodGUI::callsignToFlight(Aircraft *aircraft) { @@ -1187,6 +1414,7 @@ void ADSBDemodGUI::handleADSB( { aircraft->m_callsign = callsignTrimmed; aircraft->m_callsignItem->setText(aircraft->m_callsign); + atcCallsign(aircraft); callsignToFlight(aircraft); } @@ -1401,6 +1629,7 @@ void ADSBDemodGUI::handleADSB( aircraft->m_positionDateTime = dateTime; QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); + aircraft->m_coordinateDateTimes.push_back(dateTime); updatePosition(aircraft); } } @@ -1457,6 +1686,7 @@ void ADSBDemodGUI::handleADSB( aircraft->m_positionDateTime = dateTime; QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); + aircraft->m_coordinateDateTimes.push_back(dateTime); } } } @@ -2253,7 +2483,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, 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; + 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; @@ -2295,6 +2525,7 @@ void ADSBDemodGUI::decodeCommB(const QByteArray data, const QDateTime dateTime, { aircraft->m_callsign = callsignTrimmed; aircraft->m_callsignItem->setText(aircraft->m_callsign); + atcCallsign(aircraft); callsignToFlight(aircraft); } } @@ -3661,6 +3892,13 @@ void ADSBDemodGUI::on_allFlightPaths_clicked(bool checked) m_aircraftModel.setAllFlightPaths(checked); } +void ADSBDemodGUI::on_atcLabels_clicked(bool checked) +{ + m_settings.m_atcLabels = checked; + m_aircraftModel.setSettings(&m_settings); + applySettings(); +} + QString ADSBDemodGUI::getDataDir() { // Get directory to store app data in (aircraft & airport databases and user-definable icons) @@ -3726,59 +3964,41 @@ void ADSBDemodGUI::onMenuDialogCalled(const QPoint &p) resetContextMenuType(); } -void ADSBDemodGUI::updateDeviceSetList() +void ADSBDemodGUI::updateChannelList() { - MainWindow *mainWindow = MainWindow::getInstance(); - std::vector& deviceUISets = mainWindow->getDeviceUISets(); - std::vector::const_iterator it = deviceUISets.begin(); + std::vector channels = MainCore::instance()->getChannels("sdrangel.channel.amdemod"); - ui->device->blockSignals(true); + ui->amDemod->blockSignals(true); + ui->amDemod->clear(); - ui->device->clear(); - unsigned int deviceIndex = 0; - - for (; it != deviceUISets.end(); ++it, deviceIndex++) - { - DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine; - - if (deviceSourceEngine) { - ui->device->addItem(QString("R%1").arg(deviceIndex), deviceIndex); - } + for (const auto channel : channels) { + ui->amDemod->addItem(QString("R%1:%2").arg(channel->getDeviceSetIndex()).arg(channel->getIndexInDeviceSet())); } - int newDeviceIndex; - - if (it != deviceUISets.begin()) - { - if (m_settings.m_deviceIndex < 0) { - ui->device->setCurrentIndex(0); - } else if (m_settings.m_deviceIndex >= (int) deviceUISets.size()) { - ui->device->setCurrentIndex(deviceUISets.size() - 1); - } else { - ui->device->setCurrentIndex(m_settings.m_deviceIndex); - } - - newDeviceIndex = ui->device->currentData().toInt(); - } - else - { - newDeviceIndex = -1; + // Select current setting, if exists + // If not, make sure nothing selected, as channel may be created later on + int idx = ui->amDemod->findText(m_settings.m_amDemod); + if (idx >= 0) { + ui->amDemod->setCurrentIndex(idx); + } else { + ui->amDemod->setCurrentIndex(-1); } - if (newDeviceIndex != m_settings.m_deviceIndex) - { - qDebug("ADSBDemodGUI::updateDeviceSetLists: device index changed: %d", newDeviceIndex); - m_settings.m_deviceIndex = newDeviceIndex; - } + ui->amDemod->blockSignals(false); - ui->device->blockSignals(false); + // If no current settting, select first channel + if (m_settings.m_amDemod.isEmpty()) + { + ui->amDemod->setCurrentIndex(0); + on_amDemod_currentIndexChanged(0); + } } -void ADSBDemodGUI::on_device_currentIndexChanged(int index) +void ADSBDemodGUI::on_amDemod_currentIndexChanged(int index) { if (index >= 0) { - m_settings.m_deviceIndex = ui->device->currentData().toInt(); + m_settings.m_amDemod = ui->amDemod->currentText(); applySettings(); } } @@ -4790,11 +5010,11 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(&m_planeSpotters, &PlaneSpotters::aircraftPhoto, this, &ADSBDemodGUI::aircraftPhoto); connect(ui->photo, &ClickableLabel::clicked, this, &ADSBDemodGUI::photoClicked); - // Update device list when devices are added or removed - connect(MainCore::instance(), &MainCore::deviceSetAdded, this, &ADSBDemodGUI::updateDeviceSetList); - connect(MainCore::instance(), &MainCore::deviceSetRemoved, this, &ADSBDemodGUI::updateDeviceSetList); + // Update demod list when channels are added or removed + connect(MainCore::instance(), &MainCore::channelAdded, this, &ADSBDemodGUI::updateChannelList); + connect(MainCore::instance(), &MainCore::channelRemoved, this, &ADSBDemodGUI::updateChannelList); - updateDeviceSetList(); + updateChannelList(); displaySettings(); makeUIConnections(); applySettings(true); @@ -4907,6 +5127,8 @@ void ADSBDemodGUI::displaySettings() ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths); m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths); + m_aircraftModel.setSettings(&m_settings); + ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename)); ui->logEnable->setChecked(m_settings.m_logEnabled); @@ -5051,6 +5273,40 @@ void ADSBDemodGUI::tick() } } } + + // Create and send aircraft report every second for WebAPI + if (m_tickCount % (20*1) == 0) { + sendAircraftReport(); + } +} + +void ADSBDemodGUI::sendAircraftReport() +{ + ADSBDemod::MsgAircraftReport* message = ADSBDemod::MsgAircraftReport::create(); + + QList& report = message->getReport(); + report.reserve(m_aircraft.size()); + + QHash::iterator i = m_aircraft.begin(); + while (i != m_aircraft.end()) + { + Aircraft *aircraft = i.value(); + + ADSBDemod::MsgAircraftReport::AircraftReport aircraftReport { + aircraft->m_icaoHex, + aircraft->m_callsign, + aircraft->m_latitude, + aircraft->m_longitude, + aircraft->m_altitude, + aircraft->m_groundspeed + }; + + report.append(aircraftReport); + + ++i; + } + + m_adsbDemod->getInputMessageQueue()->push(message); } void ADSBDemodGUI::resizeTable() @@ -5059,7 +5315,8 @@ 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_ATC_CALLSIGN, new QTableWidgetItem("ATC 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)")); @@ -5611,6 +5868,7 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) { aircraft->m_callsign = callsign; aircraft->m_callsignItem->setText(aircraft->m_callsign); + atcCallsign(aircraft); } QDateTime timePosition = dateTime; @@ -5648,6 +5906,7 @@ void ADSBDemodGUI::handleImportReply(QNetworkReply* reply) { QGeoCoordinate coord(aircraft->m_latitude, aircraft->m_longitude, aircraft->m_altitude); aircraft->m_coordinates.push_back(QVariant::fromValue(coord)); + aircraft->m_coordinateDateTimes.push_back(dateTime); } aircraft->m_positionDateTime = timePosition; } @@ -5824,7 +6083,8 @@ void ADSBDemodGUI::makeUIConnections() QObject::connect(ui->getAirspacesDB, &QToolButton::clicked, this, &ADSBDemodGUI::on_getAirspacesDB_clicked); QObject::connect(ui->flightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_flightPaths_clicked); QObject::connect(ui->allFlightPaths, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_allFlightPaths_clicked); - QObject::connect(ui->device, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_device_currentIndexChanged); + QObject::connect(ui->atcLabels, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_atcLabels_clicked); + QObject::connect(ui->amDemod, QOverload::of(&QComboBox::currentIndexChanged), this, &ADSBDemodGUI::on_amDemod_currentIndexChanged); QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &ADSBDemodGUI::on_displaySettings_clicked); QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &ADSBDemodGUI::on_logEnable_clicked); QObject::connect(ui->logFilename, &QToolButton::clicked, this, &ADSBDemodGUI::on_logFilename_clicked); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.h b/plugins/channelrx/demodadsb/adsbdemodgui.h index f411d0484..2df3c69ac 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.h +++ b/plugins/channelrx/demodadsb/adsbdemodgui.h @@ -48,6 +48,7 @@ #include "adsbdemodsettings.h" #include "util/ourairportsdb.h" #include "util/osndb.h" +#include "util/airlines.h" class PluginAPI; class DeviceUISet; @@ -150,6 +151,7 @@ struct Aircraft { bool m_showAll; QVariantList m_coordinates; // Coordinates we've recorded the aircraft at + QList m_coordinateDateTimes; AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database QString m_aircraft3DModel; // 3D model for map based on aircraft type @@ -180,6 +182,7 @@ struct Aircraft { // GUI table items for above data QTableWidgetItem *m_icaoItem; QTableWidgetItem *m_callsignItem; + QTableWidgetItem* m_atcCallsignItem; QTableWidgetItem *m_modelItem; QTableWidgetItem *m_airlineItem; QTableWidgetItem *m_latitudeItem; @@ -299,6 +302,7 @@ struct Aircraft { // These are deleted by QTableWidget m_icaoItem = new QTableWidgetItem(); m_callsignItem = new QTableWidgetItem(); + m_atcCallsignItem = new QTableWidgetItem(); m_modelItem = new QTableWidgetItem(); m_airlineItem = new QTableWidgetItem(); m_altitudeItem = new QTableWidgetItem(); @@ -353,9 +357,11 @@ struct Aircraft { } QString getImage() const; - QString getText(bool all=false) const; + QString getText(const ADSBDemodSettings *settings, bool all=false) const; + // Label to use for aircraft on map + QString getLabel(const ADSBDemodSettings *settings) const; - // Name to use when selected as a target + // Name to use when selected as a target (E.g. for use as target name in Rotator Controller) QString targetName() { if (!m_callsign.isEmpty()) @@ -463,6 +469,12 @@ public: allAircraftUpdated(); } + void setSettings(const ADSBDemodSettings *settings) + { + m_settings = settings; + allAircraftUpdated(); + } + Q_INVOKABLE void findOnMap(int index); void updateAircraftInformation(QSharedPointer> aircraftInfo) @@ -481,6 +493,7 @@ private: QList m_aircrafts; bool m_flightPaths; bool m_allFlightPaths; + const ADSBDemodSettings *m_settings; }; // Airport data model used by QML map item @@ -623,6 +636,9 @@ public: } } + Q_INVOKABLE QStringList getFreqScanners() const; + Q_INVOKABLE void sendToFreqScanner(int index, const QString& id); + private: ADSBDemodGUI *m_gui; QList m_airports; @@ -874,7 +890,7 @@ public: void highlightAircraft(Aircraft *aircraft); void targetAircraft(Aircraft *aircraft); void target(const QString& name, float az, float el, float range); - bool setFrequency(float frequency); + bool setFrequency(qint64 frequency); bool useSIUints() const { return m_settings.m_siUnits; } Q_INVOKABLE void clearHighlighted(); QString get3DModel(const QString &aircraft, const QString &operatorICAO) const; @@ -974,6 +990,7 @@ private: void clearFromMap(const QString& name); void sendToMap(Aircraft *aircraft, QList *animations); Aircraft *getAircraft(int icao, bool &newAircraft); + void atcCallsign(Aircraft *aircraft); void callsignToFlight(Aircraft *aircraft); int roundTo50Feet(int alt); bool calcAirTemp(Aircraft *aircraft); @@ -1006,7 +1023,7 @@ private: void updateAirports(); void updateAirspaces(); void updateNavAids(); - void updateDeviceSetList(); + void updateChannelList(); QAction *createCheckableItem(QString& text, int idx, bool checked); Aircraft* findAircraftByFlight(const QString& flight); QString dataTimeToShortString(QDateTime dt); @@ -1021,6 +1038,7 @@ private: int grayToBinary(int gray, int bits) const; void redrawMap(); void applyImportSettings(); + void sendAircraftReport(); void leaveEvent(QEvent*); void enterEvent(EnterEventType*); @@ -1050,11 +1068,12 @@ private slots: void on_getAirspacesDB_clicked(); void on_flightPaths_clicked(bool checked); void on_allFlightPaths_clicked(bool checked); + void on_atcLabels_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); void tick(); - void on_device_currentIndexChanged(int index); + void on_amDemod_currentIndexChanged(int index); void feedSelect(const QPoint& p); void on_displaySettings_clicked(); void on_logEnable_clicked(bool checked=false); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.ui b/plugins/channelrx/demodadsb/adsbdemodgui.ui index 6b0847972..1cab19173 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.ui +++ b/plugins/channelrx/demodadsb/adsbdemodgui.ui @@ -511,7 +511,7 @@
- :/icons/controltower.png:/icons/controltower.png + :/icons/airport.png:/icons/airport.png
@@ -583,6 +583,26 @@
+ + + + Display altitude, speed and short paths for all aircraft + + + ^ + + + + :/icons/controltower.png:/icons/controltower.png + + + true + + + true + + + @@ -729,16 +749,16 @@ - + - Device + AM - + - Receive device set to set frequency on when selecting an ATC frequency on the map + AM Demod to tune when selecting an ATC frequency on the map @@ -863,6 +883,14 @@ Callsign. Links to www.flightradar24.com + + + ATC Callsign + + + Airline callsign used by ATC + + Aircraft @@ -1478,7 +1506,7 @@ logFilename logOpen findOnMapFeature - device + amDemod adsbData map diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp index 6312c319b..a7bb870a4 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp @@ -77,7 +77,6 @@ void ADSBDemodSettings::resetToDefaults() m_displayDemodStats = false; m_correlateFullPreamble = true; m_demodModeS = true; - m_deviceIndex = -1; 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 @@ -105,6 +104,9 @@ void ADSBDemodSettings::resetToDefaults() m_aircraftMinZoom = 11; m_workspaceIndex = 0; m_hidden = false; + m_atcLabels = true; + m_atcCallsigns = true; + m_transitionAlt = 6000; // Depends on airport. 18,000 in USA } QByteArray ADSBDemodSettings::serialize() const @@ -136,7 +138,6 @@ QByteArray ADSBDemodSettings::serialize() const s.writeS32(19, (int)m_airportMinimumSize); s.writeBool(20, m_displayHeliports); s.writeBool(21, m_flightPaths); - s.writeS32(22, m_deviceIndex); s.writeBool(23, m_siUnits); s.writeS32(24, (int)m_exportClientFormat); s.writeString(25, m_tableFontName); @@ -188,6 +189,11 @@ QByteArray ADSBDemodSettings::serialize() const s.writeString(63, m_mapProvider); s.writeS32(64, m_aircraftMinZoom); + s.writeBool(65, m_atcLabels); + s.writeBool(66, m_atcCallsigns); + s.writeS32(67, m_transitionAlt); + s.writeString(68, m_amDemod); + for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) { s.writeS32(100 + i, m_columnIndexes[i]); } @@ -259,7 +265,6 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data) d.readS32(19, (int *)&m_airportMinimumSize, AirportType::Medium); d.readBool(20, &m_displayHeliports, false); d.readBool(21, &m_flightPaths, true); - d.readS32(22, &m_deviceIndex, -1); d.readBool(23, &m_siUnits, false); d.readS32(24, (int *) &m_exportClientFormat, BeastBinary); d.readString(25, &m_tableFontName, "Liberation Sans"); @@ -319,6 +324,12 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data) d.readString(62, &m_checkWXAPIKey, ""); d.readString(63, &m_mapProvider, "osm"); d.readS32(64, &m_aircraftMinZoom, 11); + + d.readBool(65, &m_atcLabels, true); + d.readBool(66, &m_atcCallsigns, true); + d.readS32(67, &m_transitionAlt, 6000); + d.readString(68, &m_amDemod); + #ifdef LINUX if (m_mapProvider == "osm") { m_mapProvider = "mapboxgl"; diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.h b/plugins/channelrx/demodadsb/adsbdemodsettings.h index 54af2b81e..5187ea3da 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.h +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.h @@ -29,62 +29,63 @@ class Serializable; // Number of columns in the table -#define ADSBDEMOD_COLUMNS 53 +#define ADSBDEMOD_COLUMNS 54 // 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_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 +#define ADSB_COL_ATC_CALLSIGN 2 +#define ADSB_COL_MODEL 3 +#define ADSB_COL_AIRLINE 4 +#define ADSB_COL_COUNTRY 5 +#define ADSB_COL_GROUND_SPEED 6 +#define ADSB_COL_TRUE_AIRSPEED 7 +#define ADSB_COL_INDICATED_AIRSPEED 8 +#define ADSB_COL_MACH 9 +#define ADSB_COL_SEL_ALTITUDE 10 +#define ADSB_COL_ALTITUDE 11 +#define ADSB_COL_VERTICALRATE 12 +#define ADSB_COL_SEL_HEADING 13 +#define ADSB_COL_HEADING 14 +#define ADSB_COL_TURNRATE 15 +#define ADSB_COL_ROLL 16 +#define ADSB_COL_RANGE 17 +#define ADSB_COL_AZEL 18 +#define ADSB_COL_CATEGORY 19 +#define ADSB_COL_STATUS 20 +#define ADSB_COL_SQUAWK 21 +#define ADSB_COL_REGISTRATION 22 +#define ADSB_COL_REGISTERED 23 +#define ADSB_COL_MANUFACTURER 24 +#define ADSB_COL_OWNER 25 +#define ADSB_COL_OPERATOR_ICAO 26 +#define ADSB_COL_AP 27 +#define ADSB_COL_V_MODE 28 +#define ADSB_COL_L_MODE 29 +#define ADSB_COL_BARO 30 +#define ADSB_COL_HEADWIND 31 +#define ADSB_COL_EST_AIR_TEMP 32 +#define ADSB_COL_WIND_SPEED 33 +#define ADSB_COL_WIND_DIR 34 +#define ADSB_COL_STATIC_PRESSURE 35 +#define ADSB_COL_STATIC_AIR_TEMP 36 +#define ADSB_COL_HUMIDITY 37 +#define ADSB_COL_LATITUDE 38 +#define ADSB_COL_LONGITUDE 39 +#define ADSB_COL_TIME 40 +#define ADSB_COL_FRAMECOUNT 41 +#define ADSB_COL_TIS_B 42 +#define ADSB_COL_CORRELATION 43 +#define ADSB_COL_RSSI 44 +#define ADSB_COL_FLIGHT_STATUS 45 +#define ADSB_COL_DEP 46 +#define ADSB_COL_ARR 47 +#define ADSB_COL_STD 48 +#define ADSB_COL_ETD 49 +#define ADSB_COL_ATD 50 +#define ADSB_COL_STA 51 +#define ADSB_COL_ETA 52 +#define ADSB_COL_ATA 53 struct ADSBDemodSettings { @@ -160,7 +161,7 @@ struct ADSBDemodSettings bool m_displayDemodStats; bool m_correlateFullPreamble; bool m_demodModeS; //!< Demodulate all Mode-S frames, not just ADS-B - int m_deviceIndex; //!< Device to set to ATC frequencies + QString m_amDemod; //!< AM Demod to tune to selected ATC frequency bool m_autoResizeTableColumns; int m_interpolatorPhaseSteps; float m_interpolatorTapsPerPhase; @@ -188,6 +189,10 @@ struct ADSBDemodSettings int m_airfieldElevation; //!< QFE in ft so aircraft takeoff/land from correct position int m_aircraftMinZoom; + bool m_atcLabels; + bool m_atcCallsigns; + int m_transitionAlt; + ADSBDemodSettings(); void resetToDefaults(); void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } diff --git a/plugins/channelrx/demodadsb/icons.qrc b/plugins/channelrx/demodadsb/icons.qrc index 78a629ff7..88b132d97 100644 --- a/plugins/channelrx/demodadsb/icons.qrc +++ b/plugins/channelrx/demodadsb/icons.qrc @@ -1,6 +1,7 @@ icons/aircraft.png + icons/airport.png icons/controltower.png icons/allflightpaths.png icons/vor.png diff --git a/plugins/channelrx/demodadsb/icons/airport.png b/plugins/channelrx/demodadsb/icons/airport.png new file mode 100644 index 000000000..cd8685549 Binary files /dev/null and b/plugins/channelrx/demodadsb/icons/airport.png differ diff --git a/plugins/channelrx/demodadsb/map/map.qml b/plugins/channelrx/demodadsb/map/map.qml index 7bb791629..d1c466dda 100644 --- a/plugins/channelrx/demodadsb/map/map.qml +++ b/plugins/channelrx/demodadsb/map/map.qml @@ -328,61 +328,189 @@ Item { Component { id: airportComponent - MapQuickItem { - id: aircraft - anchorPoint.x: image.width/2 - anchorPoint.y: image.height/2 - coordinate: position - zoomLevel: airportZoomLevel + MapItemGroup { + MapItemGroup { + property var groupVisible: false + id: rangeGroup + MapCircle { + id: circle5nm + center: position + color: "transparent" + border.color: "gray" + radius: 9260 // 5nm in metres + visible: rangeGroup.groupVisible + } + MapCircle { + id: circle10nm + center: position + color: "transparent" + border.color: "gray" + radius: 18520 + visible: rangeGroup.groupVisible + } + MapCircle { + id: circle15nm + center: airport.coordinate + color: "transparent" + border.color: "gray" + radius: 27780 + visible: rangeGroup.groupVisible + } + MapQuickItem { + id: text5nm + coordinate { + latitude: position.latitude + longitude: position.longitude + (5/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180) + } + anchorPoint.x: 0 + anchorPoint.y: height/2 + sourceItem: Text { + color: "grey" + text: "5nm" + } + visible: rangeGroup.groupVisible + } + MapQuickItem { + id: text10nm + coordinate { + latitude: position.latitude + longitude: position.longitude + (10/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180) + } + anchorPoint.x: 0 + anchorPoint.y: height/2 + sourceItem: Text { + color: "grey" + text: "10nm" + } + visible: rangeGroup.groupVisible + } + MapQuickItem { + id: text15nm + coordinate { + latitude: position.latitude + longitude: position.longitude + (15/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180) + } + anchorPoint.x: 0 + anchorPoint.y: height/2 + sourceItem: Text { + color: "grey" + text: "15nm" + } + visible: rangeGroup.groupVisible + } + } - sourceItem: Grid { - columns: 1 - Grid { - horizontalItemAlignment: Grid.AlignHCenter - layer.enabled: smoothing - layer.smooth: smoothing - Image { - id: image - source: airportImage - visible: !lightIcons - } - ColorOverlay { - cached: true - width: image.width - height: image.height - source: image - color: "#c0ffffff" - visible: lightIcons - } - Rectangle { - id: bubble - color: bubbleColour - border.width: 1 - width: text.width + 5 - height: text.height + 5 - radius: 5 - Text { - id: text - anchors.centerIn: parent - text: airportData - } - MouseArea { - anchors.fill: parent - onClicked: (mouse) => { - if (showFreq) { - var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows)) - if (freqIdx == 0) { - showFreq = false + MapQuickItem { + id: airport + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + coordinate: position + zoomLevel: airportZoomLevel + sourceItem: Grid { + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + layer.enabled: smoothing + layer.smooth: smoothing + Image { + id: image + source: airportImage + visible: !lightIcons + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: (mouse) => { + if (mouse.button === Qt.RightButton) { + showRangeItem.visible = !rangeGroup.groupVisible + hideRangeItem.visible = rangeGroup.groupVisible + menuItems.clear() + var scanners = airportModel.getFreqScanners() + for (var i = 0; i < scanners.length; i++) { + menuItems.append({ + text: "Send to Frequency Scanner " + scanners[i], + airport: index, + scanner: scanners[i] + }) + } + contextMenu.popup() + } + } + onDoubleClicked: (mouse) => { + rangeGroup.groupVisible = !rangeGroup.groupVisible + } + + ListModel { + id: menuItems + } + + Menu { + id: contextMenu + MenuItem { + id: showRangeItem + text: "Show range rings" + onTriggered: rangeGroup.groupVisible = true + height: visible ? implicitHeight : 0 + } + MenuItem { + id: hideRangeItem + text: "Hide range rings" + onTriggered: rangeGroup.groupVisible = false + height: visible ? implicitHeight : 0 + } + Instantiator { + model: menuItems + MenuItem { + text: model.text + onTriggered: airportModel.sendToFreqScanner(model.airport, model.scanner) + } + onObjectAdded: function(index, object) { + contextMenu.insertItem(index, object) + } + onObjectRemoved: function(index, object) { + contextMenu.removeItem(object) + } } - } else { - showFreq = true } } - onDoubleClicked: (mouse) => { - if (showFreq) { - var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows)) - if (freqIdx != 0) { - selectedFreq = freqIdx - 1 + } + ColorOverlay { + cached: true + width: image.width + height: image.height + source: image + color: "#c0ffffff" + visible: lightIcons + } + Rectangle { + id: bubble + color: bubbleColour + border.width: 1 + width: text.width + 5 + height: text.height + 5 + radius: 5 + Text { + id: text + anchors.centerIn: parent + text: airportData + } + MouseArea { + anchors.fill: parent + onClicked: (mouse) => { + if (showFreq) { + var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows)) + if (freqIdx == 0) { + showFreq = false + } + } else { + showFreq = true + } + } + onDoubleClicked: (mouse) => { + if (showFreq) { + var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows)) + if (freqIdx != 0) { + selectedFreq = freqIdx - 1 + } } } } diff --git a/plugins/channelrx/demodadsb/readme.md b/plugins/channelrx/demodadsb/readme.md index 436038a57..d5049af48 100644 --- a/plugins/channelrx/demodadsb/readme.md +++ b/plugins/channelrx/demodadsb/readme.md @@ -80,11 +80,13 @@ Clicking the Display Settings button will open the Display Settings dialog, whic * What category of airspaces should be displayed. * The distance (in kilometres), from the location set under Preferences > My Position, at which airspaces will be displayed on the map. Displaying too many airspaces will slow down drawing of the map. * Whether NAVAIDs, such as VORs, are displayed on the map. +* Whether callsigns as said by ATC (E.g. Speedbird) are used on the map instead of the airline ICAO designator (E.g. BAW). * Whether aircraft photos are displayed for the highlighted aircraft. * The timeout, in seconds, after which an aircraft will be removed from the table and map, if an ADS-B frame has not been received from it. * The font used for the table. * 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. +* The transistion altitude in feet for use in ATC mode. Below the TA, altitude will be displayed. Above the TA flight levels will be displayed. You can also enter an [aviationstack](https://aviationstack.com/product) API key, needed to download flight information (such as departure and arrival airports and times). @@ -100,6 +102,10 @@ Checking this button draws a line on the map showing the highlighted aircraft's Checking this button draws flight paths for all aircraft. +

ATC Mode

+ +When in ATC mode, the map will display callsign, alitude, ground speed and type for all aircraft. When unchecked, only callsign (or ICAO, until callsign is received) will be displayed. +

15: 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. @@ -235,9 +241,9 @@ Click to specify the name of the .csv file which received ADS-B frames are logge Click to specify a previously written ADS-B .csv log file, which is read and used to update the ADS-B data table and map. -

21: Select device set

+

21: AM Demod

-Specify the SDRangel device set that will be have its centre frequency set when an airport ATC frequency is clicked on the map. Typically, this device set would be a second SDR (as ATC frequencies are around 120MHz, so they can not be received simultaneously with 1090MHz for ADS-B) and have an AM Demodulator channel plugin. +Specify the AM Demodulator that will be have its centre frequency set when an airport ATC frequency is clicked on the map.

ADS-B Data

@@ -247,6 +253,7 @@ The table displays the decoded ADS-B and Mode-S data for each aircraft along sid * 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 / Mode-S) +* ATC callsign - Callsign used by ATC (E.g. Speedbird for British Airways). * Aircraft - The aircraft model. (DB) * Airline - The logo of the operator of the aircraft (or owner if no operator known). (DB) * Country - The flag of the country the aircraft is registered in. (DB) @@ -321,7 +328,7 @@ The map displays aircraft locations and data geographically. Four types of map c The antenna location is placed according to My Position set under the Preferences > My Position menu. If My Position is not set correctly, the position of aircraft may not be computed correctly. -Aircraft are only placed upon the map when a position can be calculated, which can require several frames to be received. +Aircraft are only placed upon the map when a position can be calculated, which can require several ADS-B frames to be received. * To pan around the map, click the left mouse button and drag. To zoom in or out, use the mouse scroll wheel. * Left clicking on an aircraft will highlight the corresponding row in the table for the aircraft and the information box on the map will be coloured orange, rather than blue. @@ -330,13 +337,14 @@ Aircraft are only placed upon the map when a position can be calculated, which c * Left clicking the information box next to an airport will reveal ATC frequencies for the airport (if the OurAirports database has been downloaded) and METAR weather information (if the CheckWX API key has been entered). The METAR for the airport is downloaded each time the information box is opened. This information box can be closed by left clicking on the airport identifier. -Double clicking on one of the listed frequencies, will set it as the centre frequency on the selected SDRangel device set (21). +Double clicking on one of the listed frequencies, will tune the AM Demod (21) to that frequency. The Az/El row gives the azimuth and elevation of the airport from the location set under Preferences > My Position. Double clicking on this row will set the airport as the active target. +* Right clicking on an airport will display a popup menu, allowing range rings to be shown or hidden, and for the ATC frequencies for the airport to be send to a Frequency Scanner.

Attribution

Airline logos and flags are by Steve Hibberd from https://radarspotting.com -Map icons are by Alice Design, Alex Ahineev, Botho Willer, Verry Obito, Sean Maldjia, Tinashe Mugayi, Georgiana Ionescu, Andreas Vögele, Tom Fricker, Will Sullivan, Tim Tores, BGBOXXX Design, and Angriawan Ditya Zulkarnain from the Noun Project https://thenounproject.com/ +Map icons are by Cuperto, Alice Design, Alex Ahineev, Botho Willer, Verry Obito, Sean Maldjia, Tinashe Mugayi, Georgiana Ionescu, Andreas Vögele, Tom Fricker, Will Sullivan, Tim Tores, BGBOXXX Design, and Angriawan Ditya Zulkarnain from the Noun Project https://thenounproject.com/ NDB icon is by Inductiveload from WikiMedia. diff --git a/plugins/channelrx/freqscanner/freqscanner.cpp b/plugins/channelrx/freqscanner/freqscanner.cpp index 3d56f9d82..91665d72f 100644 --- a/plugins/channelrx/freqscanner/freqscanner.cpp +++ b/plugins/channelrx/freqscanner/freqscanner.cpp @@ -33,7 +33,6 @@ #include "SWGWorkspaceInfo.h" #include "SWGFreqScannerSettings.h" #include "SWGChannelReport.h" -#include "SWGMapItem.h" #include "device/deviceset.h" #include "dsp/dspengine.h" @@ -673,7 +672,6 @@ void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStri } if (settingsKeys.contains("frequencies") - || settingsKeys.contains("enabled") || settingsKeys.contains("priority") || settingsKeys.contains("measurement") || settingsKeys.contains("mode") @@ -785,6 +783,26 @@ void FreqScanner::webapiUpdateChannelSettings( if (channelSettingsKeys.contains("threshold")) { settings.m_threshold = response.getFreqScannerSettings()->getThreshold(); } + if (channelSettingsKeys.contains("frequencies")) + { + settings.m_frequencies.clear(); + settings.m_enabled.clear(); + settings.m_notes.clear(); + QList *frequencies = response.getFreqScannerSettings()->getFrequencies(); + if (frequencies) + { + for (const auto frequency : *frequencies) + { + settings.m_frequencies.append(frequency->getFrequency()); + settings.m_enabled.append((bool)frequency->getEnabled()); + if (frequency->getNotes()) { + settings.m_notes.append(*frequency->getNotes()); + } else { + settings.m_notes.append(""); + } + } + } + } if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getFreqScannerSettings()->getRgbColor(); } @@ -817,12 +835,36 @@ void FreqScanner::webapiUpdateChannelSettings( } } +QList *FreqScanner::createFrequencyList(const FreqScannerSettings& settings) +{ + QList *frequencies = new QList(); + for (int i = 0; i < settings.m_frequencies.size(); i++) + { + SWGSDRangel::SWGFreqScannerFrequency *frequency = new SWGSDRangel::SWGFreqScannerFrequency(); + frequency->init(); + frequency->setFrequency(settings.m_frequencies[i]); + frequency->setEnabled(settings.m_enabled[i]); + if (!settings.m_notes[i].isEmpty()) { + frequency->setNotes(new QString(settings.m_notes[i])); + } + frequencies->append(frequency); + } + return frequencies; +} + void FreqScanner::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreqScannerSettings& settings) { response.getFreqScannerSettings()->setChannelFrequencyOffset(settings.m_channelFrequencyOffset); response.getFreqScannerSettings()->setChannelBandwidth(settings.m_channelBandwidth); response.getFreqScannerSettings()->setThreshold(settings.m_threshold); + QList *frequencies = createFrequencyList(settings); + if (response.getFreqScannerSettings()->getFrequencies()) { + *response.getFreqScannerSettings()->getFrequencies() = *frequencies; + } else { + response.getFreqScannerSettings()->setFrequencies(frequencies); + } + response.getFreqScannerSettings()->setRgbColor(settings.m_rgbColor); if (response.getFreqScannerSettings()->getTitle()) { *response.getFreqScannerSettings()->getTitle() = settings.m_title; @@ -927,6 +969,15 @@ void FreqScanner::webapiFormatChannelSettings( if (channelSettingsKeys.contains("threshold") || force) { swgFreqScannerSettings->setThreshold(settings.m_threshold); } + if (channelSettingsKeys.contains("frequencies") || force) { + QList *frequencies = createFrequencyList(settings); + if (swgFreqScannerSettings->getFrequencies()) { + *swgFreqScannerSettings->getFrequencies() = *frequencies; + } else { + swgFreqScannerSettings->setFrequencies(frequencies); + } + } + if (channelSettingsKeys.contains("rgbColor") || force) { swgFreqScannerSettings->setRgbColor(settings.m_rgbColor); } diff --git a/plugins/channelrx/freqscanner/freqscanner.h b/plugins/channelrx/freqscanner/freqscanner.h index cd65b1fcf..f1188e2b6 100644 --- a/plugins/channelrx/freqscanner/freqscanner.h +++ b/plugins/channelrx/freqscanner/freqscanner.h @@ -32,6 +32,8 @@ #include "freqscannerbaseband.h" #include "freqscannersettings.h" +#include "SWGChannelSettings.h" + class QNetworkAccessManager; class QNetworkReply; class QThread; @@ -407,6 +409,8 @@ private: void processScanResults(const QDateTime& fftStartTime, const QList& results); void setDeviceCenterFrequency(qint64 frequency); + static QList *createFrequencyList(const FreqScannerSettings& settings); + private slots: void networkManagerFinished(QNetworkReply *reply); void handleIndexInDeviceSetChanged(int index); diff --git a/plugins/channelrx/freqscanner/freqscannergui.cpp b/plugins/channelrx/freqscanner/freqscannergui.cpp index 85ff90db9..7df687c50 100644 --- a/plugins/channelrx/freqscanner/freqscannergui.cpp +++ b/plugins/channelrx/freqscanner/freqscannergui.cpp @@ -630,12 +630,7 @@ void FreqScannerGUI::on_addRange_clicked() addRow(f, true); } blockApplySettings(false); - QList settingsKeys({ - "frequencies", - "enabled", - "notes" - }); - applySettings(settingsKeys); + applySetting("frequencies"); } } @@ -651,12 +646,7 @@ void FreqScannerGUI::on_remove_clicked() m_settings.m_enabled.removeAt(row); m_settings.m_notes.removeAt(row); } - QList settingsKeys({ - "frequencies", - "enabled", - "notes" - }); - applySettings(settingsKeys); + applySetting("frequencies"); } void FreqScannerGUI::on_removeInactive_clicked() @@ -671,15 +661,9 @@ void FreqScannerGUI::on_removeInactive_clicked() m_settings.m_notes.removeAt(i); } } - QList settingsKeys({ - "frequencies", - "enabled", - "notes" - }); - applySettings(settingsKeys); + applySetting("frequencies"); } - static QList takeRow(QTableWidget* table, int row) { QList rowItems; @@ -754,22 +738,17 @@ void FreqScannerGUI::on_table_cellChanged(int row, int column) } m_settings.m_frequencies[row] = value; updateAnnotation(row); - QList settingsKeys({ - "frequencies", - "enabled", - "notes" - }); - applySettings(settingsKeys); + applySetting("frequencies"); } else if (column == COL_ENABLE) { m_settings.m_enabled[row] = item->checkState() == Qt::Checked; - applySetting("enabled"); + applySetting("frequencies"); } else if (column == COL_NOTES) { m_settings.m_notes[row] = item->text(); - applySetting("notes"); + applySetting("frequencies"); } } } diff --git a/plugins/channelrx/freqscanner/freqscannersettings.cpp b/plugins/channelrx/freqscanner/freqscannersettings.cpp index 8c6a00a15..139f3d6e6 100644 --- a/plugins/channelrx/freqscanner/freqscannersettings.cpp +++ b/plugins/channelrx/freqscanner/freqscannersettings.cpp @@ -203,11 +203,7 @@ void FreqScannerSettings::applySettings(const QStringList& settingsKeys, const F } if (settingsKeys.contains("frequencies")) { m_frequencies = settings.m_frequencies; - } - if (settingsKeys.contains("enabled")) { m_enabled = settings.m_enabled; - } - if (settingsKeys.contains("notes")) { m_notes = settings.m_notes; } if (settingsKeys.contains("channel")) { @@ -293,19 +289,6 @@ QString FreqScannerSettings::getDebugString(const QStringList& settingsKeys, boo } ostr << " m_frequencies: " << s.join(",").toStdString(); } - if (settingsKeys.contains("enabled") || force) - { - // Don't display - /*QStringList s; - for (auto e : m_enabled) { - s.append(e ? "true" : "false"); - } - ostr << " m_enabled: " << s.join(",").toStdString();*/ - } - if (settingsKeys.contains("notes") || force) { - // Don't display - //ostr << " m_notes: " << m_notes.join(",").toStdString(); - } if (settingsKeys.contains("channel") || force) { ostr << " m_channel: " << m_channel.toStdString(); } diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 09ded3913..2f6de1557 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -213,6 +213,7 @@ set(sdrbase_SOURCES settings/mainsettings.cpp settings/rollupstate.cpp + util/airlines.cpp util/ais.cpp util/android.cpp util/aprsfi.cpp @@ -447,6 +448,7 @@ set(sdrbase_HEADERS settings/mainsettings.h settings/rollupstate.h + util/airlines.h util/ais.h util/android.h util/aprsfi.h diff --git a/sdrbase/channel/channelwebapiutils.cpp b/sdrbase/channel/channelwebapiutils.cpp index 25527223d..8bdbfb2c4 100644 --- a/sdrbase/channel/channelwebapiutils.cpp +++ b/sdrbase/channel/channelwebapiutils.cpp @@ -1292,6 +1292,79 @@ bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsig } } +bool ChannelWebAPIUtils::patchChannelSetting(unsigned int deviceSetIndex, unsigned int channelIndex, const QString &setting, const QJsonArray& value) +{ + SWGSDRangel::SWGChannelSettings channelSettingsResponse; + QString errorResponse; + int httpRC; + ChannelAPI *channel; + + if (getChannelSettings(deviceSetIndex, channelIndex, channelSettingsResponse, channel)) + { + // Patch setting + QJsonObject *jsonObj = channelSettingsResponse.asJsonObject(); + + // Set value + bool found = false; + for (QJsonObject::iterator it = jsonObj->begin(); it != jsonObj->end(); it++) + { + QJsonValue jsonValue = it.value(); + + if (jsonValue.isObject()) + { + QJsonObject subObject = jsonValue.toObject(); + + if (subObject.contains(setting)) + { + subObject[setting] = value; + it.value() = subObject; + found = true; + break; + } + } + } + if (!found) + { + for (QJsonObject::iterator it = jsonObj->begin(); it != jsonObj->end(); it++) + { + QJsonValueRef jsonValue = it.value(); + + if (jsonValue.isObject()) + { + QJsonObject subObject = jsonValue.toObject(); + + subObject.insert(setting, value); + jsonValue = subObject; + } + } + } + + QStringList channelSettingsKeys; + channelSettingsKeys.append(setting); + channelSettingsResponse.init(); + channelSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + + httpRC = channel->webapiSettingsPutPatch(false, channelSettingsKeys, channelSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::patchChannelSetting: set channel setting %s OK", qPrintable(setting)); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::patchChannelSetting: set channel setting error %d: %s", + httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } + } + else + { + return false; + } +} + bool ChannelWebAPIUtils::getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, int &value) { SWGSDRangel::SWGFeatureSettings featureSettingsResponse; diff --git a/sdrbase/channel/channelwebapiutils.h b/sdrbase/channel/channelwebapiutils.h index 2de9b5eac..65c659df7 100644 --- a/sdrbase/channel/channelwebapiutils.h +++ b/sdrbase/channel/channelwebapiutils.h @@ -70,6 +70,7 @@ public: static bool patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value); static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value); static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value); + static bool patchChannelSetting(unsigned int deviceSetIndex, unsigned int channeIndex, const QString &setting, const QJsonArray& value); static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, int &value); static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double &value); static bool getFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, QString &value); diff --git a/sdrbase/maincore.cpp b/sdrbase/maincore.cpp index d9ed30619..36e358de9 100644 --- a/sdrbase/maincore.cpp +++ b/sdrbase/maincore.cpp @@ -433,3 +433,20 @@ void MainCore::updateWakeLock() } #endif +std::vector MainCore::getChannels(const QString& uri) +{ + std::vector channels; + + for (const auto deviceSet : m_deviceSets) + { + for (int chi = 0; chi < deviceSet->getNumberOfChannels(); chi++) + { + ChannelAPI* channel = deviceSet->getChannelAt(chi); + if (channel->getURI() == uri) { + channels.push_back(channel); + } + } + } + + return channels; +} diff --git a/sdrbase/maincore.h b/sdrbase/maincore.h index 4baf3fe92..417997824 100644 --- a/sdrbase/maincore.h +++ b/sdrbase/maincore.h @@ -853,6 +853,7 @@ public: PluginManager *getPluginManager() const { return m_pluginManager; } std::vector& getDeviceSets() { return m_deviceSets; } std::vector& getFeatureeSets() { return m_featureSets; } + std::vector getChannels(const QString& uri); //!< Get all channels from any device set with the given URI void setLoggingOptions(); DeviceAPI *getDevice(unsigned int deviceSetIndex); ChannelAPI *getChannel(unsigned int deviceSetIndex, int channelIndex); diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index e2797e284..3be7af1e8 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -696,6 +696,36 @@ margin-bottom: 20px;