From 2f280ec06ad61f31d6503f734f8ae66bbc25b908 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 21 Jan 2025 10:41:01 +0000 Subject: [PATCH 1/2] Radiosonde: Fix radio type sent to SondeHub --- plugins/feature/radiosonde/radiosondegui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/feature/radiosonde/radiosondegui.cpp b/plugins/feature/radiosonde/radiosondegui.cpp index ecf023eb5..9c2e4ac6b 100644 --- a/plugins/feature/radiosonde/radiosondegui.cpp +++ b/plugins/feature/radiosonde/radiosondegui.cpp @@ -965,7 +965,7 @@ QStringList RadiosondeGUI::getRadios() for (const auto& channel : channels) { - DeviceAPI *device = mainCore->getDevice(channel.m_index); + DeviceAPI *device = mainCore->getDevice(channel.m_superIndex); if (device) { QString name = device->getHardwareId(); From 52d59b8609ab880bad06546779adee4ea7b855a9 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Tue, 21 Jan 2025 13:33:11 +0000 Subject: [PATCH 2/2] Radiosonde: Add option to display predicted paths. --- plugins/feature/radiosonde/radiosondegui.cpp | 133 +++++++++++++++++- plugins/feature/radiosonde/radiosondegui.h | 9 ++ plugins/feature/radiosonde/radiosondegui.ui | 27 +++- .../feature/radiosonde/radiosondesettings.cpp | 9 ++ .../feature/radiosonde/radiosondesettings.h | 1 + plugins/feature/radiosonde/readme.md | 3 +- sdrbase/util/sondehub.cpp | 59 +++++++- sdrbase/util/sondehub.h | 11 ++ 8 files changed, 240 insertions(+), 12 deletions(-) diff --git a/plugins/feature/radiosonde/radiosondegui.cpp b/plugins/feature/radiosonde/radiosondegui.cpp index 9c2e4ac6b..ec381a9c7 100644 --- a/plugins/feature/radiosonde/radiosondegui.cpp +++ b/plugins/feature/radiosonde/radiosondegui.cpp @@ -157,6 +157,13 @@ RadiosondeGUI::RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, F connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); m_sondeHub = SondeHub::create(); + if (m_sondeHub) + { + connect(m_sondeHub, &SondeHub::prediction, this, &RadiosondeGUI::handlePrediction); + connect(&m_predicitionTimer, &QTimer::timeout, this, &RadiosondeGUI::requestPredictions); + m_predicitionTimer.setInterval(60 * 1000); + m_predicitionTimer.setSingleShot(false); + } // Initialise chart ui->chart->setRenderHint(QPainter::Antialiasing); @@ -257,12 +264,14 @@ void RadiosondeGUI::displaySettings() ui->y2->setCurrentIndex((int)m_settings.m_y2); ui->feed->setChecked(m_settings.m_feedEnabled); + ui->showPredictedPaths->setChecked(m_settings.m_showPredictedPaths); getRollupContents()->restoreState(m_rollupState); blockApplySettings(false); getRollupContents()->arrangeRollups(); updatePosition(); + applyShowPredictedPaths(); } void RadiosondeGUI::onMenuDialogCalled(const QPoint &p) @@ -673,6 +682,10 @@ void RadiosondeGUI::updateRadiosondes(RS41Frame *message, QDateTime dateTime) MainCore::instance()->getSettings().getAltitude() ); } + + if (!found) { + requestPredictions(); + } } void RadiosondeGUI::on_radiosondes_itemSelectionChanged() @@ -908,16 +921,38 @@ void RadiosondeGUI::on_deleteAll_clicked() { QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text(); // Remove from map - sendToMap(serial, "", - "", "", - "", 0.0f, - 0.0f, 0.0f, 0.0f, QDateTime(), - 0.0f); + clearFromMapFeature(serial, 0); // Remove from table ui->radiosondes->removeRow(row); // Remove from hash and free memory delete m_radiosondes.take(serial); } + deletePredictedPaths(); +} + +void RadiosondeGUI::deletePredictedPaths() +{ + for (const auto& prediction : m_predictions) { + clearFromMapFeature(prediction, 3); + } + m_predictions.clear(); +} + +void RadiosondeGUI::clearFromMapFeature(const QString& name, int type) +{ + QList mapPipes; + MainCore::instance()->getMessagePipes().getMessagePipes(m_radiosonde, "mapitems", mapPipes); + + for (const auto& pipe : mapPipes) + { + MessageQueue *messageQueue = qobject_cast(pipe->m_element); + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + swgMapItem->setName(new QString(name)); + swgMapItem->setImage(new QString("")); + swgMapItem->setType(type); + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_radiosonde, swgMapItem); + messageQueue->push(msg); + } } void RadiosondeGUI::makeUIConnections() @@ -928,6 +963,7 @@ void RadiosondeGUI::makeUIConnections() QObject::connect(ui->y2, qOverload(&QComboBox::currentIndexChanged), this, &RadiosondeGUI::on_y2_currentIndexChanged); QObject::connect(ui->deleteAll, &QPushButton::clicked, this, &RadiosondeGUI::on_deleteAll_clicked); QObject::connect(ui->feed, &ButtonSwitch::clicked, this, &RadiosondeGUI::on_feed_clicked); + QObject::connect(ui->showPredictedPaths, &ButtonSwitch::clicked, this, &RadiosondeGUI::on_showPredictedPaths_clicked); } void RadiosondeGUI::on_feed_clicked(bool checked) @@ -956,6 +992,28 @@ void RadiosondeGUI::feedSelect(const QPoint& p) } } +void RadiosondeGUI::on_showPredictedPaths_clicked(bool checked) +{ + m_settings.m_showPredictedPaths = checked; + m_settingsKeys.append("showPredictedPaths"); + applySettings(); + applyShowPredictedPaths(); +} + +void RadiosondeGUI::applyShowPredictedPaths() +{ + if (m_settings.m_showPredictedPaths) + { + requestPredictions(); + m_predicitionTimer.start(); + } + else + { + m_predicitionTimer.stop(); + deletePredictedPaths(); + } +} + // Get names of devices with radiosonde demods, for SondeHub Radio string QStringList RadiosondeGUI::getRadios() { @@ -1019,6 +1077,71 @@ void RadiosondeGUI::updatePosition() } } +void RadiosondeGUI::requestPredictions() +{ + if (m_sondeHub && m_settings.m_showPredictedPaths) + { + for (int row = 0; row < ui->radiosondes->rowCount(); row++) + { + QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text(); + m_sondeHub->getPrediction(serial); + } + } +} + +void RadiosondeGUI::handlePrediction(const QString& serial, const QList& positions) +{ + if (positions.size() < 2) { + return; + } + + // Send to Map feature + QList mapPipes; + MainCore::instance()->getMessagePipes().getMessagePipes(m_radiosonde, "mapitems", mapPipes); + + if (mapPipes.size() > 0) + { + QString name = QString("%1_prediction").arg(serial); + + for (const auto& pipe : mapPipes) + { + MessageQueue *messageQueue = qobject_cast(pipe->m_element); + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + + swgMapItem->setName(new QString(name)); + swgMapItem->setLatitude(positions[0].m_latitude); + swgMapItem->setLongitude(positions[0].m_longitude); + swgMapItem->setAltitude(positions[0].m_altitude); + QString image = QString("none"); + swgMapItem->setImage(new QString(image)); + swgMapItem->setImageRotation(0); + swgMapItem->setFixedPosition(true); + swgMapItem->setLabel(new QString(serial)); + swgMapItem->setAltitudeReference(0); + QList *coords = new QList(); + + for (const auto& position : positions) + { + SWGSDRangel::SWGMapCoordinate* c = new SWGSDRangel::SWGMapCoordinate(); + c->setLatitude(position.m_latitude); + c->setLongitude(position.m_longitude); + c->setAltitude(position.m_altitude); + coords->append(c); + } + + swgMapItem->setCoordinates(coords); + swgMapItem->setType(3); + + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_radiosonde, swgMapItem); + messageQueue->push(msg); + + if (!m_predictions.contains(name)) { + m_predictions.append(name); + } + } + } +} + void RadiosondeGUI::preferenceChanged(int elementType) { Preferences::ElementType pref = (Preferences::ElementType)elementType; diff --git a/plugins/feature/radiosonde/radiosondegui.h b/plugins/feature/radiosonde/radiosondegui.h index 9afb619cd..fd6c31d47 100644 --- a/plugins/feature/radiosonde/radiosondegui.h +++ b/plugins/feature/radiosonde/radiosondegui.h @@ -108,6 +108,9 @@ private: static const int m_minMobilePositionUpdateTime = 30; // In seconds static const int m_minFixedPositionUpdateTime = 5 * 60; + QTimer m_predicitionTimer; + QStringList m_predictions; + explicit RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); virtual ~RadiosondeGUI(); @@ -130,6 +133,9 @@ private: float getData(RadiosondeSettings::ChartData dataType, RadiosondeData *radiosonde, RS41Frame *message); void updatePosition(); QStringList getRadios(); + void applyShowPredictedPaths(); + void deletePredictedPaths(); + void clearFromMapFeature(const QString& name, int type); enum RadiosondeCol { RADIOSONDE_COL_SERIAL, @@ -168,6 +174,9 @@ private slots: void on_deleteAll_clicked(); void on_feed_clicked(bool checked); void feedSelect(const QPoint& p); + void on_showPredictedPaths_clicked(bool checked); + void requestPredictions(); + void handlePrediction(const QString& serial, const QList& positions); void preferenceChanged(int elementType); }; diff --git a/plugins/feature/radiosonde/radiosondegui.ui b/plugins/feature/radiosonde/radiosondegui.ui index eee177b19..106c74341 100644 --- a/plugins/feature/radiosonde/radiosondegui.ui +++ b/plugins/feature/radiosonde/radiosondegui.ui @@ -31,7 +31,7 @@ Radiosonde - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight @@ -76,20 +76,20 @@ - Qt::Vertical + Qt::Orientation::Vertical Radiosondes - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers - QAbstractItemView::SingleSelection + QAbstractItemView::SelectionMode::SingleSelection - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows @@ -416,6 +416,23 @@ + + + + Show predicted paths on map + + + ... + + + + :/logarithmic.png:/logarithmic.png + + + true + + + diff --git a/plugins/feature/radiosonde/radiosondesettings.cpp b/plugins/feature/radiosonde/radiosondesettings.cpp index c45c27b90..35140dcd2 100644 --- a/plugins/feature/radiosonde/radiosondesettings.cpp +++ b/plugins/feature/radiosonde/radiosondesettings.cpp @@ -60,6 +60,7 @@ void RadiosondeSettings::resetToDefaults() m_displayPosition = false; m_mobile = false; m_email = ""; + m_showPredictedPaths = false; for (int i = 0; i < RADIOSONDES_COLUMNS; i++) { @@ -95,6 +96,7 @@ QByteArray RadiosondeSettings::serialize() const s.writeBool(17, m_displayPosition); s.writeBool(18, m_mobile); s.writeString(19, m_email); + s.writeBool(20, m_showPredictedPaths); for (int i = 0; i < RADIOSONDES_COLUMNS; i++) { @@ -159,6 +161,7 @@ bool RadiosondeSettings::deserialize(const QByteArray& data) d.readBool(17, &m_displayPosition, false); d.readBool(18, &m_mobile, false); d.readString(19, &m_email, ""); + d.readBool(20, &m_showPredictedPaths, false); for (int i = 0; i < RADIOSONDES_COLUMNS; i++) { d.readS32(300 + i, &m_radiosondesColumnIndexes[i], i); @@ -224,6 +227,9 @@ void RadiosondeSettings::applySettings(const QStringList& settingsKeys, const Ra if (settingsKeys.contains("email")) { m_email = settings.m_email; } + if (settingsKeys.contains("showPredictedPaths")) { + m_showPredictedPaths = settings.m_showPredictedPaths; + } if (settingsKeys.contains("workspaceIndex")) { m_workspaceIndex = settings.m_workspaceIndex; } @@ -292,6 +298,9 @@ QString RadiosondeSettings::getDebugString(const QStringList& settingsKeys, bool if (settingsKeys.contains("email") || force) { ostr << " m_email: " << m_email.toStdString(); } + if (settingsKeys.contains("showPredictedPaths") || force) { + ostr << " m_showPredictedPaths: " << m_showPredictedPaths; + } if (settingsKeys.contains("workspaceIndex") || force) { ostr << " m_workspaceIndex: " << m_workspaceIndex; } diff --git a/plugins/feature/radiosonde/radiosondesettings.h b/plugins/feature/radiosonde/radiosondesettings.h index 1f7071027..2112f26fa 100644 --- a/plugins/feature/radiosonde/radiosondesettings.h +++ b/plugins/feature/radiosonde/radiosondesettings.h @@ -62,6 +62,7 @@ struct RadiosondeSettings bool m_displayPosition; bool m_mobile; QString m_email; + bool m_showPredictedPaths; int m_radiosondesColumnIndexes[RADIOSONDES_COLUMNS]; int m_radiosondesColumnSizes[RADIOSONDES_COLUMNS]; diff --git a/plugins/feature/radiosonde/readme.md b/plugins/feature/radiosonde/readme.md index 6fd8e850a..55cad6f4d 100644 --- a/plugins/feature/radiosonde/readme.md +++ b/plugins/feature/radiosonde/readme.md @@ -7,7 +7,7 @@ based on data received via [Radiosonde Demodulators](../../channelrx/demodradios The chart can plot two data series vs time for the radiosonde selected in the table. -The Radiosonde feature can draw balloons objects on the [Map](../../feature/map/readme.md) feature in 2D and 3D. +The Radiosonde feature can draw balloons objects and predicted paths on the [Map](../../feature/map/readme.md) feature in 2D and 3D. Received data can be forwarded to [SondeHub](https://sondehub.org/). Your location can be displayed on the SondeHub map, as either a stationary receiver or chase car. @@ -48,6 +48,7 @@ The Radiosonde feature can plot balloons (during ascent) and parachutes (during To use, simply open a Map feature and the Radiosonde plugin will display objects based upon the data it receives from that point. Selecting a radiosonde item on the map will display a text bubble containing information from the above table. To centre the map on an item in the table, double click in the Lat or Lon columns. +Predicted paths can be displayed by checking the Show Predicted Paths button. The path is predicted by SondeHub. ![Radiosonde on map](../../../doc/img/Radiosonde_plugin_map.png) diff --git a/sdrbase/util/sondehub.cpp b/sdrbase/util/sondehub.cpp index 9cb76158c..d15e665f7 100644 --- a/sdrbase/util/sondehub.cpp +++ b/sdrbase/util/sondehub.cpp @@ -183,6 +183,17 @@ void SondeHub::updatePosition( m_networkManager->put(request, data); } +void SondeHub::getPrediction(const QString& serial) +{ + QUrl url(QString("https://api.v2.sondehub.org/predictions?vehicles=%1").arg(serial)); + + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + request.setHeader(QNetworkRequest::UserAgentHeader, "sdrangel"); + + m_networkManager->get(request); +} + void SondeHub::handleReply(QNetworkReply* reply) { if (reply) @@ -221,7 +232,53 @@ void SondeHub::handleReply(QNetworkReply* reply) } } } - //qDebug() << "SondeHub::handleReply: obj" << QJsonDocument(obj); + //qDebug() << "SondeHub::handleReply: obj" << QJsonDocument(obj); + } + else if (document.isArray()) + { + QJsonArray array = document.array(); + + for (auto arrayRef : array) + { + if (arrayRef.isObject()) + { + QJsonObject obj = arrayRef.toObject(); + + if (obj.contains(QStringLiteral("vehicle")) && obj.contains(QStringLiteral("data"))) + { + QJsonArray data; + // Perhaps a bug that data is a string rather than an array? + if (obj.value(QStringLiteral("data")).isString()) + { + QJsonDocument dataDocument = QJsonDocument::fromJson(obj.value(QStringLiteral("data")).toString().toUtf8()); + data = dataDocument.array(); + } + else + { + data = obj.value(QStringLiteral("data")).toArray(); + } + + QList positions; + for (auto dataObjRef : data) + { + QJsonObject positionObj = dataObjRef.toObject(); + Position position; + + position.m_dateTime = QDateTime::fromSecsSinceEpoch(positionObj.value(QStringLiteral("time")).toInt()); + position.m_latitude = positionObj.value(QStringLiteral("lat")).toDouble(); + position.m_longitude = positionObj.value(QStringLiteral("lon")).toDouble(); + position.m_altitude = positionObj.value(QStringLiteral("alt")).toDouble(); + positions.append(position); + } + + emit prediction(obj.value("vehicle").toString(), positions); + } + } + else + { + qDebug() << "SondeHub::handleReply:" << bytes; + } + } } else { diff --git a/sdrbase/util/sondehub.h b/sdrbase/util/sondehub.h index 6ad663a6f..8409ac94b 100644 --- a/sdrbase/util/sondehub.h +++ b/sdrbase/util/sondehub.h @@ -36,6 +36,13 @@ protected: public: + struct Position { + float m_latitude; + float m_longitude; + float m_altitude; + QDateTime m_dateTime; + }; + static SondeHub* create(); ~SondeHub(); @@ -61,10 +68,14 @@ public: bool mobile ); + void getPrediction(const QString& serial); private slots: void handleReply(QNetworkReply* reply); +signals: + void prediction(const QString& serial, const QList& path); + private: QNetworkAccessManager *m_networkManager;