diff --git a/.appveyor.yml b/.appveyor.yml index efdd4ee94..279b1d44d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -81,6 +81,7 @@ for: qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \ qml-module-qtquick-controls qml-module-qtquick-layouts \ libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \ + libqt5charts5-dev \ libusb-1.0-0-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \ libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \ libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b13074bd..ec7e299ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -308,6 +308,7 @@ if (BUILD_GUI) find_package(Qt5 COMPONENTS Quick) find_package(Qt5 COMPONENTS QuickWidgets) find_package(Qt5 COMPONENTS Positioning) + find_package(Qt5 COMPONENTS Charts) endif() # other requirements diff --git a/debian/control b/debian/control index 692ee4e43..dfdfce52a 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Build-Depends: debhelper (>= 9), qml-module-qtquick-controls, qml-module-qtquick-layouts, libqt5serialport5-dev, + libqt5charts5-dev, qtdeclarative5-dev, qtpositioning5-dev, qtlocation5-dev, @@ -55,7 +56,7 @@ Description: SDR/Analyzer/Generator front-end for various hardware Builds on Linux, Windows and Mac O/S Reception modes supported: Analog: AM, ATV, NFM, WFM, SSB, broadcast FM - Digital: D-Star, Yaesu SF, DMR, dPMR, LoRa, ADS-B + Digital: D-Star, Yaesu SF, DMR, dPMR, LoRa, ADS-B, Packet (AX.25/APRS) Analyzer: Generic channel Transmission modes supported: Analog: AM, ATV, NFM, SSB, WFM diff --git a/doc/img/APRS_map.png b/doc/img/APRS_map.png new file mode 100644 index 000000000..fa9c5ae44 Binary files /dev/null and b/doc/img/APRS_map.png differ diff --git a/doc/img/APRS_plugin.png b/doc/img/APRS_plugin.png new file mode 100644 index 000000000..c3f5c18ad Binary files /dev/null and b/doc/img/APRS_plugin.png differ diff --git a/doc/img/GS232Controller_plugin.png b/doc/img/GS232Controller_plugin.png index a67064ab4..9dcef6887 100644 Binary files a/doc/img/GS232Controller_plugin.png and b/doc/img/GS232Controller_plugin.png differ diff --git a/doc/img/GS232Controller_plugin.xcf b/doc/img/GS232Controller_plugin.xcf index 01cb5a5a5..3d3b2b299 100644 Binary files a/doc/img/GS232Controller_plugin.xcf and b/doc/img/GS232Controller_plugin.xcf differ diff --git a/doc/img/Map_plugin.png b/doc/img/Map_plugin.png new file mode 100644 index 000000000..8fb44fc9a Binary files /dev/null and b/doc/img/Map_plugin.png differ diff --git a/doc/img/PacketDemod_plugin.png b/doc/img/PacketDemod_plugin.png new file mode 100644 index 000000000..f0e0ea17b Binary files /dev/null and b/doc/img/PacketDemod_plugin.png differ diff --git a/doc/img/StarTracker_map.png b/doc/img/StarTracker_map.png new file mode 100644 index 000000000..3ac644bb0 Binary files /dev/null and b/doc/img/StarTracker_map.png differ diff --git a/doc/img/StarTracker_plugin.png b/doc/img/StarTracker_plugin.png new file mode 100644 index 000000000..67324a930 Binary files /dev/null and b/doc/img/StarTracker_plugin.png differ diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 36623f77b..fc20e0df6 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(filesink) add_subdirectory(freqtracker) add_subdirectory(demodchirpchat) add_subdirectory(demodvorsc) +add_subdirectory(demodpacket) if(LIBDSDCC_FOUND AND LIBMBE_FOUND) add_subdirectory(demoddsd) diff --git a/plugins/channelrx/demodadsb/adsbdemod.cpp b/plugins/channelrx/demodadsb/adsbdemod.cpp index 3cd87e795..41a1a6cb7 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.cpp +++ b/plugins/channelrx/demodadsb/adsbdemod.cpp @@ -34,12 +34,14 @@ #include "SWGADSBDemodSettings.h" #include "SWGChannelReport.h" #include "SWGADSBDemodReport.h" +#include "SWGTargetAzimuthElevation.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" #include "util/db.h" +#include "maincore.h" #include "adsbdemod.h" #include "adsbdemodworker.h" @@ -471,3 +473,27 @@ void ADSBDemod::networkManagerFinished(QNetworkReply *reply) reply->deleteLater(); } + +void ADSBDemod::setTarget(const QString& name, float targetAzimuth, float targetElevation) +{ + m_targetAzimuth = targetAzimuth; + m_targetElevation = targetElevation; + m_targetAzElValid = true; + + // Send to Rotator Controllers + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(this, "target"); + if (mapMessageQueues) + { + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation(); + swgTarget->setName(new QString(name)); + swgTarget->setAzimuth(targetAzimuth); + swgTarget->setElevation(targetElevation); + (*it)->push(MainCore::MsgTargetAzimuthElevation::create(this, swgTarget)); + } + } +} diff --git a/plugins/channelrx/demodadsb/adsbdemod.h b/plugins/channelrx/demodadsb/adsbdemod.h index 326831f62..104259541 100644 --- a/plugins/channelrx/demodadsb/adsbdemod.h +++ b/plugins/channelrx/demodadsb/adsbdemod.h @@ -119,12 +119,7 @@ public: m_basebandSink->setMessageQueueToGUI(queue); } - void setTarget(float targetAzimuth, float targetElevation) - { - m_targetAzimuth = targetAzimuth; - m_targetElevation = targetElevation; - m_targetAzElValid = true; - } + void setTarget(const QString& name, float targetAzimuth, float targetElevation); void clearTarget() { m_targetAzElValid = false; } uint32_t getNumberOfDeviceStreams() const; diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.cpp b/plugins/channelrx/demodadsb/adsbdemodgui.cpp index 8571c9f84..abd6963c2 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodgui.cpp @@ -29,6 +29,8 @@ #include #include +#include "SWGMapItem.h" + #include "ui_adsbdemodgui.h" #include "channel/channelwebapiutils.h" #include "plugin/pluginapi.h" @@ -148,6 +150,104 @@ static Real modulus(double x, double y) return x - y * std::floor(x/y); } +QString Aircraft::getImage() +{ + if (m_emitterCategory.length() > 0) + { + if (!m_emitterCategory.compare("Heavy")) + return QString("aircraft_4engine.png"); + else if (!m_emitterCategory.compare("Large")) + return QString("aircraft_2engine.png"); + else if (!m_emitterCategory.compare("Small")) + return QString("aircraft_2enginesmall.png"); + else if (!m_emitterCategory.compare("Rotorcraft")) + return QString("aircraft_helicopter.png"); + else if (!m_emitterCategory.compare("High performance")) + return QString("aircraft_fighter.png"); + else if (!m_emitterCategory.compare("Light") + || !m_emitterCategory.compare("Ultralight") + || !m_emitterCategory.compare("Glider/sailplane")) + return QString("aircraft_light.png"); + else if (!m_emitterCategory.compare("Space vehicle")) + return QString("aircraft_space.png"); + else if (!m_emitterCategory.compare("UAV")) + return QString("aircraft_drone.png"); + else if (!m_emitterCategory.compare("Emergency vehicle") + || !m_emitterCategory.compare("Service vehicle")) + return QString("truck.png"); + else + return QString("aircraft_2engine.png"); + } + else + return QString("aircraft_2engine.png"); +} + +QString Aircraft::getText(bool all) +{ + QStringList list; + if (m_flight.length() > 0) + { + list.append(QString("Flight: %1").arg(m_flight)); + } + else + { + list.append(QString("ICAO: %1").arg(m_icao, 1, 16)); + } + if (m_showAll || m_isHighlighted || all) + { + if (m_aircraftInfo != nullptr) + { + if (m_aircraftInfo->m_model.size() > 0) + { + list.append(QString("Aircraft: %1").arg(m_aircraftInfo->m_model)); + } + } + if (m_altitudeValid) + { + if (m_gui->useSIUints()) + list.append(QString("Altitude: %1 (m)").arg(Units::feetToIntegerMetres(m_altitude))); + else + list.append(QString("Altitude: %1 (ft)").arg(m_altitude)); + } + if (m_speedValid) + { + if (m_gui->useSIUints()) + list.append(QString("%1: %2 (kph)").arg(m_speedTypeNames[m_speedType]).arg(Units::knotsToIntegerKPH(m_speed))); + else + list.append(QString("%1: %2 (kn)").arg(m_speedTypeNames[m_speedType]).arg(m_speed)); + } + if (m_verticalRateValid) + { + QString desc; + Real rate; + QString units; + + if (m_gui->useSIUints()) + { + rate = Units::feetPerMinToIntegerMetresPerSecond(m_verticalRate); + units = QString("m/s"); + } + else + { + rate = m_verticalRate; + units = QString("ft/min"); + } + if (m_verticalRate == 0) + desc = "Level flight"; + else if (rate > 0) + desc = QString("Climbing: %1 (%2)").arg(rate).arg(units); + else + desc = QString("Descending: %1 (%2)").arg(rate).arg(units); + list.append(QString(desc)); + } + if ((m_status.length() > 0) && m_status.compare("No emergency")) + { + list.append(m_status); + } + } + return list.join("\n"); +} + QVariant AircraftModel::data(const QModelIndex &index, int role) const { int row = index.row(); @@ -170,101 +270,12 @@ 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 - QStringList list; - if (m_aircrafts[row]->m_flight.length() > 0) - { - list.append(QString("Flight: %1").arg(m_aircrafts[row]->m_flight)); - } - else - { - list.append(QString("ICAO: %1").arg(m_aircrafts[row]->m_icao, 1, 16)); - } - if (m_aircrafts[row]->m_showAll || m_aircrafts[row]->m_isHighlighted) - { - if (m_aircrafts[row]->m_aircraftInfo != nullptr) - { - if (m_aircrafts[row]->m_aircraftInfo->m_model.size() > 0) - { - list.append(QString("Aircraft: %1").arg(m_aircrafts[row]->m_aircraftInfo->m_model)); - } - } - if (m_aircrafts[row]->m_altitudeValid) - { - if (m_aircrafts[row]->m_gui->useSIUints()) - list.append(QString("Altitude: %1 (m)").arg(Units::feetToIntegerMetres(m_aircrafts[row]->m_altitude))); - else - list.append(QString("Altitude: %1 (ft)").arg(m_aircrafts[row]->m_altitude)); - } - if (m_aircrafts[row]->m_speedValid) - { - if (m_aircrafts[row]->m_gui->useSIUints()) - list.append(QString("%1: %2 (kph)").arg(m_aircrafts[row]->m_speedTypeNames[m_aircrafts[row]->m_speedType]).arg(Units::knotsToIntegerKPH(m_aircrafts[row]->m_speed))); - else - list.append(QString("%1: %2 (kn)").arg(m_aircrafts[row]->m_speedTypeNames[m_aircrafts[row]->m_speedType]).arg(m_aircrafts[row]->m_speed)); - } - if (m_aircrafts[row]->m_verticalRateValid) - { - QString desc; - Real rate; - QString units; - - if (m_aircrafts[row]->m_gui->useSIUints()) - { - rate = Units::feetPerMinToIntegerMetresPerSecond(m_aircrafts[row]->m_verticalRate); - units = QString("m/s"); - } - else - { - rate = m_aircrafts[row]->m_verticalRate; - units = QString("ft/min"); - } - if (m_aircrafts[row]->m_verticalRate == 0) - desc = "Level flight"; - else if (rate > 0) - desc = QString("Climbing: %1 (%2)").arg(rate).arg(units); - else - desc = QString("Descending: %1 (%2)").arg(rate).arg(units); - list.append(QString(desc)); - } - if ((m_aircrafts[row]->m_status.length() > 0) && m_aircrafts[row]->m_status.compare("No emergency")) - { - list.append(m_aircrafts[row]->m_status); - } - } - QString data = list.join("\n"); - return QVariant::fromValue(data); + return QVariant::fromValue(m_aircrafts[row]->getText()); } else if (role == AircraftModel::aircraftImageRole) { // Select an image to use for the aircraft - if (m_aircrafts[row]->m_emitterCategory.length() > 0) - { - if (!m_aircrafts[row]->m_emitterCategory.compare("Heavy")) - return QVariant::fromValue(QString("aircraft_4engine.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Large")) - return QVariant::fromValue(QString("aircraft_2engine.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Small")) - return QVariant::fromValue(QString("aircraft_2enginesmall.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Rotorcraft")) - return QVariant::fromValue(QString("aircraft_helicopter.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("High performance")) - return QVariant::fromValue(QString("aircraft_fighter.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Light") - || !m_aircrafts[row]->m_emitterCategory.compare("Ultralight") - || !m_aircrafts[row]->m_emitterCategory.compare("Glider/sailplane")) - return QVariant::fromValue(QString("aircraft_light.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Space vehicle")) - return QVariant::fromValue(QString("aircraft_space.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("UAV")) - return QVariant::fromValue(QString("aircraft_drone.png")); - else if (!m_aircrafts[row]->m_emitterCategory.compare("Emergency vehicle") - || !m_aircrafts[row]->m_emitterCategory.compare("Service vehicle")) - return QVariant::fromValue(QString("truck.png")); - else - return QVariant::fromValue(QString("aircraft_2engine.png")); - } - else - return QVariant::fromValue(QString("aircraft_2engine.png")); + return QVariant::fromValue(m_aircrafts[row]->getImage()); } else if (role == AircraftModel::bubbleColourRole) { @@ -280,7 +291,7 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const } else if (role == AircraftModel::aircraftPathRole) { - if (m_flightPaths) + if ((m_flightPaths && m_aircrafts[row]->m_isHighlighted) || m_allFlightPaths) return m_aircrafts[row]->m_coordinates; else return QVariantList(); @@ -407,7 +418,7 @@ bool AirportModel::setData(const QModelIndex &index, const QVariant& value, int else if (idx == m_airports[row]->m_frequencies.size()) { // Set airport as target - m_gui->target(m_azimuth[row], m_elevation[row]); + m_gui->target(m_airports[row]->m_name, m_azimuth[row], m_elevation[row]); emit dataChanged(index, index); } return true; @@ -442,7 +453,29 @@ void ADSBDemodGUI::updatePosition(Aircraft *aircraft) aircraft->m_rangeItem->setText(QString::number(aircraft->m_range/1000.0, 'f', 1)); aircraft->m_azElItem->setText(QString("%1/%2").arg(std::round(aircraft->m_azimuth)).arg(std::round(aircraft->m_elevation))); if (aircraft == m_trackAircraft) - m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation); + m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation); + + // Send to Map feature + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_adsbDemod, "mapitems"); + if (mapMessageQueues) + { + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16))); + swgMapItem->setLatitude(aircraft->m_latitude); + swgMapItem->setLongitude(aircraft->m_longitude); + swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage()))); + swgMapItem->setImageRotation(aircraft->m_heading); + swgMapItem->setText(new QString(aircraft->getText(true))); + + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem); + (*it)->push(msg); + } + } } // Called when we have lat & long from local decode and we need to check if it is in a valid range (<180nm/333km) @@ -1359,6 +1392,12 @@ void ADSBDemodGUI::on_flightPaths_clicked(bool checked) m_aircraftModel.setFlightPaths(checked); } +void ADSBDemodGUI::on_allFlightPaths_clicked(bool checked) +{ + m_settings.m_allFlightPaths = checked; + m_aircraftModel.setAllFlightPaths(checked); +} + QString ADSBDemodGUI::getDataDir() { // Get directory to store app data in (aircraft & airport databases and user-definable icons) @@ -1633,7 +1672,7 @@ void ADSBDemodGUI::updateAirports() } // Set a static target, such as an airport -void ADSBDemodGUI::target(float az, float el) +void ADSBDemodGUI::target(const QString& name, float az, float el) { if (m_trackAircraft) { @@ -1642,7 +1681,7 @@ void ADSBDemodGUI::target(float az, float el) m_aircraftModel.aircraftUpdated(m_trackAircraft); m_trackAircraft = nullptr; } - m_adsbDemod->setTarget(az, el); + m_adsbDemod->setTarget(name, az, el); } void ADSBDemodGUI::targetAircraft(Aircraft *aircraft) @@ -1658,7 +1697,7 @@ void ADSBDemodGUI::targetAircraft(Aircraft *aircraft) // Track this aircraft m_trackAircraft = aircraft; if (aircraft->m_positionValid) - m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation); + m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation); // Change colour of new target aircraft->m_isTarget = true; m_aircraftModel.aircraftUpdated(aircraft); @@ -1922,6 +1961,8 @@ void ADSBDemodGUI::displaySettings() ui->flightPaths->setChecked(m_settings.m_flightPaths); m_aircraftModel.setFlightPaths(m_settings.m_flightPaths); + ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths); + m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths); displayStreamIndex(); @@ -2035,6 +2076,21 @@ void ADSBDemodGUI::tick() ui->adsbData->removeRow(aircraft->m_icaoItem->row()); // Remove aircraft from hash i = m_aircraft.erase(i); + // Remove from map feature + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_adsbDemod, "mapitems"); + if (mapMessageQueues) + { + QList::iterator it = mapMessageQueues->begin(); + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16))); + swgMapItem->setImage(new QString("")); + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem); + (*it)->push(msg); + } + } // And finally free its memory delete aircraft; } diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.h b/plugins/channelrx/demodadsb/adsbdemodgui.h index 512dd8ed7..352fc926d 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.h +++ b/plugins/channelrx/demodadsb/adsbdemodgui.h @@ -34,6 +34,7 @@ #include "util/azel.h" #include "util/movingaverage.h" #include "util/httpdownloadmanager.h" +#include "maincore.h" #include "adsbdemodsettings.h" #include "ourairportsdb.h" @@ -207,6 +208,19 @@ struct Aircraft { m_correlationItem = new QTableWidgetItem(); m_rssiItem = new QTableWidgetItem(); } + + QString getImage(); + QString getText(bool all=false); + + // Name to use when selected as a target + QString targetName() + { + if (!m_flight.isEmpty()) + return QString("Flight: %1").arg(m_flight); + else + return QString("ICAO: %1").arg(m_icao, 0, 16); + } + }; // Aircraft data model used by QML map item @@ -300,9 +314,16 @@ public: allAircraftUpdated(); } + void setAllFlightPaths(bool allFlightPaths) + { + m_allFlightPaths = allFlightPaths; + allAircraftUpdated(); + } + private: QList m_aircrafts; bool m_flightPaths; + bool m_allFlightPaths; }; // Airport data model used by QML map item @@ -445,7 +466,7 @@ public: virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } void highlightAircraft(Aircraft *aircraft); void targetAircraft(Aircraft *aircraft); - void target(float az, float el); + void target(const QString& name, float az, float el); bool setFrequency(float frequency); bool useSIUints() { return m_settings.m_siUnits; } @@ -547,6 +568,7 @@ private slots: void on_getOSNDB_clicked(); void on_getAirportDB_clicked(); void on_flightPaths_clicked(bool checked); + void on_allFlightPaths_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void handleInputMessages(); diff --git a/plugins/channelrx/demodadsb/adsbdemodgui.ui b/plugins/channelrx/demodadsb/adsbdemodgui.ui index 95886ee4e..5a0a8d312 100644 --- a/plugins/channelrx/demodadsb/adsbdemodgui.ui +++ b/plugins/channelrx/demodadsb/adsbdemodgui.ui @@ -546,7 +546,7 @@ - Display flight paths + Display flight path for selected aircraft ^ @@ -563,6 +563,26 @@ + + + + Display flight paths for all aircraft + + + ^ + + + + :/icons/allflightpaths.png:/icons/allflightpaths.png + + + true + + + true + + + @@ -945,17 +965,17 @@ QWidget
QtQuickWidgets/QQuickWidget
- - ButtonSwitch - QToolButton -
gui/buttonswitch.h
-
RollupWidget QWidget
gui/rollupwidget.h
1
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
LevelMeterSignalDB QWidget diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp index c8edf7997..2f608063d 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.cpp +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.cpp @@ -53,10 +53,11 @@ void ADSBDemodSettings::resetToDefaults() m_airportMinimumSize = AirportType::Medium; m_displayHeliports = false; m_flightPaths = true; + m_allFlightPaths = false; m_siUnits = false; m_tableFontName = "Liberation Sans"; m_tableFontSize = 9; - m_displayDemodStats = true; + m_displayDemodStats = false; m_correlateFullPreamble = true; m_demodModeS = false; m_deviceIndex = -1; @@ -109,6 +110,7 @@ QByteArray ADSBDemodSettings::serialize() const s.writeBool(30, m_autoResizeTableColumns); s.writeS32(31, m_interpolatorPhaseSteps); s.writeFloat(32, m_interpolatorTapsPerPhase); + s.writeBool(33, m_allFlightPaths); for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) s.writeS32(100 + i, m_columnIndexes[i]); @@ -188,6 +190,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data) d.readBool(30, &m_autoResizeTableColumns, false); d.readS32(31, &m_interpolatorPhaseSteps, 4); d.readFloat(32, &m_interpolatorTapsPerPhase, 3.5f); + d.readBool(33, &m_allFlightPaths, false); for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) d.readS32(100 + i, &m_columnIndexes[i], i); diff --git a/plugins/channelrx/demodadsb/adsbdemodsettings.h b/plugins/channelrx/demodadsb/adsbdemodsettings.h index 081e5934d..e3191632b 100644 --- a/plugins/channelrx/demodadsb/adsbdemodsettings.h +++ b/plugins/channelrx/demodadsb/adsbdemodsettings.h @@ -66,6 +66,7 @@ struct ADSBDemodSettings } m_airportMinimumSize; //!< What's the minimum size airport that should be displayed bool m_displayHeliports; //!< Whether to display heliports on the map bool m_flightPaths; //!< Whether to display flight paths + bool m_allFlightPaths; //!< Whether to display flight paths for all aircraft bool m_siUnits; //!< Uses m,kph rather than ft/knts QString m_tableFontName; //!< Font to use for table int m_tableFontSize; diff --git a/plugins/channelrx/demodadsb/icons.qrc b/plugins/channelrx/demodadsb/icons.qrc index 4bafcd1f5..6923f2feb 100644 --- a/plugins/channelrx/demodadsb/icons.qrc +++ b/plugins/channelrx/demodadsb/icons.qrc @@ -2,5 +2,6 @@ icons/aircraft.png icons/controltower.png + icons/allflightpaths.png diff --git a/plugins/channelrx/demodadsb/icons/allflightpaths.png b/plugins/channelrx/demodadsb/icons/allflightpaths.png new file mode 100644 index 000000000..e75c45a8c Binary files /dev/null and b/plugins/channelrx/demodadsb/icons/allflightpaths.png differ diff --git a/plugins/channelrx/demodpacket/CMakeLists.txt b/plugins/channelrx/demodpacket/CMakeLists.txt new file mode 100644 index 000000000..2c50abb49 --- /dev/null +++ b/plugins/channelrx/demodpacket/CMakeLists.txt @@ -0,0 +1,58 @@ +project(demodpacket) + +set(demodpacket_SOURCES + packetdemod.cpp + packetdemodsettings.cpp + packetdemodbaseband.cpp + packetdemodsink.cpp + packetdemodplugin.cpp + packetdemodwebapiadapter.cpp +) + +set(demodpacket_HEADERS + packetdemod.h + packetdemodsettings.h + packetdemodbaseband.h + packetdemodsink.h + packetdemodplugin.h + packetdemodwebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(demodpacket_SOURCES + ${demodpacket_SOURCES} + packetdemodgui.cpp + packetdemodgui.ui + ) + set(demodpacket_HEADERS + ${demodpacket_HEADERS} + packetdemodgui.h + ) + + set(TARGET_NAME demodpacket) + set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME demodpacketsrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${demodpacket_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/channelrx/demodpacket/packetdemod.cpp b/plugins/channelrx/demodpacket/packetdemod.cpp new file mode 100644 index 000000000..068d9fb88 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemod.cpp @@ -0,0 +1,434 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "packetdemod.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGPacketDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGMapItem.h" + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/deviceapi.h" +#include "feature/feature.h" +#include "util/db.h" +#include "maincore.h" + +MESSAGE_CLASS_DEFINITION(PacketDemod::MsgConfigurePacketDemod, Message) + +const char * const PacketDemod::m_channelIdURI = "sdrangel.channelrx.packetdemod"; +const char * const PacketDemod::m_channelId = "PacketDemod"; + +PacketDemod::PacketDemod(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0) +{ + setObjectName(m_channelId); + + m_basebandSink = new PacketDemodBaseband(this); + m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); + m_basebandSink->moveToThread(&m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); + m_deviceAPI->addChannelSinkAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +PacketDemod::~PacketDemod() +{ + qDebug("PacketDemod::~PacketDemod"); + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + + if (m_basebandSink->isRunning()) { + stop(); + } + + delete m_basebandSink; +} + +uint32_t PacketDemod::getNumberOfDeviceStreams() const +{ + return m_deviceAPI->getNbSourceStreams(); +} + +void PacketDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +{ + (void) firstOfBurst; + m_basebandSink->feed(begin, end); +} + +void PacketDemod::start() +{ + qDebug("PacketDemod::start"); + + m_basebandSink->reset(); + m_basebandSink->startWork(); + m_thread.start(); + + DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency); + m_basebandSink->getInputMessageQueue()->push(dspMsg); + + PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::create(m_settings, true); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +void PacketDemod::stop() +{ + qDebug("PacketDemod::stop"); + m_basebandSink->stopWork(); + m_thread.quit(); + m_thread.wait(); +} + +bool PacketDemod::handleMessage(const Message& cmd) +{ + if (MsgConfigurePacketDemod::match(cmd)) + { + MsgConfigurePacketDemod& cfg = (MsgConfigurePacketDemod&) cmd; + qDebug() << "PacketDemod::handleMessage: MsgConfigurePacketDemod"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "PacketDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + // Forward to GUI if any + if (m_guiMessageQueue) + { + rep = new DSPSignalNotification(notif); + m_guiMessageQueue->push(rep); + } + + return true; + } + else if (MainCore::MsgPacket::match(cmd)) + { + // Forward to GUI + MainCore::MsgPacket& report = (MainCore::MsgPacket&)cmd; + if (getMessageQueueToGUI()) + { + MainCore::MsgPacket *msg = new MainCore::MsgPacket(report); + getMessageQueueToGUI()->push(msg); + } + + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + + // Forward to APRS and other packet features + QList *packetMessageQueues = messagePipes.getMessageQueues(this, "packets"); + if (packetMessageQueues) + { + QList::iterator it = packetMessageQueues->begin(); + for (; it != packetMessageQueues->end(); ++it) + { + MainCore::MsgPacket *msg = new MainCore::MsgPacket(report); + (*it)->push(msg); + } + } + + return true; + } + else + { + return false; + } +} + +void PacketDemod::applySettings(const PacketDemodSettings& settings, bool force) +{ + qDebug() << "PacketDemod::applySettings:" + << " m_streamIndex: " << settings.m_streamIndex + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex + << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex + << " force: " << force; + + QList reverseAPIKeys; + + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); + } + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { + reverseAPIKeys.append("fmDeviation"); + } + + if (m_settings.m_streamIndex != settings.m_streamIndex) + { + if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only + { + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); + m_deviceAPI->addChannelSinkAPI(this); + } + + reverseAPIKeys.append("streamIndex"); + } + + PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +QByteArray PacketDemod::serialize() const +{ + return m_settings.serialize(); +} + +bool PacketDemod::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int PacketDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings()); + response.getPacketDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int PacketDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + PacketDemodSettings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("PacketDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePacketDemod *msgToGUI = MsgConfigurePacketDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void PacketDemod::webapiUpdateChannelSettings( + PacketDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("inputFrequencyOffset")) { + settings.m_inputFrequencyOffset = response.getPacketDemodSettings()->getInputFrequencyOffset(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getPacketDemodSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getPacketDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getPacketDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getPacketDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getPacketDemodSettings()->getStreamIndex(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getPacketDemodSettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getPacketDemodSettings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getPacketDemodSettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getPacketDemodSettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getPacketDemodSettings()->getReverseApiChannelIndex(); + } +} + +void PacketDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const PacketDemodSettings& settings) +{ + response.getPacketDemodSettings()->setFmDeviation(settings.m_fmDeviation); + response.getPacketDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getPacketDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getPacketDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getPacketDemodSettings()->getTitle()) { + *response.getPacketDemodSettings()->getTitle() = settings.m_title; + } else { + response.getPacketDemodSettings()->setTitle(new QString(settings.m_title)); + } + + response.getPacketDemodSettings()->setStreamIndex(settings.m_streamIndex); + response.getPacketDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getPacketDemodSettings()->getReverseApiAddress()) { + *response.getPacketDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getPacketDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getPacketDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getPacketDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getPacketDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void PacketDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const PacketDemodSettings& settings, bool force) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force); + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex) + .arg(settings.m_reverseAPIChannelIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgChannelSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgChannelSettings; +} + +void PacketDemod::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const PacketDemodSettings& settings, + bool force +) +{ + swgChannelSettings->setDirection(0); // Single sink (Rx) + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("PacketDemod")); + swgChannelSettings->setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings()); + SWGSDRangel::SWGPacketDemodSettings *swgPacketDemodSettings = swgChannelSettings->getPacketDemodSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("fmDeviation") || force) { + swgPacketDemodSettings->setFmDeviation(settings.m_fmDeviation); + } + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgPacketDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgPacketDemodSettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgPacketDemodSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgPacketDemodSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("streamIndex") || force) { + swgPacketDemodSettings->setStreamIndex(settings.m_streamIndex); + } +} + +void PacketDemod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "PacketDemod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("PacketDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/channelrx/demodpacket/packetdemod.h b/plugins/channelrx/demodpacket/packetdemod.h new file mode 100644 index 000000000..3bff4f0ef --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemod.h @@ -0,0 +1,152 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMOD_H +#define INCLUDE_PACKETDEMOD_H + +#include + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelapi.h" +#include "util/message.h" + +#include "packetdemodbaseband.h" +#include "packetdemodsettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class DeviceAPI; + +class PacketDemod : public BasebandSampleSink, public ChannelAPI { + Q_OBJECT +public: + class MsgConfigurePacketDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PacketDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigurePacketDemod* create(const PacketDemodSettings& settings, bool force) + { + return new MsgConfigurePacketDemod(settings, force); + } + + private: + PacketDemodSettings m_settings; + bool m_force; + + MsgConfigurePacketDemod(const PacketDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + PacketDemod(DeviceAPI *deviceAPI); + virtual ~PacketDemod(); + virtual void destroy() { delete this; } + + using BasebandSampleSink::feed; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual const QString& getURI() const { return getName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int getNbSinkStreams() const { return 1; } + virtual int getNbSourceStreams() const { return 0; } + + virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const + { + (void) streamIndex; + (void) sinkElseSource; + return 0; + } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const PacketDemodSettings& settings); + + static void webapiUpdateChannelSettings( + PacketDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response); + + double getMagSq() const { return m_basebandSink->getMagSq(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_basebandSink->getMagSqLevels(avg, peak, nbSamples); + } +/* void setMessageQueueToGUI(MessageQueue* queue) override { + BasebandSampleSink::setMessageQueueToGUI(queue); + m_basebandSink->setMessageQueueToGUI(queue); + }*/ + + uint32_t getNumberOfDeviceStreams() const; + + static const char * const m_channelIdURI; + static const char * const m_channelId; + +private: + DeviceAPI *m_deviceAPI; + QThread m_thread; + PacketDemodBaseband* m_basebandSink; + PacketDemodSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + qint64 m_centerFrequency; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const PacketDemodSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& channelSettingsKeys, const PacketDemodSettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const PacketDemodSettings& settings, + bool force + ); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + +}; + +#endif // INCLUDE_PACKETDEMOD_H diff --git a/plugins/channelrx/demodpacket/packetdemodbaseband.cpp b/plugins/channelrx/demodpacket/packetdemodbaseband.cpp new file mode 100644 index 000000000..61b729f0d --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodbaseband.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" + +#include "packetdemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(PacketDemodBaseband::MsgConfigurePacketDemodBaseband, Message) + +PacketDemodBaseband::PacketDemodBaseband(PacketDemod *packetDemod) : + m_sink(packetDemod), + m_running(false), + m_mutex(QMutex::Recursive) +{ + qDebug("PacketDemodBaseband::PacketDemodBaseband"); + + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownChannelizer(&m_sink); +} + +PacketDemodBaseband::~PacketDemodBaseband() +{ + m_inputMessageQueue.clear(); + + delete m_channelizer; +} + +void PacketDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); + m_sampleFifo.reset(); +} + +void PacketDemodBaseband::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &PacketDemodBaseband::handleData, + Qt::QueuedConnection + ); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; +} + +void PacketDemodBaseband::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + QObject::disconnect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &PacketDemodBaseband::handleData + ); + m_running = false; +} + +void PacketDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void PacketDemodBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void PacketDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool PacketDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigurePacketDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigurePacketDemodBaseband& cfg = (MsgConfigurePacketDemodBaseband&) cmd; + qDebug() << "PacketDemodBaseband::handleMessage: MsgConfigurePacketDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "PacketDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + setBasebandSampleRate(notif.getSampleRate()); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + + return true; + } + else + { + return false; + } +} + +void PacketDemodBaseband::applySettings(const PacketDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +void PacketDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/demodpacket/packetdemodbaseband.h b/plugins/channelrx/demodpacket/packetdemodbaseband.h new file mode 100644 index 000000000..cee2cd39a --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodbaseband.h @@ -0,0 +1,94 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODBASEBAND_H +#define INCLUDE_PACKETDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "packetdemodsink.h" + +class DownChannelizer; +class PacketDemod; + +class PacketDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigurePacketDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PacketDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigurePacketDemodBaseband* create(const PacketDemodSettings& settings, bool force) + { + return new MsgConfigurePacketDemodBaseband(settings, force); + } + + private: + PacketDemodSettings m_settings; + bool m_force; + + MsgConfigurePacketDemodBaseband(const PacketDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + PacketDemodBaseband(PacketDemod *packetDemod); + ~PacketDemodBaseband(); + void reset(); + void startWork(); + void stopWork(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_sink.getMagSqLevels(avg, peak, nbSamples); + } + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); } + void setBasebandSampleRate(int sampleRate); + double getMagSq() const { return m_sink.getMagSq(); } + bool isRunning() const { return m_running; } + +private: + SampleSinkFifo m_sampleFifo; + DownChannelizer *m_channelizer; + PacketDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + PacketDemodSettings m_settings; + bool m_running; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void calculateOffset(PacketDemodSink *sink); + void applySettings(const PacketDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_PACKETDEMODBASEBAND_H diff --git a/plugins/channelrx/demodpacket/packetdemodgui.cpp b/plugins/channelrx/demodpacket/packetdemodgui.cpp new file mode 100644 index 000000000..464ad61df --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.cpp @@ -0,0 +1,560 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "packetdemodgui.h" +#include "util/ax25.h" + +#include "device/deviceuiset.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "ui_packetdemodgui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "util/morse.h" +#include "util/units.h" +#include "gui/basicchannelsettingsdialog.h" +#include "gui/devicestreamselectiondialog.h" +#include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "channel/channelwebapiutils.h" +#include "maincore.h" + +#include "packetdemod.h" +#include "packetdemodsink.h" + +#define PACKET_COL_FROM 0 +#define PACKET_COL_TO 1 +#define PACKET_COL_VIA 2 +#define PACKET_COL_TYPE 3 +#define PACKET_COL_PID 4 +#define PACKET_COL_DATA_ASCII 5 +#define PACKET_COL_DATA_HEX 6 + +void PacketDemodGUI::resizeTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + // Trailing spaces are for sort arrow + int row = ui->packets->rowCount(); + ui->packets->setRowCount(row + 1); + ui->packets->setItem(row, PACKET_COL_FROM, new QTableWidgetItem("123456-15-")); + ui->packets->setItem(row, PACKET_COL_TO, new QTableWidgetItem("123456-15-")); + ui->packets->setItem(row, PACKET_COL_VIA, new QTableWidgetItem("123456-15-")); + ui->packets->setItem(row, PACKET_COL_TYPE, new QTableWidgetItem("Type-")); + ui->packets->setItem(row, PACKET_COL_PID, new QTableWidgetItem("PID-")); + ui->packets->setItem(row, PACKET_COL_DATA_ASCII, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ")); + ui->packets->setItem(row, PACKET_COL_DATA_HEX, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ")); + ui->packets->resizeColumnsToContents(); + ui->packets->removeRow(row); +} + +// Columns in table reordered +void PacketDemodGUI::packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_columnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void PacketDemodGUI::packets_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_columnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void PacketDemodGUI::columnSelectMenu(QPoint pos) +{ + menu->popup(ui->packets->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void PacketDemodGUI::columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->packets->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu item +QAction *PacketDemodGUI::createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked())); + return action; +} + +PacketDemodGUI* PacketDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + PacketDemodGUI* gui = new PacketDemodGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void PacketDemodGUI::destroy() +{ + delete this; +} + +void PacketDemodGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray PacketDemodGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool PacketDemodGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +// Add row to table +void PacketDemodGUI::packetReceived(QByteArray packet) +{ + AX25Packet ax25; + + if (ax25.decode(packet)) + { + ui->packets->setSortingEnabled(false); + int row = ui->packets->rowCount(); + ui->packets->setRowCount(row + 1); + + QTableWidgetItem *fromItem = new QTableWidgetItem(); + QTableWidgetItem *toItem = new QTableWidgetItem(); + QTableWidgetItem *viaItem = new QTableWidgetItem(); + QTableWidgetItem *typeItem = new QTableWidgetItem(); + QTableWidgetItem *pidItem = new QTableWidgetItem(); + QTableWidgetItem *dataASCIIItem = new QTableWidgetItem(); + QTableWidgetItem *dataHexItem = new QTableWidgetItem(); + ui->packets->setItem(row, PACKET_COL_FROM, fromItem); + ui->packets->setItem(row, PACKET_COL_TO, toItem); + ui->packets->setItem(row, PACKET_COL_VIA, viaItem); + ui->packets->setItem(row, PACKET_COL_TYPE, typeItem); + ui->packets->setItem(row, PACKET_COL_PID, pidItem); + ui->packets->setItem(row, PACKET_COL_DATA_ASCII, dataASCIIItem); + ui->packets->setItem(row, PACKET_COL_DATA_HEX, dataHexItem); + fromItem->setText(ax25.m_from); + toItem->setText(ax25.m_to); + viaItem->setText(ax25.m_via); + typeItem->setText(ax25.m_type); + pidItem->setText(ax25.m_pid); + dataASCIIItem->setText(ax25.m_dataASCII); + dataHexItem->setText(ax25.m_dataHex); + ui->packets->setSortingEnabled(true); + ui->packets->scrollToItem(fromItem); + filterRow(row); + } + else + qDebug() << "Unsupported AX.25 packet: " << packet; +} + +bool PacketDemodGUI::handleMessage(const Message& message) +{ + if (PacketDemod::MsgConfigurePacketDemod::match(message)) + { + qDebug("PacketDemodGUI::handleMessage: PacketDemod::MsgConfigurePacketDemod"); + const PacketDemod::MsgConfigurePacketDemod& cfg = (PacketDemod::MsgConfigurePacketDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_basebandSampleRate = notif.getSampleRate(); + return true; + } + else if (MainCore::MsgPacket::match(message)) + { + MainCore::MsgPacket& report = (MainCore::MsgPacket&) message; + packetReceived(report.getPacket()); + return true; + } + + return false; +} + +void PacketDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void PacketDemodGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void PacketDemodGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +void PacketDemodGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void PacketDemodGUI::on_mode_currentIndexChanged(int value) +{ + (void) value; + QString mode = ui->mode->currentText(); + // TODO: Support 9600 FSK +} + +void PacketDemodGUI::on_rfBW_valueChanged(int value) +{ + float bw = value * 100.0f; + ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + m_channelMarker.setBandwidth(bw); + m_settings.m_rfBandwidth = bw; + applySettings(); +} + +void PacketDemodGUI::on_fmDev_valueChanged(int value) +{ + ui->fmDevText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + m_settings.m_fmDeviation = value * 100.0; + applySettings(); +} + +void PacketDemodGUI::on_filterFrom_editingFinished() +{ + m_settings.m_filterFrom = ui->filterFrom->text(); + filter(); + applySettings(); +} + +void PacketDemodGUI::on_filterTo_editingFinished() +{ + m_settings.m_filterTo = ui->filterTo->text(); + filter(); + applySettings(); +} + +void PacketDemodGUI::on_filterPID_stateChanged(int state) +{ + m_settings.m_filterPID = state==Qt::Checked ? "f0" : ""; + filter(); + applySettings(); +} + +void PacketDemodGUI::on_clearTable_clicked() +{ + ui->packets->setRowCount(0); +} + +void PacketDemodGUI::filterRow(int row) +{ + bool hidden = false; + if (m_settings.m_filterFrom != "") + { + QRegExp re(m_settings.m_filterFrom); + QTableWidgetItem *fromItem = ui->packets->item(row, PACKET_COL_FROM); + if (!re.exactMatch(fromItem->text())) + hidden = true; + } + if (m_settings.m_filterTo != "") + { + QRegExp re(m_settings.m_filterTo); + QTableWidgetItem *toItem = ui->packets->item(row, PACKET_COL_TO); + if (!re.exactMatch(toItem->text())) + hidden = true; + } + if (m_settings.m_filterPID != "") + { + QTableWidgetItem *pidItem = ui->packets->item(row, PACKET_COL_PID); + if (pidItem->text() != m_settings.m_filterPID) + hidden = true; + } + ui->packets->setRowHidden(row, hidden); +} + +void PacketDemodGUI::filter() +{ + for (int i = 0; i < ui->packets->rowCount(); i++) + { + filterRow(i); + } +} + +void PacketDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void PacketDemodGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine)) + { + DeviceStreamSelectionDialog dialog(this); + dialog.setNumberOfStreams(m_packetDemod->getNumberOfDeviceStreams()); + dialog.setStreamIndex(m_settings.m_streamIndex); + dialog.move(p); + dialog.exec(); + + m_settings.m_streamIndex = dialog.getSelectedStreamIndex(); + m_channelMarker.clearStreamIndexes(); + m_channelMarker.addStreamIndex(m_settings.m_streamIndex); + displayStreamIndex(); + applySettings(); + } + + resetContextMenuType(); +} + +PacketDemodGUI::PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::PacketDemodGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_tickCount(0) +{ + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_packetDemod = reinterpret_cast(rxChannel); + m_packetDemod->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::yellow); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle("Packet Demodulator"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + setTitleColor(m_channelMarker.getColor()); + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + // Resize the table using dummy data + resizeTable(); + // Allow user to reorder columns + ui->packets->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->packets->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + menu = new QMenu(ui->packets); + for (int i = 0; i < ui->packets->horizontalHeader()->count(); i++) + { + QString text = ui->packets->horizontalHeaderItem(i)->text(); + menu->addAction(createCheckableItem(text, i, true)); + } + ui->packets->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->packets->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->packets->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(packets_sectionMoved(int, int, int))); + connect(ui->packets->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(packets_sectionResized(int, int, int))); + + displaySettings(); + applySettings(true); +} + +PacketDemodGUI::~PacketDemodGUI() +{ + delete ui; +} + +void PacketDemodGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void PacketDemodGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + PacketDemod::MsgConfigurePacketDemod* message = PacketDemod::MsgConfigurePacketDemod::create( m_settings, force); + m_packetDemod->getInputMessageQueue()->push(message); + } +} + +void PacketDemodGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + + ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1)); + ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0); + + ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1)); + ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0); + + displayStreamIndex(); + + ui->filterFrom->setText(m_settings.m_filterFrom); + ui->filterTo->setText(m_settings.m_filterTo); + ui->filterPID->setChecked(m_settings.m_filterPID == "f0"); + + // Order and size columns + QHeaderView *header = ui->packets->horizontalHeader(); + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + { + bool hidden = m_settings.m_columnSizes[i] == 0; + header->setSectionHidden(i, hidden); + menu->actions().at(i)->setChecked(!hidden); + if (m_settings.m_columnSizes[i] > 0) + ui->packets->setColumnWidth(i, m_settings.m_columnSizes[i]); + header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]); + } + + filter(); + + blockApplySettings(false); +} + +void PacketDemodGUI::displayStreamIndex() +{ + if (m_deviceUISet->m_deviceMIMOEngine) { + setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex)); + } else { + setStreamIndicator("S"); // single channel indicator + } +} + +void PacketDemodGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void PacketDemodGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void PacketDemodGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_packetDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + ui->channelPowerMeter->levelChanged( + (100.0f + powDbAvg) / 100.0f, + (100.0f + powDbPeak) / 100.0f, + nbMagsqSamples); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); + } + + m_tickCount++; +} diff --git a/plugins/channelrx/demodpacket/packetdemodgui.h b/plugins/channelrx/demodpacket/packetdemodgui.h new file mode 100644 index 000000000..898df575b --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.h @@ -0,0 +1,119 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODGUI_H +#define INCLUDE_PACKETDEMODGUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "dsp/movingaverage.h" +#include "util/messagequeue.h" +#include "packetdemodsettings.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +class PacketDemod; +class PacketDemodGUI; + +namespace Ui { + class PacketDemodGUI; +} +class PacketDemodGUI; + +class PacketDemodGUI : public ChannelGUI { + Q_OBJECT + +public: + static PacketDemodGUI* 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; } + +public slots: + void channelMarkerChangedByCursor(); + void channelMarkerHighlightedByCursor(); + +private: + Ui::PacketDemodGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + PacketDemodSettings m_settings; + bool m_doApplySettings; + + PacketDemod* m_packetDemod; + int m_basebandSampleRate; + uint32_t m_tickCount; + MessageQueue m_inputMessageQueue; + + QMenu *menu; // Column select context menu + + explicit PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~PacketDemodGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void displayStreamIndex(); + void packetReceived(QByteArray packet); + bool handleMessage(const Message& message); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void resizeTable(); + QAction *createCheckableItem(QString& text, int idx, bool checked); + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_mode_currentIndexChanged(int value); + void on_rfBW_valueChanged(int index); + void on_fmDev_valueChanged(int value); + void on_filterFrom_editingFinished(); + void on_filterTo_editingFinished(); + void on_filterPID_stateChanged(int state); + void on_clearTable_clicked(); + void filterRow(int row); + void filter(); + void packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void packets_sectionResized(int logicalIndex, int oldSize, int newSize); + void columnSelectMenu(QPoint pos); + void columnSelectMenuChecked(bool checked = false); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void tick(); +}; + +#endif // INCLUDE_PACKETDEMODGUI_H diff --git a/plugins/channelrx/demodpacket/packetdemodgui.ui b/plugins/channelrx/demodpacket/packetdemodgui.ui new file mode 100644 index 000000000..b4c67fada --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.ui @@ -0,0 +1,579 @@ + + + PacketDemodGUI + + + + 0 + 0 + 398 + 519 + + + + + 0 + 0 + + + + + 352 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + Packet Demodulator + + + Packet Demodulator + + + + + 0 + 0 + 390 + 101 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + + + + 100 + 0 + + + + Baud rate and modulation + + + + 1200 AFSK + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + BW + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + RF bandwidth + + + 10 + + + 400 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 10.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Dev + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Frequency deviation + + + 10 + + + 60 + + + 1 + + + 50 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 5.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + From + + + + + + + Display only packets where the source address (From) matches the specified regular expression + + + + + + + To: + + + + + + + Display only packets where the destination address (To) matches the specified regular expression + + + + + + + + + + Check to display only packets with PID set to No L3 (f0). This is typically used for APRS and BBS packets. + + + PID No L3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear packets from table + + + + + + + :/bin.png:/bin.png + + + + + + + + + + + 0 + 110 + 391 + 261 + + + + + 0 + 0 + + + + Received Packets + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Received packets + + + QAbstractItemView::NoEditTriggers + + + + From + + + Source callsign/address + + + + + To + + + Destination callsign/address + + + + + Via + + + Repeater addresses + + + + + Type + + + AX.25 frame type + + + + + PID + + + Layer 3 protocol ID + + + + + Data (ASCII) + + + Packet data as ASCII + + + + + Data (Hex) + + + Packet data as hex + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+
+ + packets + + + + + +
diff --git a/plugins/channelrx/demodpacket/packetdemodplugin.cpp b/plugins/channelrx/demodpacket/packetdemodplugin.cpp new file mode 100644 index 000000000..27bfe534e --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodplugin.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "packetdemodgui.h" +#endif +#include "packetdemod.h" +#include "packetdemodwebapiadapter.h" +#include "packetdemodplugin.h" + +const PluginDescriptor PacketDemodPlugin::m_pluginDescriptor = { + PacketDemod::m_channelId, + QStringLiteral("Packet Demodulator"), + QStringLiteral("6.4.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +PacketDemodPlugin::PacketDemodPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& PacketDemodPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void PacketDemodPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerRxChannel(PacketDemod::m_channelIdURI, PacketDemod::m_channelId, this); +} + +void PacketDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + PacketDemod *instance = new PacketDemod(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* PacketDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) const +{ + (void) deviceUISet; + (void) rxChannel; + return 0; +} +#else +ChannelGUI* PacketDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const +{ + return PacketDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +ChannelWebAPIAdapter* PacketDemodPlugin::createChannelWebAPIAdapter() const +{ + return new PacketDemodWebAPIAdapter(); +} diff --git a/plugins/channelrx/demodpacket/packetdemodplugin.h b/plugins/channelrx/demodpacket/packetdemodplugin.h new file mode 100644 index 000000000..f8e3c3dd1 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODPLUGIN_H +#define INCLUDE_PACKETDEMODPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class PacketDemodPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.packetdemod") + +public: + explicit PacketDemodPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const; + virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const; + virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_PACKETDEMODPLUGIN_H diff --git a/plugins/channelrx/demodpacket/packetdemodsettings.cpp b/plugins/channelrx/demodpacket/packetdemodsettings.cpp new file mode 100644 index 000000000..3d8d1546f --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsettings.cpp @@ -0,0 +1,145 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "packetdemodsettings.h" + +PacketDemodSettings::PacketDemodSettings() : + m_channelMarker(0) +{ + resetToDefaults(); +} + +void PacketDemodSettings::resetToDefaults() +{ + m_inputFrequencyOffset = 0; + m_baud = 1200; + m_rfBandwidth = 12500.0f; + m_fmDeviation = 2500.0f; + m_filterFrom = ""; + m_filterTo = ""; + m_filterPID = ""; + + m_rgbColor = QColor(255, 255, 0).rgb(); + m_title = "Packet Demodulator"; + m_streamIndex = 0; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; + + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + { + m_columnIndexes[i] = i; + m_columnSizes[i] = -1; // Autosize + } +} + +QByteArray PacketDemodSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, m_streamIndex); + s.writeString(4, m_filterFrom); + s.writeString(4, m_filterTo); + s.writeString(5, m_filterPID); + + if (m_channelMarker) { + s.writeBlob(6, m_channelMarker->serialize()); + } + + s.writeU32(7, m_rgbColor); + s.writeString(9, m_title); + s.writeBool(14, m_useReverseAPI); + s.writeString(15, m_reverseAPIAddress); + s.writeU32(16, m_reverseAPIPort); + s.writeU32(17, m_reverseAPIDeviceIndex); + s.writeU32(18, m_reverseAPIChannelIndex); + + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + s.writeS32(100 + i, m_columnIndexes[i]); + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + s.writeS32(200 + i, m_columnSizes[i]); + + return s.final(); +} + +bool PacketDemodSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readS32(1, &m_inputFrequencyOffset, 0); + d.readS32(2, &m_streamIndex, 0); + d.readString(3, &m_filterFrom, ""); + d.readString(4, &m_filterTo, ""); + d.readString(5, &m_filterPID, ""); + d.readBlob(6, &bytetmp); + + if (m_channelMarker) { + m_channelMarker->deserialize(bytetmp); + } + + d.readU32(7, &m_rgbColor); + d.readString(9, &m_title, "Packet Demodulator"); + d.readBool(14, &m_useReverseAPI, false); + d.readString(15, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(16, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(17, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(18, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + d.readS32(100 + i, &m_columnIndexes[i], i); + for (int i = 0; i < PACKETDEMOD_COLUMNS; i++) + d.readS32(200 + i, &m_columnSizes[i], -1); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/channelrx/demodpacket/packetdemodsettings.h b/plugins/channelrx/demodpacket/packetdemodsettings.h new file mode 100644 index 000000000..326f09fa3 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsettings.h @@ -0,0 +1,61 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODSETTINGS_H +#define INCLUDE_PACKETDEMODSETTINGS_H + +#include +#include + +class Serializable; + +// Number of columns in the table +#define PACKETDEMOD_COLUMNS 7 + +struct PacketDemodSettings +{ + qint32 m_inputFrequencyOffset; + qint32 m_baud; + Real m_rfBandwidth; + Real m_fmDeviation; + QString m_filterFrom; + QString m_filterTo; + QString m_filterPID; + + quint32 m_rgbColor; + QString m_title; + Serializable *m_channelMarker; + QString m_audioDeviceName; + int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; + + int m_columnIndexes[PACKETDEMOD_COLUMNS];//!< How the columns are ordered in the table + int m_columnSizes[PACKETDEMOD_COLUMNS]; //!< Size of the columns in the table + + PacketDemodSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* INCLUDE_PACKETDEMODSETTINGS_H */ diff --git a/plugins/channelrx/demodpacket/packetdemodsink.cpp b/plugins/channelrx/demodpacket/packetdemodsink.cpp new file mode 100644 index 000000000..ce3930b6c --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsink.cpp @@ -0,0 +1,306 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspengine.h" +#include "util/db.h" +#include "util/stepfunctions.h" +#include "pipes/pipeendpoint.h" +#include "maincore.h" + +#include "packetdemod.h" +#include "packetdemodsink.h" + +PacketDemodSink::PacketDemodSink(PacketDemod *packetDemod) : + m_packetDemod(packetDemod), + m_channelSampleRate(PACKETDEMOD_CHANNEL_SAMPLE_RATE), + m_channelFrequencyOffset(0), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_messageQueueToChannel(nullptr), + m_f1(nullptr), + m_f0(nullptr), + m_corrBuf(nullptr), + m_corrIdx(0), + m_corrCnt(0) +{ + m_magsq = 0.0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +PacketDemodSink::~PacketDemodSink() +{ +} + +void PacketDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else // decimate + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } +} + +void PacketDemodSink::processOneSample(Complex &ci) +{ + Complex ca; + + // FM demodulation + double magsqRaw; + Real deviation; + Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); + + // Calculate average and peak levels for level meter + Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + m_magsqCount++; + + m_corrBuf[m_corrIdx] = fmDemod; + if (m_corrCnt >= m_correlationLength && magsq > 1e-7) + { + // Correlate with 1200 + 2200 baud complex exponentials + Complex corrF0 = 0.0f; + Complex corrF1 = 0.0f; + for (int i = 0; i < m_correlationLength; i++) + { + int j = m_corrIdx - i; + if (j < 0) + j += m_correlationLength; + corrF0 += m_f0[i] * m_corrBuf[j]; + corrF1 += m_f1[i] * m_corrBuf[j]; + } + m_corrCnt--; // Avoid overflow in increment below + + // Low pass filter, to minimize changes above the baud rate + Real f0Filt = m_lowpassF0.filter(std::abs(corrF0)); + Real f1Filt = m_lowpassF1.filter(std::abs(corrF1)); + + // Determine which is the closest match and then quantise to 1 or -1 + // FIXME: We should try to account for the fact that higher frequencies can have preemphasis + float diff = f1Filt - f0Filt; + int sample = diff >= 0.0f ? 1 : 0; + + // Look for edge + if (sample != m_samplePrev) + { + m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud/2; + } + else + { + m_syncCount--; + if (m_syncCount <= 0) + { + // HDLC deframing + + // Should be in the middle of the symbol + // NRZI decoding + int bit; + if (sample != m_symbolPrev) + bit = 0; + else + bit = 1; + m_symbolPrev = sample; + + // Store in shift reg + m_bits |= bit << m_bitCount; + m_bitCount++; + + if (bit == 1) + { + m_onesCount++; + // Shouldn't ever get 7 1s in a row + if ((m_onesCount == 7) && m_gotSOP) + { + m_gotSOP = false; + m_byteCount = 0; + } + } + else if (bit == 0) + { + if (m_onesCount == 5) + { + // Remove bit-stuffing (5 1s followed by a 0) + m_bitCount--; + } + else if (m_onesCount == 6) + { + // Start/end of packet + if ((m_bitCount == 8) && (m_bits == 0x7e) && (m_byteCount > 0)) + { + // End of packet + // Check CRC is valid + m_crc.init(); + m_crc.calculate(m_bytes, m_byteCount - 2); + uint16_t calcCrc = m_crc.get(); + uint16_t rxCrc = m_bytes[m_byteCount-2] | (m_bytes[m_byteCount-1] << 8); + if (calcCrc == rxCrc) + { + QByteArray rxPacket((char *)m_bytes, m_byteCount); + qDebug() << "RX: " << rxPacket.toHex(); + if (getMessageQueueToChannel()) + { + MainCore::MsgPacket *msg = MainCore::MsgPacket::create(m_packetDemod, rxPacket, QDateTime::currentDateTime()); // FIXME pointer + getMessageQueueToChannel()->push(msg); + } + } + else + qDebug() << QString("CRC mismatch: %1 %2").arg(calcCrc, 4, 16, QLatin1Char('0')).arg(rxCrc, 4, 16, QLatin1Char('0')); + // Reset state to start receiving next packet + m_gotSOP = false; + m_bits = 0; + m_bitCount = 0; + m_byteCount = 0; + } + else + { + // Start of packet + m_gotSOP = true; + m_bits = 0; + m_bitCount = 0; + m_byteCount = 0; + } + } + m_onesCount = 0; + } + + if (m_gotSOP) + { + if (m_bitCount == 8) + { + if (m_byteCount >= 512) + { + // Too many bytes + m_gotSOP = false; + m_byteCount = 0; + } + else + { + m_bytes[m_byteCount] = m_bits; + m_byteCount++; + } + m_bits = 0; + m_bitCount = 0; + } + } + m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud; + } + } + m_samplePrev = sample; + } + m_corrIdx = (m_corrIdx + 1) % m_correlationLength; + m_corrCnt++; +} + +void PacketDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "PacketDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if ((m_channelFrequencyOffset != channelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, PACKETDEMOD_CHANNEL_BANDWIDTH); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) PACKETDEMOD_CHANNEL_SAMPLE_RATE; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void PacketDemodSink::applySettings(const PacketDemodSettings& settings, bool force) +{ + qDebug() << "PacketDemodSink::applySettings:" + << " force: " << force; + + if (force) + { + m_lowpass.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f); + m_phaseDiscri.setFMScaling(PACKETDEMOD_CHANNEL_SAMPLE_RATE / (2.0f * settings.m_fmDeviation)); + + delete m_f1; + delete m_f0; + delete m_corrBuf; + m_correlationLength = PACKETDEMOD_CHANNEL_SAMPLE_RATE/settings.m_baud; + m_f1 = new Complex[m_correlationLength](); + m_f0 = new Complex[m_correlationLength](); + m_corrBuf = new Complex[m_correlationLength](); + m_corrIdx = 0; + m_corrCnt = 0; + Real f0 = 0.0f; + Real f1 = 0.0f; + for (int i = 0; i < m_correlationLength; i++) + { + m_f0[i] = Complex(cos(f0), sin(f0)); + m_f1[i] = Complex(cos(f1), sin(f1)); + f0 += 2.0f*(Real)M_PI*2200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE; + f1 += 2.0f*(Real)M_PI*1200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE; + } + + m_lowpassF1.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f); + m_lowpassF0.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f); + m_samplePrev = 0; + m_syncCount = 0; + m_symbolPrev = 0; + m_bits = 0; + m_bitCount = 0; + m_onesCount = 0; + m_gotSOP = false; + m_byteCount = 0; + } + + m_settings = settings; +} diff --git a/plugins/channelrx/demodpacket/packetdemodsink.h b/plugins/channelrx/demodpacket/packetdemodsink.h new file mode 100644 index 000000000..45ebb8de7 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsink.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODSINK_H +#define INCLUDE_PACKETDEMODSINK_H + +#include "dsp/channelsamplesink.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/firfilter.h" +#include "util/movingaverage.h" +#include "util/doublebufferfifo.h" +#include "util/messagequeue.h" +#include "util/crc.h" + +#include "packetdemodsettings.h" + +#include +#include +#include + +#define PACKETDEMOD_CHANNEL_BANDWIDTH 9600 +// Must be integer multiple of m_baud=1200 +#define PACKETDEMOD_CHANNEL_SAMPLE_RATE 38400 + +class PacketDemod; + +class PacketDemodSink : public ChannelSampleSink { +public: + PacketDemodSink(PacketDemod *packetDemod); + ~PacketDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const PacketDemodSettings& settings, bool force = false); + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + + double getMagSq() const { return m_magsq; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + PacketDemod *m_packetDemod; + PacketDemodSettings m_settings; + int m_channelSampleRate; + int m_channelFrequencyOffset; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MessageQueue *m_messageQueueToChannel; + + MovingAverageUtil m_movingAverage; + + Lowpass m_lowpass; + PhaseDiscriminators m_phaseDiscri; + + int m_correlationLength; + Complex *m_f1; + Complex *m_f0; + Complex *m_corrBuf; + int m_corrIdx; + int m_corrCnt; + + Lowpass m_lowpassF1; + Lowpass m_lowpassF0; + + int m_samplePrev; + int m_syncCount; + int m_symbolPrev; + unsigned char m_bits; + int m_bitCount; + int m_onesCount; + bool m_gotSOP; + unsigned char m_bytes[512]; // Info field can be 256 bytes + int m_byteCount; + crc16x25 m_crc; + + void processOneSample(Complex &ci); + MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } +}; + +#endif // INCLUDE_PACKETDEMODSINK_H diff --git a/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp new file mode 100644 index 000000000..60ba79449 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGChannelSettings.h" +#include "packetdemod.h" +#include "packetdemodwebapiadapter.h" + +PacketDemodWebAPIAdapter::PacketDemodWebAPIAdapter() +{} + +PacketDemodWebAPIAdapter::~PacketDemodWebAPIAdapter() +{} + +int PacketDemodWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings()); + response.getPacketDemodSettings()->init(); + PacketDemod::webapiFormatChannelSettings(response, m_settings); + + return 200; +} + +int PacketDemodWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; + (void) errorMessage; + PacketDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response); + + return 200; +} diff --git a/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h new file mode 100644 index 000000000..79f3660b5 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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_PACKETDEMOD_WEBAPIADAPTER_H +#define INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "packetdemodsettings.h" + +/** + * Standalone API adapter only for the settings + */ +class PacketDemodWebAPIAdapter : public ChannelWebAPIAdapter { +public: + PacketDemodWebAPIAdapter(); + virtual ~PacketDemodWebAPIAdapter(); + + virtual QByteArray serialize() const { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + +private: + PacketDemodSettings m_settings; +}; + +#endif // INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H diff --git a/plugins/channelrx/demodpacket/readme.md b/plugins/channelrx/demodpacket/readme.md new file mode 100644 index 000000000..803f074fd --- /dev/null +++ b/plugins/channelrx/demodpacket/readme.md @@ -0,0 +1,63 @@ +

Packet radio demodulator plugin

+ +

Introduction

+ +This plugin can be used to demodulate packet radio (APRS/AX.25) data packets. Received packets can be sent to the APRS Feature for decoding and display. + +

Interface

+ +![Packet Demodulator plugin GUI](../../../doc/img/PacketDemod_plugin.png) + +

1: Frequency shift from center frequency of reception

+ +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

2: Channel power

+ +Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. + +

3: Level meter in dB

+ + - top bar (green): average value + - bottom bar (blue green): instantaneous peak value + - tip vertical bar (bright green): peak hold value + +

4: Modulation

+ +This specifies the baud rate and modulation that is used for the packet transmission. Currently 1200 baud AFSK is supported. + +

5: RF Bandwidth

+ +This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. + +

6: Frequency deviation

+ +Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. Typical values are 2.5 kHz and 5 kHz. + +

7: Filter Packets From

+ +Entering a regular expression in the From field displays only packets where the source address, displayed in the From column, matches the regular expression. + +

8: Filter Packets To

+ +Entering a regular expression in the To field displays only packets where the destination address, displayed in the To column, matches the regular expression. + +

9: Filter PID No L3

+ +Checking this option displays only packets where the PID (Protocol ID) field is 0xf0 (no L3). This value is used by APRS and BBS data packets, and helps to filter out control packets. + +

10: Clear Packets from table

+ +Pressing this button clears all packets from the table. + +

Received Packets Table

+ +The received packets table displays the contexts of the packets that have been received. Only packets with valid CRCs are displayed. + +* From - The source address / callsign of the sender of the packet. +* To - The destination address. +* Via - List of addresses of repeaters the packet has passed through or directed via. +* Type - The AX.25 frame type. +* PID - Protocol Identifier. +* Data (ASCII) - The AX.25 information field displayed as ASCII. +* Data (Hex) - The AX.25 information field displayed as hexidecimal. diff --git a/plugins/channelrx/demodvor/vordemod.cpp b/plugins/channelrx/demodvor/vordemod.cpp index d6dc16ce7..48efc60e5 100644 --- a/plugins/channelrx/demodvor/vordemod.cpp +++ b/plugins/channelrx/demodvor/vordemod.cpp @@ -473,10 +473,10 @@ void VORDemod::webapiFormatChannelSettings( swgVORDemodSettings->setStreamIndex(settings.m_streamIndex); } if (channelSettingsKeys.contains("identThreshold") || force) { - swgVORDemodSettings->setAudioMute(settings.m_identThreshold); + swgVORDemodSettings->setIdentThreshold(settings.m_identThreshold); } if (channelSettingsKeys.contains("magDecAdjust") || force) { - swgVORDemodSettings->setAudioMute(settings.m_magDecAdjust ? 1 : 0); + swgVORDemodSettings->setMagDecAdjust(settings.m_magDecAdjust ? 1 : 0); } } diff --git a/plugins/channelrx/demodvor/vordemodgui.cpp b/plugins/channelrx/demodvor/vordemodgui.cpp index 0bd14eff3..c512b82d3 100644 --- a/plugins/channelrx/demodvor/vordemodgui.cpp +++ b/plugins/channelrx/demodvor/vordemodgui.cpp @@ -384,8 +384,8 @@ static bool calcIntersectionPoint(float lat1, float lon1, float bearing1, float double lat3Rad = asin(sinLat1*cos(delta13)+cosLat1*sin(delta13)*cos(theta13)); double lon3Rad = lon1Rad + atan2(sin(theta13)*sin(delta13)*cosLat1, cos(delta13)-sinLat1*sin(lat3Rad)); - intersectLat = Units::radiansToDegress(lat3Rad); - intersectLon = Units::radiansToDegress(lon3Rad); + intersectLat = Units::radiansToDegrees(lat3Rad); + intersectLon = Units::radiansToDegrees(lon3Rad); return true; } diff --git a/plugins/channelrx/demodvor/vordemodsink.cpp b/plugins/channelrx/demodvor/vordemodsink.cpp index 272836971..0a2db5385 100644 --- a/plugins/channelrx/demodvor/vordemodsink.cpp +++ b/plugins/channelrx/demodvor/vordemodsink.cpp @@ -222,7 +222,7 @@ void VORDemodSink::processOneSample(Complex &ci) if (m_varGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1) { m_varGoertzel.goertzel(mag); - varPhase = Units::radiansToDegress(m_varGoertzel.phase()); + varPhase = Units::radiansToDegrees(m_varGoertzel.phase()); varMag = m_varGoertzel.mag(); m_varGoertzel.reset(); } @@ -245,7 +245,7 @@ void VORDemodSink::processOneSample(Complex &ci) if (m_refGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1) { m_refGoertzel.goertzel(phi); - float phaseDeg = Units::radiansToDegress(m_refGoertzel.phase()); + float phaseDeg = Units::radiansToDegrees(m_refGoertzel.phase()); double refMag = m_refGoertzel.mag(); int groupDelay = (301-1)/2; float filterPhaseShift = 360.0*30.0*groupDelay/VORDEMOD_CHANNEL_SAMPLE_RATE; diff --git a/plugins/channelrx/demodvorsc/vordemodscsink.cpp b/plugins/channelrx/demodvorsc/vordemodscsink.cpp index 5c31f63a2..46caad290 100644 --- a/plugins/channelrx/demodvorsc/vordemodscsink.cpp +++ b/plugins/channelrx/demodvorsc/vordemodscsink.cpp @@ -211,7 +211,7 @@ void VORDemodSCSink::processOneSample(Complex &ci) if (m_varGoertzel.size() == VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE - 1) { m_varGoertzel.goertzel(mag); - varPhase = Units::radiansToDegress(m_varGoertzel.phase()); + varPhase = Units::radiansToDegrees(m_varGoertzel.phase()); varMag = m_varGoertzel.mag(); m_varGoertzel.reset(); } @@ -234,7 +234,7 @@ void VORDemodSCSink::processOneSample(Complex &ci) if (m_refGoertzel.size() == VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE - 1) { m_refGoertzel.goertzel(phi); - float phaseDeg = Units::radiansToDegress(m_refGoertzel.phase()); + float phaseDeg = Units::radiansToDegrees(m_refGoertzel.phase()); double refMag = m_refGoertzel.mag(); int groupDelay = (301-1)/2; float filterPhaseShift = 360.0*30.0*groupDelay/VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE; diff --git a/plugins/channeltx/modpacket/packetmodsource.cpp b/plugins/channeltx/modpacket/packetmodsource.cpp index f4f8431d1..379600074 100644 --- a/plugins/channeltx/modpacket/packetmodsource.cpp +++ b/plugins/channeltx/modpacket/packetmodsource.cpp @@ -25,6 +25,7 @@ #include "util/crc.h" #include "util/messagequeue.h" #include "maincore.h" +#include "channel/channelapi.h" PacketModSource::PacketModSource() : m_channelSampleRate(48000), @@ -424,33 +425,56 @@ void PacketModSource::applyChannelSettings(int channelSampleRate, int channelFre } } +static bool ax25_ssid(QByteArray& b, int i, int len, uint8_t& ssid) +{ + if (b[i] == '-') + { + if (len > i + 1) + { + ssid = b[i+1] - '0'; + if ((len > i + 2) && isdigit(b[i+2])) { + ssid = (ssid*10) + (b[i+2] - '0'); + } + if (ssid >= 16) + { + qDebug() << "ax25_address: SSID greater than 15 not supported"; + ssid = ssid & 0xf; + return false; + } + else + { + return true; + } + } + else + { + qDebug() << "ax25_address: SSID number missing"; + return false; + } + } + else + return false; +} + static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl) { int len; int i; QByteArray b; - int ssid; + uint8_t ssid = 0; + bool hyphenSeen = false; len = address.length(); b = address.toUtf8(); ssid = 0; for (i = 0; i < 6; i++) { - if ((i < len) && (ssid == 0)) + if ((i < len) && !hyphenSeen) { if (b[i] == '-') { - if (len > i + 1) - { - ssid = b[i+1] - '0'; - if ((len > i + 2) && isdigit(b[i+2])) { - ssid = (ssid*10) + (b[i+1] - '0'); - } - if (ssid >= 16) - qDebug() << "ax25_address: SSID greater than 15 not supported"; - } - else - qDebug() << "ax25_address: SSID number missing"; + ax25_ssid(b, i, len, ssid); + hyphenSeen = true; *p++ = ' ' << 1; } else @@ -463,6 +487,10 @@ static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl) *p++ = ' ' << 1; } } + if (b[i] == '-') + { + ax25_ssid(b, i, len, ssid); + } *p++ = crrl | (ssid << 1); return p; @@ -534,6 +562,7 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt { uint8_t packet[AX25_MAX_BYTES]; uint8_t *crc_start; + uint8_t *packet_end; uint8_t *p; crc16x25 crc; uint16_t crcValue; @@ -565,6 +594,7 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt crcValue = crc.get(); *p++ = crcValue & 0xff; *p++ = (crcValue >> 8); + packet_end = p; // Flag for (int i = 0; i < std::min(m_settings.m_ax25PostFlags, AX25_MAX_FLAGS); i++) *p++ = AX25_FLAG; @@ -582,8 +612,17 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt for (int j = 0; j < 8; j++) { int tx_bit = (packet[i] >> j) & 1; - // Stuff 0 if last 5 bits are 1s - if ((packet[i] != AX25_FLAG) && (m_last5Bits == 0x1f)) + // Stuff 0 if last 5 bits are 1s, unless transmitting flag + // Except for special case of when last 5 bits of CRC are 1s + if ( ( (packet[i] != AX25_FLAG) + || ( (&packet[i] >= crc_start) + && ( (&packet[i] < packet_end) + || ((&packet[i] == packet_end) && (j == 0)) + ) + ) + ) + && (m_last5Bits == 0x1f) + ) addBit(0); addBit(tx_bit); } diff --git a/plugins/feature/CMakeLists.txt b/plugins/feature/CMakeLists.txt index b9a1b553d..0e1e7d3da 100644 --- a/plugins/feature/CMakeLists.txt +++ b/plugins/feature/CMakeLists.txt @@ -5,10 +5,13 @@ if (Qt5SerialPort_FOUND) endif() if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND) + add_subdirectory(map) add_subdirectory(vorlocalizer) endif() add_subdirectory(afc) +add_subdirectory(aprs) add_subdirectory(demodanalyzer) add_subdirectory(rigctlserver) add_subdirectory(simpleptt) +add_subdirectory(startracker) diff --git a/plugins/feature/afc/afc.cpp b/plugins/feature/afc/afc.cpp index 7af948f64..1e0c1732a 100644 --- a/plugins/feature/afc/afc.cpp +++ b/plugins/feature/afc/afc.cpp @@ -135,8 +135,7 @@ bool AFC::handleMessage(const Message& cmd) qDebug() << "AFC::handleMessage: MessagePipesCommon::MsgReportChannelDeleted"; MessagePipesCommon::MsgReportChannelDeleted& report = (MessagePipesCommon::MsgReportChannelDeleted&) cmd; const MessagePipesCommon::ChannelRegistrationKey& channelKey = report.getChannelRegistrationKey(); - const ChannelAPI *channel = channelKey.m_key; - MainCore::instance()->getMessagePipes().unregisterChannelToFeature(channel, this, "settings"); + MainCore::instance()->getMessagePipes().unregisterChannelToFeature(channelKey.m_key, this, "settings"); return true; } diff --git a/plugins/feature/aprs/CMakeLists.txt b/plugins/feature/aprs/CMakeLists.txt new file mode 100644 index 000000000..30d66a3ed --- /dev/null +++ b/plugins/feature/aprs/CMakeLists.txt @@ -0,0 +1,67 @@ +project(aprs) + +set(aprs_SOURCES + aprs.cpp + aprssettings.cpp + aprsplugin.cpp + aprsworker.cpp + aprswebapiadapter.cpp +) + +set(aprs_HEADERS + aprs.h + aprssettings.h + aprsplugin.h + aprsreport.h + aprsworker.h + aprswebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(aprs_SOURCES + ${aprs_SOURCES} + aprsgui.cpp + aprsgui.ui + aprssettingsdialog.cpp + aprssettingsdialog.ui + aprs.qrc + ) + set(aprs_HEADERS + ${aprs_HEADERS} + aprsgui.h + aprssettingsdialog.h + ) + + set(TARGET_NAME aprs) + set(TARGET_LIB "Qt5::Widgets" Qt5::Charts) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME aprssrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${aprs_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) + +if(WIN32) + # Run deployqt for Charts etc + include(DeployQt) + windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/aprs) +endif() diff --git a/plugins/feature/aprs/aprs.cpp b/plugins/feature/aprs/aprs.cpp new file mode 100644 index 000000000..51e072f93 --- /dev/null +++ b/plugins/feature/aprs/aprs.cpp @@ -0,0 +1,415 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "SWGFeatureSettings.h" +#include "SWGFeatureReport.h" +#include "SWGFeatureActions.h" +#include "SWGDeviceState.h" + +#include "dsp/dspengine.h" + +#include "device/deviceset.h" +#include "channel/channelapi.h" +#include "maincore.h" +#include "aprsworker.h" +#include "aprs.h" + +MESSAGE_CLASS_DEFINITION(APRS::MsgConfigureAPRS, Message) +MESSAGE_CLASS_DEFINITION(APRS::MsgReportWorker, Message) + +const char* const APRS::m_featureIdURI = "sdrangel.feature.aprs"; +const char* const APRS::m_featureId = "APRS"; + +APRS::APRS(WebAPIAdapterInterface *webAPIAdapterInterface) : + Feature(m_featureIdURI, webAPIAdapterInterface) +{ + qDebug("APRS::APRS: webAPIAdapterInterface: %p", webAPIAdapterInterface); + setObjectName(m_featureId); + m_worker = new APRSWorker(this, webAPIAdapterInterface); + m_state = StIdle; + m_errorMessage = "APRS error"; + connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes())); + m_updatePipesTimer.start(1000); +} + +APRS::~APRS() +{ + if (m_worker->isRunning()) { + stop(); + } + + delete m_worker; +} + +void APRS::start() +{ + qDebug("APRS::start"); + m_worker->reset(); + m_worker->setMessageQueueToFeature(getInputMessageQueue()); + m_worker->setMessageQueueToGUI(getMessageQueueToGUI()); + bool ok = m_worker->startWork(); + m_state = ok ? StIdle : StError; + m_thread.start(); + + APRSWorker::MsgConfigureAPRSWorker *msg = APRSWorker::MsgConfigureAPRSWorker::create(m_settings, true); + m_worker->getInputMessageQueue()->push(msg); +} + +void APRS::stop() +{ + qDebug("APRS::stop"); + m_worker->stopWork(); + m_state = StIdle; + m_thread.quit(); + m_thread.wait(); +} + +bool APRS::handleMessage(const Message& cmd) +{ + if (MsgConfigureAPRS::match(cmd)) + { + MsgConfigureAPRS& cfg = (MsgConfigureAPRS&) cmd; + qDebug() << "APRS::handleMessage: MsgConfigureAPRS"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgReportWorker::match(cmd)) + { + MsgReportWorker& report = (MsgReportWorker&) cmd; + if (report.getMessage() == "Connected") + m_state = StRunning; + else if (report.getMessage() == "Disconnected") + m_state = StIdle; + else + { + m_state = StError; + m_errorMessage = report.getMessage(); + } + return true; + } + else if (MainCore::MsgPacket::match(cmd)) + { + MainCore::MsgPacket& report = (MainCore::MsgPacket&) cmd; + if (getMessageQueueToGUI()) + { + MainCore::MsgPacket *copy = new MainCore::MsgPacket(report); + getMessageQueueToGUI()->push(copy); + } + if (m_state == StRunning) + { + MainCore::MsgPacket *copy = new MainCore::MsgPacket(report); + m_worker->getInputMessageQueue()->push(copy); + } + return true; + } + else + { + return false; + } +} + +void APRS::updatePipes() +{ + QList availablePipes = updateAvailablePipeSources("packets", APRSSettings::m_pipeTypes, APRSSettings::m_pipeURIs, this); + + if (availablePipes != m_availablePipes) + { + m_availablePipes = availablePipes; + if (getMessageQueueToGUI()) + { + MsgReportPipes *msgToGUI = MsgReportPipes::create(); + QList& msgAvailablePipes = msgToGUI->getAvailablePipes(); + msgAvailablePipes.append(availablePipes); + getMessageQueueToGUI()->push(msgToGUI); + } + } +} + +QByteArray APRS::serialize() const +{ + return m_settings.serialize(); +} + +bool APRS::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureAPRS *msg = MsgConfigureAPRS::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureAPRS *msg = MsgConfigureAPRS::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void APRS::applySettings(const APRSSettings& settings, bool force) +{ + qDebug() << "APRS::applySettings:" + << " m_igateEnabled: " << settings.m_igateEnabled + << " m_title: " << settings.m_title + << " m_rgbColor: " << settings.m_rgbColor + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex + << " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex + << " force: " << force; + + QList reverseAPIKeys; + + if ((m_settings.m_igateEnabled != settings.m_igateEnabled) || force) + { + if (settings.m_igateEnabled) + start(); + else + stop(); + reverseAPIKeys.append("igateEnabled"); + } + + if ((m_settings.m_title != settings.m_title) || force) { + reverseAPIKeys.append("title"); + } + if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) { + reverseAPIKeys.append("rgbColor"); + } + + APRSWorker::MsgConfigureAPRSWorker *msg = APRSWorker::MsgConfigureAPRSWorker::create( + settings, force + ); + m_worker->getInputMessageQueue()->push(msg); + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) || + (m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +int APRS::webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) run; + (void) response; + (void) errorMessage; + //getFeatureStateStr(*response.getState()); + //MsgStartStopIGate *msg = MsgStartStopIGate::create(run); + //getInputMessageQueue()->push(msg); + return 202; +} + +int APRS::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setAprsSettings(new SWGSDRangel::SWGAPRSSettings()); + response.getAprsSettings()->init(); + webapiFormatFeatureSettings(response, m_settings); + return 200; +} + +int APRS::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + APRSSettings settings = m_settings; + webapiUpdateFeatureSettings(settings, featureSettingsKeys, response); + + MsgConfigureAPRS *msg = MsgConfigureAPRS::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("APRS::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAPRS *msgToGUI = MsgConfigureAPRS::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatFeatureSettings(response, settings); + + return 200; +} + +void APRS::webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const APRSSettings& settings) +{ + response.getAprsSettings()->setIgateServer(new QString(settings.m_igateServer)); + response.getAprsSettings()->setIgatePort(settings.m_igatePort); + response.getAprsSettings()->setIgateCallsign(new QString(settings.m_igateCallsign)); + response.getAprsSettings()->setIgatePasscode(new QString(settings.m_igatePasscode)); + response.getAprsSettings()->setIgateFilter(new QString(settings.m_igateFilter)); + response.getAprsSettings()->setIgateEnabled(settings.m_igateEnabled ? 1 : 0); + + if (response.getAprsSettings()->getTitle()) { + *response.getAprsSettings()->getTitle() = settings.m_title; + } else { + response.getAprsSettings()->setTitle(new QString(settings.m_title)); + } + + response.getAprsSettings()->setRgbColor(settings.m_rgbColor); + response.getAprsSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getAprsSettings()->getReverseApiAddress()) { + *response.getAprsSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getAprsSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getAprsSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getAprsSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex); + response.getAprsSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex); +} + +void APRS::webapiUpdateFeatureSettings( + APRSSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response) +{ + if (featureSettingsKeys.contains("igateServer")) { + settings.m_igateServer = *response.getAprsSettings()->getIgateServer(); + } + if (featureSettingsKeys.contains("igatePort")) { + settings.m_igatePort = response.getAprsSettings()->getIgatePort(); + } + if (featureSettingsKeys.contains("igateCallsign")) { + settings.m_igateCallsign = *response.getAprsSettings()->getIgateCallsign(); + } + if (featureSettingsKeys.contains("igatePasscode")) { + settings.m_igatePasscode = *response.getAprsSettings()->getIgatePasscode(); + } + if (featureSettingsKeys.contains("igateFilter")) { + settings.m_igateFilter = *response.getAprsSettings()->getIgateFilter(); + } + if (featureSettingsKeys.contains("title")) { + settings.m_title = *response.getAprsSettings()->getTitle(); + } + if (featureSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getAprsSettings()->getRgbColor(); + } + if (featureSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getAprsSettings()->getUseReverseApi() != 0; + } + if (featureSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getAprsSettings()->getReverseApiAddress(); + } + if (featureSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getAprsSettings()->getReverseApiPort(); + } + if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIFeatureSetIndex = response.getAprsSettings()->getReverseApiDeviceIndex(); + } + if (featureSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIFeatureIndex = response.getAprsSettings()->getReverseApiChannelIndex(); + } +} + +void APRS::webapiReverseSendSettings(QList& featureSettingsKeys, const APRSSettings& settings, bool force) +{ + SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings(); + // swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet()); + // swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex()); + swgFeatureSettings->setFeatureType(new QString("APRS")); + swgFeatureSettings->setAprsSettings(new SWGSDRangel::SWGAPRSSettings()); + SWGSDRangel::SWGAPRSSettings *swgAPRSSettings = swgFeatureSettings->getAprsSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (featureSettingsKeys.contains("igateServer") || force) { + swgAPRSSettings->setIgateServer(new QString(settings.m_igateServer)); + } + if (featureSettingsKeys.contains("igatePort") || force) { + swgAPRSSettings->setIgatePort(settings.m_igatePort); + } + if (featureSettingsKeys.contains("igateCallsign") || force) { + swgAPRSSettings->setIgateCallsign(new QString(settings.m_igateCallsign)); + } + if (featureSettingsKeys.contains("igatePasscode") || force) { + swgAPRSSettings->setIgatePasscode(new QString(settings.m_igatePasscode)); + } + if (featureSettingsKeys.contains("igateFilter") || force) { + swgAPRSSettings->setIgateFilter(new QString(settings.m_igateFilter)); + } + if (featureSettingsKeys.contains("title") || force) { + swgAPRSSettings->setTitle(new QString(settings.m_title)); + } + if (featureSettingsKeys.contains("rgbColor") || force) { + swgAPRSSettings->setRgbColor(settings.m_rgbColor); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIFeatureSetIndex) + .arg(settings.m_reverseAPIFeatureIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgFeatureSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgFeatureSettings; +} + +void APRS::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "APRS::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("APRS::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/feature/aprs/aprs.h b/plugins/feature/aprs/aprs.h new file mode 100644 index 000000000..760b80721 --- /dev/null +++ b/plugins/feature/aprs/aprs.h @@ -0,0 +1,143 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_APRS_H_ +#define INCLUDE_FEATURE_APRS_H_ + +#include +#include +#include +#include + +#include "feature/feature.h" +#include "util/message.h" + +#include "aprssettings.h" + +class WebAPIAdapterInterface; +class APRSWorker; +class QNetworkAccessManager; +class QNetworkReply; + +namespace SWGSDRangel { + class SWGDeviceState; +} + +class APRS : public Feature +{ + Q_OBJECT +public: + class MsgConfigureAPRS : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const APRSSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAPRS* create(const APRSSettings& settings, bool force) { + return new MsgConfigureAPRS(settings, force); + } + + private: + APRSSettings m_settings; + bool m_force; + + MsgConfigureAPRS(const APRSSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgReportWorker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QString getMessage() { return m_message; } + + static MsgReportWorker* create(QString message) { + return new MsgReportWorker(message); + } + + private: + QString m_message; + + MsgReportWorker(QString message) : + Message(), + m_message(message) + {} + }; + + APRS(WebAPIAdapterInterface *webAPIAdapterInterface); + virtual ~APRS(); + virtual void destroy() { delete this; } + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) const { id = objectName(); } + virtual void getTitle(QString& title) const { title = m_settings.m_title; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + static void webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const APRSSettings& settings); + + static void webapiUpdateFeatureSettings( + APRSSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response); + + static const char* const m_featureIdURI; + static const char* const m_featureId; + +private: + QThread m_thread; + APRSWorker *m_worker; + APRSSettings m_settings; + QList m_availablePipes; + QTimer m_updatePipesTimer; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void start(); + void stop(); + void applySettings(const APRSSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& featureSettingsKeys, const APRSSettings& settings, bool force); + +private slots: + void updatePipes(); + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_FEATURE_APRS_H_ diff --git a/plugins/feature/aprs/aprs.qrc b/plugins/feature/aprs/aprs.qrc new file mode 100644 index 000000000..5bd0fca3f --- /dev/null +++ b/plugins/feature/aprs/aprs.qrc @@ -0,0 +1,196 @@ + + + aprs/aprs-symbols-24-0-00.png + aprs/aprs-symbols-24-0-01.png + aprs/aprs-symbols-24-0-02.png + aprs/aprs-symbols-24-0-03.png + aprs/aprs-symbols-24-0-04.png + aprs/aprs-symbols-24-0-05.png + aprs/aprs-symbols-24-0-06.png + aprs/aprs-symbols-24-0-07.png + aprs/aprs-symbols-24-0-08.png + aprs/aprs-symbols-24-0-09.png + aprs/aprs-symbols-24-0-10.png + aprs/aprs-symbols-24-0-11.png + aprs/aprs-symbols-24-0-12.png + aprs/aprs-symbols-24-0-13.png + aprs/aprs-symbols-24-0-14.png + aprs/aprs-symbols-24-0-15.png + aprs/aprs-symbols-24-0-16.png + aprs/aprs-symbols-24-0-17.png + aprs/aprs-symbols-24-0-18.png + aprs/aprs-symbols-24-0-19.png + aprs/aprs-symbols-24-0-20.png + aprs/aprs-symbols-24-0-21.png + aprs/aprs-symbols-24-0-22.png + aprs/aprs-symbols-24-0-23.png + aprs/aprs-symbols-24-0-24.png + aprs/aprs-symbols-24-0-25.png + aprs/aprs-symbols-24-0-26.png + aprs/aprs-symbols-24-0-27.png + aprs/aprs-symbols-24-0-28.png + aprs/aprs-symbols-24-0-29.png + aprs/aprs-symbols-24-0-30.png + aprs/aprs-symbols-24-0-31.png + aprs/aprs-symbols-24-0-32.png + aprs/aprs-symbols-24-0-33.png + aprs/aprs-symbols-24-0-34.png + aprs/aprs-symbols-24-0-35.png + aprs/aprs-symbols-24-0-36.png + aprs/aprs-symbols-24-0-37.png + aprs/aprs-symbols-24-0-38.png + aprs/aprs-symbols-24-0-39.png + aprs/aprs-symbols-24-0-40.png + aprs/aprs-symbols-24-0-41.png + aprs/aprs-symbols-24-0-42.png + aprs/aprs-symbols-24-0-43.png + aprs/aprs-symbols-24-0-44.png + aprs/aprs-symbols-24-0-45.png + aprs/aprs-symbols-24-0-46.png + aprs/aprs-symbols-24-0-47.png + aprs/aprs-symbols-24-0-48.png + aprs/aprs-symbols-24-0-49.png + aprs/aprs-symbols-24-0-50.png + aprs/aprs-symbols-24-0-51.png + aprs/aprs-symbols-24-0-52.png + aprs/aprs-symbols-24-0-53.png + aprs/aprs-symbols-24-0-54.png + aprs/aprs-symbols-24-0-55.png + aprs/aprs-symbols-24-0-56.png + aprs/aprs-symbols-24-0-57.png + aprs/aprs-symbols-24-0-58.png + aprs/aprs-symbols-24-0-59.png + aprs/aprs-symbols-24-0-60.png + aprs/aprs-symbols-24-0-61.png + aprs/aprs-symbols-24-0-62.png + aprs/aprs-symbols-24-0-63.png + aprs/aprs-symbols-24-0-64.png + aprs/aprs-symbols-24-0-65.png + aprs/aprs-symbols-24-0-66.png + aprs/aprs-symbols-24-0-67.png + aprs/aprs-symbols-24-0-68.png + aprs/aprs-symbols-24-0-69.png + aprs/aprs-symbols-24-0-70.png + aprs/aprs-symbols-24-0-71.png + aprs/aprs-symbols-24-0-72.png + aprs/aprs-symbols-24-0-73.png + aprs/aprs-symbols-24-0-74.png + aprs/aprs-symbols-24-0-75.png + aprs/aprs-symbols-24-0-76.png + aprs/aprs-symbols-24-0-77.png + aprs/aprs-symbols-24-0-78.png + aprs/aprs-symbols-24-0-79.png + aprs/aprs-symbols-24-0-80.png + aprs/aprs-symbols-24-0-81.png + aprs/aprs-symbols-24-0-82.png + aprs/aprs-symbols-24-0-83.png + aprs/aprs-symbols-24-0-84.png + aprs/aprs-symbols-24-0-85.png + aprs/aprs-symbols-24-0-86.png + aprs/aprs-symbols-24-0-87.png + aprs/aprs-symbols-24-0-88.png + aprs/aprs-symbols-24-0-89.png + aprs/aprs-symbols-24-0-90.png + aprs/aprs-symbols-24-0-91.png + aprs/aprs-symbols-24-0-92.png + aprs/aprs-symbols-24-0-93.png + aprs/aprs-symbols-24-0-94.png + aprs/aprs-symbols-24-0-95.png + aprs/aprs-symbols-24-1-00.png + aprs/aprs-symbols-24-1-01.png + aprs/aprs-symbols-24-1-02.png + aprs/aprs-symbols-24-1-03.png + aprs/aprs-symbols-24-1-04.png + aprs/aprs-symbols-24-1-05.png + aprs/aprs-symbols-24-1-06.png + aprs/aprs-symbols-24-1-07.png + aprs/aprs-symbols-24-1-08.png + aprs/aprs-symbols-24-1-09.png + aprs/aprs-symbols-24-1-10.png + aprs/aprs-symbols-24-1-11.png + aprs/aprs-symbols-24-1-12.png + aprs/aprs-symbols-24-1-13.png + aprs/aprs-symbols-24-1-14.png + aprs/aprs-symbols-24-1-15.png + aprs/aprs-symbols-24-1-16.png + aprs/aprs-symbols-24-1-17.png + aprs/aprs-symbols-24-1-18.png + aprs/aprs-symbols-24-1-19.png + aprs/aprs-symbols-24-1-20.png + aprs/aprs-symbols-24-1-21.png + aprs/aprs-symbols-24-1-22.png + aprs/aprs-symbols-24-1-23.png + aprs/aprs-symbols-24-1-24.png + aprs/aprs-symbols-24-1-25.png + aprs/aprs-symbols-24-1-26.png + aprs/aprs-symbols-24-1-27.png + aprs/aprs-symbols-24-1-28.png + aprs/aprs-symbols-24-1-29.png + aprs/aprs-symbols-24-1-30.png + aprs/aprs-symbols-24-1-31.png + aprs/aprs-symbols-24-1-32.png + aprs/aprs-symbols-24-1-33.png + aprs/aprs-symbols-24-1-34.png + aprs/aprs-symbols-24-1-35.png + aprs/aprs-symbols-24-1-36.png + aprs/aprs-symbols-24-1-37.png + aprs/aprs-symbols-24-1-38.png + aprs/aprs-symbols-24-1-39.png + aprs/aprs-symbols-24-1-40.png + aprs/aprs-symbols-24-1-41.png + aprs/aprs-symbols-24-1-42.png + aprs/aprs-symbols-24-1-43.png + aprs/aprs-symbols-24-1-44.png + aprs/aprs-symbols-24-1-45.png + aprs/aprs-symbols-24-1-46.png + aprs/aprs-symbols-24-1-47.png + aprs/aprs-symbols-24-1-48.png + aprs/aprs-symbols-24-1-49.png + aprs/aprs-symbols-24-1-50.png + aprs/aprs-symbols-24-1-51.png + aprs/aprs-symbols-24-1-52.png + aprs/aprs-symbols-24-1-53.png + aprs/aprs-symbols-24-1-54.png + aprs/aprs-symbols-24-1-55.png + aprs/aprs-symbols-24-1-56.png + aprs/aprs-symbols-24-1-57.png + aprs/aprs-symbols-24-1-58.png + aprs/aprs-symbols-24-1-59.png + aprs/aprs-symbols-24-1-60.png + aprs/aprs-symbols-24-1-61.png + aprs/aprs-symbols-24-1-62.png + aprs/aprs-symbols-24-1-63.png + aprs/aprs-symbols-24-1-64.png + aprs/aprs-symbols-24-1-65.png + aprs/aprs-symbols-24-1-66.png + aprs/aprs-symbols-24-1-67.png + aprs/aprs-symbols-24-1-68.png + aprs/aprs-symbols-24-1-69.png + aprs/aprs-symbols-24-1-70.png + aprs/aprs-symbols-24-1-71.png + aprs/aprs-symbols-24-1-72.png + aprs/aprs-symbols-24-1-73.png + aprs/aprs-symbols-24-1-74.png + aprs/aprs-symbols-24-1-75.png + aprs/aprs-symbols-24-1-76.png + aprs/aprs-symbols-24-1-77.png + aprs/aprs-symbols-24-1-78.png + aprs/aprs-symbols-24-1-79.png + aprs/aprs-symbols-24-1-80.png + aprs/aprs-symbols-24-1-81.png + aprs/aprs-symbols-24-1-82.png + aprs/aprs-symbols-24-1-83.png + aprs/aprs-symbols-24-1-84.png + aprs/aprs-symbols-24-1-85.png + aprs/aprs-symbols-24-1-86.png + aprs/aprs-symbols-24-1-87.png + aprs/aprs-symbols-24-1-88.png + aprs/aprs-symbols-24-1-89.png + aprs/aprs-symbols-24-1-90.png + aprs/aprs-symbols-24-1-91.png + aprs/aprs-symbols-24-1-92.png + aprs/aprs-symbols-24-1-93.png + aprs/aprs-symbols-24-1-94.png + aprs/aprs-symbols-24-1-95.png + + diff --git a/plugins/feature/aprs/aprs/README.txt b/plugins/feature/aprs/aprs/README.txt new file mode 100644 index 000000000..f00b46878 --- /dev/null +++ b/plugins/feature/aprs/aprs/README.txt @@ -0,0 +1,6 @@ +APRS images are from: https://github.com/hessu/aprs-symbols/tree/master/png + +To split in to individual files, using ImageMagick: + + convert aprs-symbols-24-0.png -crop 24x24 aprs-symbols-24-0-%02d.png + convert aprs-symbols-24-1.png -crop 24x24 aprs-symbols-24-1-%02d.png diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-00.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-00.png new file mode 100644 index 000000000..ba8651982 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-00.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-01.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-01.png new file mode 100644 index 000000000..735b857cf Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-01.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-02.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-02.png new file mode 100644 index 000000000..db2be5442 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-02.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-03.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-03.png new file mode 100644 index 000000000..3acaa3ab6 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-03.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-04.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-04.png new file mode 100644 index 000000000..069842bfd Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-04.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-05.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-05.png new file mode 100644 index 000000000..c01f35abd Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-05.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-06.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-06.png new file mode 100644 index 000000000..fc2be8b63 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-06.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-07.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-07.png new file mode 100644 index 000000000..b6c7335d9 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-07.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-08.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-08.png new file mode 100644 index 000000000..2b67505fa Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-08.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-09.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-09.png new file mode 100644 index 000000000..4d6e210a7 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-09.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-10.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-10.png new file mode 100644 index 000000000..a442c3c91 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-10.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-11.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-11.png new file mode 100644 index 000000000..5882e2102 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-11.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-12.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-12.png new file mode 100644 index 000000000..7a8826f7f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-12.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-13.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-13.png new file mode 100644 index 000000000..f1cc2c93b Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-13.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-14.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-14.png new file mode 100644 index 000000000..2e21d9088 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-14.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-15.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-15.png new file mode 100644 index 000000000..64dc61e45 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-15.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-16.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-16.png new file mode 100644 index 000000000..c1f58c656 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-16.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-17.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-17.png new file mode 100644 index 000000000..43b92dae5 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-17.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-18.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-18.png new file mode 100644 index 000000000..0657289e8 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-18.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-19.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-19.png new file mode 100644 index 000000000..90b2751d1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-19.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-20.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-20.png new file mode 100644 index 000000000..71bf45363 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-20.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-21.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-21.png new file mode 100644 index 000000000..57c46021a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-21.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-22.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-22.png new file mode 100644 index 000000000..06adffd0a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-22.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-23.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-23.png new file mode 100644 index 000000000..9b48f7513 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-23.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-24.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-24.png new file mode 100644 index 000000000..3c6d6f1d4 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-24.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-25.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-25.png new file mode 100644 index 000000000..ec1c55c56 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-25.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-26.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-26.png new file mode 100644 index 000000000..a5aedfc1d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-26.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-27.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-27.png new file mode 100644 index 000000000..a6ed7dd07 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-27.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-28.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-28.png new file mode 100644 index 000000000..dd7123d65 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-28.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-29.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-29.png new file mode 100644 index 000000000..b39cc6f61 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-29.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-30.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-30.png new file mode 100644 index 000000000..14da48e04 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-30.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-31.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-31.png new file mode 100644 index 000000000..c49ece344 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-31.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-32.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-32.png new file mode 100644 index 000000000..6f5d9ce72 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-32.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-33.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-33.png new file mode 100644 index 000000000..46d764cc5 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-33.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-34.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-34.png new file mode 100644 index 000000000..f5315545c Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-34.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-35.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-35.png new file mode 100644 index 000000000..287674ad1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-35.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-36.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-36.png new file mode 100644 index 000000000..d82122c75 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-36.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-37.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-37.png new file mode 100644 index 000000000..e7442d7ab Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-37.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-38.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-38.png new file mode 100644 index 000000000..dd820d34e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-38.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-39.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-39.png new file mode 100644 index 000000000..82d90a9a7 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-39.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-40.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-40.png new file mode 100644 index 000000000..7afd9dcd6 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-40.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-41.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-41.png new file mode 100644 index 000000000..3ff2fb09d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-41.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-42.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-42.png new file mode 100644 index 000000000..2db826329 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-42.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-43.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-43.png new file mode 100644 index 000000000..2a0d6c51d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-43.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-44.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-44.png new file mode 100644 index 000000000..5398c3fad Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-44.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-45.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-45.png new file mode 100644 index 000000000..5f799ad20 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-45.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-46.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-46.png new file mode 100644 index 000000000..b653bc526 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-46.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-47.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-47.png new file mode 100644 index 000000000..01a0e0c69 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-47.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-48.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-48.png new file mode 100644 index 000000000..776acf044 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-48.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-49.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-49.png new file mode 100644 index 000000000..b96ebd101 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-49.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-50.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-50.png new file mode 100644 index 000000000..3bdcf7fad Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-50.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-51.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-51.png new file mode 100644 index 000000000..82d30e786 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-51.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-52.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-52.png new file mode 100644 index 000000000..efc51b756 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-52.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-53.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-53.png new file mode 100644 index 000000000..c60350892 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-53.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-54.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-54.png new file mode 100644 index 000000000..b731bfc06 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-54.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-55.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-55.png new file mode 100644 index 000000000..82ec76c38 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-55.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-56.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-56.png new file mode 100644 index 000000000..a9f2bfe8b Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-56.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-57.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-57.png new file mode 100644 index 000000000..2c8b77be9 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-57.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-58.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-58.png new file mode 100644 index 000000000..58b229ccf Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-58.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-59.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-59.png new file mode 100644 index 000000000..ec63d7394 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-59.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-60.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-60.png new file mode 100644 index 000000000..8c0475f17 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-60.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-61.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-61.png new file mode 100644 index 000000000..e21c1e990 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-61.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-62.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-62.png new file mode 100644 index 000000000..f15491e46 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-62.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-63.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-63.png new file mode 100644 index 000000000..98454335a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-63.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-64.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-64.png new file mode 100644 index 000000000..79cb8f25a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-64.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-65.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-65.png new file mode 100644 index 000000000..72edab89f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-65.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-66.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-66.png new file mode 100644 index 000000000..3ed0c63cd Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-66.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-67.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-67.png new file mode 100644 index 000000000..d8dcdc891 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-67.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-68.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-68.png new file mode 100644 index 000000000..6e9c27f9c Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-68.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-69.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-69.png new file mode 100644 index 000000000..813ed0556 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-69.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-70.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-70.png new file mode 100644 index 000000000..94f7e4a85 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-70.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-71.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-71.png new file mode 100644 index 000000000..46bf710ae Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-71.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-72.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-72.png new file mode 100644 index 000000000..8b395ce00 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-72.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-73.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-73.png new file mode 100644 index 000000000..7985337fc Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-73.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-74.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-74.png new file mode 100644 index 000000000..e4956530a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-74.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-75.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-75.png new file mode 100644 index 000000000..ba584a0fc Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-75.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-76.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-76.png new file mode 100644 index 000000000..9a5b1939f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-76.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-77.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-77.png new file mode 100644 index 000000000..a67ab9979 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-77.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-78.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-78.png new file mode 100644 index 000000000..b95533ffd Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-78.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-79.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-79.png new file mode 100644 index 000000000..d62722509 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-79.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-80.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-80.png new file mode 100644 index 000000000..e3490311a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-80.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-81.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-81.png new file mode 100644 index 000000000..1d45164c0 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-81.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-82.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-82.png new file mode 100644 index 000000000..465bcb3b1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-82.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-83.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-83.png new file mode 100644 index 000000000..9054dc5ce Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-83.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-84.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-84.png new file mode 100644 index 000000000..c12e649e3 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-84.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-85.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-85.png new file mode 100644 index 000000000..8593989da Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-85.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-86.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-86.png new file mode 100644 index 000000000..5d263a734 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-86.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-87.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-87.png new file mode 100644 index 000000000..495e3c52d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-87.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-88.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-88.png new file mode 100644 index 000000000..8575c84e8 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-88.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-89.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-89.png new file mode 100644 index 000000000..983a9f9c9 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-89.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-90.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-90.png new file mode 100644 index 000000000..c1cee7315 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-90.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-91.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-91.png new file mode 100644 index 000000000..17c34921e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-91.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-92.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-92.png new file mode 100644 index 000000000..daef46fb9 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-92.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-93.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-93.png new file mode 100644 index 000000000..0e6198dc5 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-93.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-94.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-94.png new file mode 100644 index 000000000..38c3807bc Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-94.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0-95.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0-95.png new file mode 100644 index 000000000..720692a06 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0-95.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-0.png b/plugins/feature/aprs/aprs/aprs-symbols-24-0.png new file mode 100644 index 000000000..92bf11472 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-0.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-00.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-00.png new file mode 100644 index 000000000..48dbf6885 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-00.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-01.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-01.png new file mode 100644 index 000000000..aa021b995 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-01.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-02.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-02.png new file mode 100644 index 000000000..bf282d76d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-02.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-03.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-03.png new file mode 100644 index 000000000..c7eeb7978 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-03.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-04.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-04.png new file mode 100644 index 000000000..901555085 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-04.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-05.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-05.png new file mode 100644 index 000000000..79516ae45 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-05.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-06.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-06.png new file mode 100644 index 000000000..183866171 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-06.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-07.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-07.png new file mode 100644 index 000000000..61b5fe155 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-07.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-08.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-08.png new file mode 100644 index 000000000..3ceb7afdb Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-08.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-09.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-09.png new file mode 100644 index 000000000..3e2d0876f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-09.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-10.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-10.png new file mode 100644 index 000000000..a275b4fa9 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-10.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-11.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-11.png new file mode 100644 index 000000000..41ea2ff1a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-11.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-12.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-12.png new file mode 100644 index 000000000..3180294d7 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-12.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-13.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-13.png new file mode 100644 index 000000000..0ff1d6b3b Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-13.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-14.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-14.png new file mode 100644 index 000000000..859172d78 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-14.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-15.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-15.png new file mode 100644 index 000000000..3de0c5f1a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-15.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-16.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-16.png new file mode 100644 index 000000000..ff8ba9e33 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-16.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-17.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-17.png new file mode 100644 index 000000000..7afb5fb78 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-17.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-18.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-18.png new file mode 100644 index 000000000..f1408e8c2 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-18.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-19.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-19.png new file mode 100644 index 000000000..9544e474e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-19.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-20.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-20.png new file mode 100644 index 000000000..5e46ed0f8 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-20.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-21.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-21.png new file mode 100644 index 000000000..48a4f732d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-21.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-22.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-22.png new file mode 100644 index 000000000..be1aaf802 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-22.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-23.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-23.png new file mode 100644 index 000000000..3ce6fc1df Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-23.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-24.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-24.png new file mode 100644 index 000000000..8824dd5ad Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-24.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-25.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-25.png new file mode 100644 index 000000000..415e56f0b Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-25.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-26.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-26.png new file mode 100644 index 000000000..e917eac4a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-26.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-27.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-27.png new file mode 100644 index 000000000..d1bc4dc75 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-27.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-28.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-28.png new file mode 100644 index 000000000..35728e2a7 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-28.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-29.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-29.png new file mode 100644 index 000000000..85a00aaec Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-29.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-30.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-30.png new file mode 100644 index 000000000..00704a1b2 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-30.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-31.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-31.png new file mode 100644 index 000000000..267488969 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-31.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-32.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-32.png new file mode 100644 index 000000000..6862fec11 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-32.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-33.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-33.png new file mode 100644 index 000000000..52ad64a16 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-33.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-34.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-34.png new file mode 100644 index 000000000..1aaec30b6 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-34.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-35.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-35.png new file mode 100644 index 000000000..fcc4dc389 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-35.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-36.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-36.png new file mode 100644 index 000000000..4c01a117f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-36.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-37.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-37.png new file mode 100644 index 000000000..0ce531a8a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-37.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-38.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-38.png new file mode 100644 index 000000000..7b313423a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-38.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-39.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-39.png new file mode 100644 index 000000000..896c61554 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-39.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-40.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-40.png new file mode 100644 index 000000000..37aa6b8c6 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-40.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-41.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-41.png new file mode 100644 index 000000000..19a1c226e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-41.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-42.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-42.png new file mode 100644 index 000000000..486917a8a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-42.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-43.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-43.png new file mode 100644 index 000000000..b15ac8329 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-43.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-44.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-44.png new file mode 100644 index 000000000..04cfadd13 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-44.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-45.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-45.png new file mode 100644 index 000000000..fb1ca5d87 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-45.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-46.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-46.png new file mode 100644 index 000000000..7303b8f5e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-46.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-47.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-47.png new file mode 100644 index 000000000..b04b8892d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-47.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-48.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-48.png new file mode 100644 index 000000000..dbece4dc1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-48.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-49.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-49.png new file mode 100644 index 000000000..7610e0bee Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-49.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-50.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-50.png new file mode 100644 index 000000000..146ca7c12 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-50.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-51.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-51.png new file mode 100644 index 000000000..343ba0194 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-51.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-52.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-52.png new file mode 100644 index 000000000..082470851 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-52.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-53.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-53.png new file mode 100644 index 000000000..aedfb1f39 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-53.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-54.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-54.png new file mode 100644 index 000000000..a781bf02f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-54.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-55.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-55.png new file mode 100644 index 000000000..f27359f7a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-55.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-56.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-56.png new file mode 100644 index 000000000..16b2c98c1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-56.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-57.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-57.png new file mode 100644 index 000000000..049cd0399 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-57.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-58.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-58.png new file mode 100644 index 000000000..8a2bda7e1 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-58.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-59.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-59.png new file mode 100644 index 000000000..0ff38f59a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-59.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-60.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-60.png new file mode 100644 index 000000000..44829e453 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-60.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-61.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-61.png new file mode 100644 index 000000000..4466b5e07 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-61.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-62.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-62.png new file mode 100644 index 000000000..3c3144d1d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-62.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-63.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-63.png new file mode 100644 index 000000000..69087488c Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-63.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-64.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-64.png new file mode 100644 index 000000000..d3001a1fe Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-64.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-65.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-65.png new file mode 100644 index 000000000..340c1806d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-65.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-66.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-66.png new file mode 100644 index 000000000..2f8909098 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-66.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-67.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-67.png new file mode 100644 index 000000000..20b3dac99 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-67.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-68.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-68.png new file mode 100644 index 000000000..e8e425e9c Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-68.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-69.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-69.png new file mode 100644 index 000000000..1162e9979 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-69.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-70.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-70.png new file mode 100644 index 000000000..dff30cfbd Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-70.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-71.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-71.png new file mode 100644 index 000000000..25c500a08 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-71.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-72.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-72.png new file mode 100644 index 000000000..fecc28648 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-72.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-73.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-73.png new file mode 100644 index 000000000..cc7241976 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-73.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-74.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-74.png new file mode 100644 index 000000000..8800b9f0a Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-74.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-75.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-75.png new file mode 100644 index 000000000..61dd87718 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-75.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-76.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-76.png new file mode 100644 index 000000000..5e2448a80 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-76.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-77.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-77.png new file mode 100644 index 000000000..e03e1d410 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-77.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-78.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-78.png new file mode 100644 index 000000000..be13c4372 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-78.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-79.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-79.png new file mode 100644 index 000000000..893bf2b8e Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-79.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-80.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-80.png new file mode 100644 index 000000000..012f16610 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-80.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-81.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-81.png new file mode 100644 index 000000000..2d77fbf63 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-81.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-82.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-82.png new file mode 100644 index 000000000..d9b112191 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-82.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-83.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-83.png new file mode 100644 index 000000000..f2752c9a2 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-83.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-84.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-84.png new file mode 100644 index 000000000..a99f984cb Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-84.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-85.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-85.png new file mode 100644 index 000000000..7a7548211 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-85.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-86.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-86.png new file mode 100644 index 000000000..4d37f1c9d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-86.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-87.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-87.png new file mode 100644 index 000000000..a739eb2d4 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-87.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-88.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-88.png new file mode 100644 index 000000000..45fd70a95 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-88.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-89.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-89.png new file mode 100644 index 000000000..75aa62616 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-89.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-90.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-90.png new file mode 100644 index 000000000..c9886d5fb Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-90.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-91.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-91.png new file mode 100644 index 000000000..17ab131d7 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-91.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-92.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-92.png new file mode 100644 index 000000000..9fd8b494f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-92.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-93.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-93.png new file mode 100644 index 000000000..404040c4d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-93.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-94.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-94.png new file mode 100644 index 000000000..7a0cc264d Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-94.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1-95.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1-95.png new file mode 100644 index 000000000..a257b9c62 Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1-95.png differ diff --git a/plugins/feature/aprs/aprs/aprs-symbols-24-1.png b/plugins/feature/aprs/aprs/aprs-symbols-24-1.png new file mode 100644 index 000000000..b83d4b14f Binary files /dev/null and b/plugins/feature/aprs/aprs/aprs-symbols-24-1.png differ diff --git a/plugins/feature/aprs/aprsgui.cpp b/plugins/feature/aprs/aprsgui.cpp new file mode 100644 index 000000000..4e7326178 --- /dev/null +++ b/plugins/feature/aprs/aprsgui.cpp @@ -0,0 +1,1934 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "SWGMapItem.h" + +#include "feature/featureuiset.h" +#include "feature/featurewebapiutils.h" +#include "gui/basicfeaturesettingsdialog.h" +#include "mainwindow.h" +#include "maincore.h" +#include "device/deviceuiset.h" + +#include "ui_aprsgui.h" +#include "aprs.h" +#include "aprsgui.h" +#include "aprssettingsdialog.h" + +#define PACKETS_COL_DATE 0 +#define PACKETS_COL_TIME 1 +#define PACKETS_COL_FROM 2 +#define PACKETS_COL_TO 3 +#define PACKETS_COL_VIA 4 +#define PACKETS_COL_DATA 5 + +#define WEATHER_COL_DATE 0 +#define WEATHER_COL_TIME 1 +#define WEATHER_COL_WIND_DIRECTION 2 +#define WEATHER_COL_WIND_SPEED 3 +#define WEATHER_COL_GUSTS 4 +#define WEATHER_COL_TEMPERATURE 5 +#define WEATHER_COL_HUMIDITY 6 +#define WEATHER_COL_PRESSURE 7 +#define WEATHER_COL_RAIN_LAST_HOUR 8 +#define WEATHER_COL_RAIN_LAST_24_HOURS 9 +#define WEATHER_COL_RAIN_SINCE_MIDNIGHT 10 +#define WEATHER_COL_LUMINOSITY 11 +#define WEATHER_COL_SNOWFALL 12 +#define WEATHER_COL_RADIATION_LEVEL 13 +#define WEATHER_COL_FLOOD_LEVEL 14 + +#define STATUS_COL_DATE 0 +#define STATUS_COL_TIME 1 +#define STATUS_COL_STATUS 2 +#define STATUS_COL_SYMBOL 3 +#define STATUS_COL_MAIDENHEAD 4 +#define STATUS_COL_BEAM_HEADING 5 +#define STATUS_COL_BEAM_POWER 6 + +#define MESSAGE_COL_DATE 0 +#define MESSAGE_COL_TIME 1 +#define MESSAGE_COL_ADDRESSEE 2 +#define MESSAGE_COL_MESSAGE 3 +#define MESSAGE_COL_NO 4 + +#define TELEMETRY_COL_DATE 0 +#define TELEMETRY_COL_TIME 1 +#define TELEMETRY_COL_SEQ_NO 2 +#define TELEMETRY_COL_A1 3 +#define TELEMETRY_COL_A2 4 +#define TELEMETRY_COL_A3 5 +#define TELEMETRY_COL_A4 6 +#define TELEMETRY_COL_A5 7 +#define TELEMETRY_COL_B1 8 +#define TELEMETRY_COL_B2 9 +#define TELEMETRY_COL_B3 10 +#define TELEMETRY_COL_B4 11 +#define TELEMETRY_COL_B5 12 +#define TELEMETRY_COL_B6 13 +#define TELEMETRY_COL_B7 14 +#define TELEMETRY_COL_B8 15 +#define TELEMETRY_COL_COMMENT 16 + +#define MOTION_COL_DATE 0 +#define MOTION_COL_TIME 1 +#define MOTION_COL_LATITUDE 2 +#define MOTION_COL_LONGITUDE 3 +#define MOTION_COL_ALTITUDE 4 +#define MOTION_COL_COURSE 5 +#define MOTION_COL_SPEED 6 + +APRSGUI* APRSGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) +{ + APRSGUI* gui = new APRSGUI(pluginAPI, featureUISet, feature); + return gui; +} + +void APRSGUI::destroy() +{ + delete this; +} + +void APRSGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray APRSGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool APRSGUI::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + displaySettings(); + applySettings(true); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +bool APRSGUI::handleMessage(const Message& message) +{ + if (APRS::MsgConfigureAPRS::match(message)) + { + qDebug("APRSGUI::handleMessage: APRS::MsgConfigureAPRS"); + const APRS::MsgConfigureAPRS& cfg = (APRS::MsgConfigureAPRS&) message; + m_settings = cfg.getSettings(); + qDebug() << m_settings.m_igateCallsign; + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (PipeEndPoint::MsgReportPipes::match(message)) + { + PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message; + m_availablePipes = report.getAvailablePipes(); + updatePipeList(); + + return true; + } + else if (MainCore::MsgPacket::match(message)) + { + MainCore::MsgPacket& report = (MainCore::MsgPacket&) message; + AX25Packet ax25; + APRSPacket *aprs = new APRSPacket(); + if (ax25.decode(report.getPacket())) + { + if (aprs->decode(ax25)) + { + aprs->m_dateTime = report.getDateTime(); + + APRSStation *station; + bool addToStationSel = false; + + // Is packet for an existing object or station? + if (!aprs->m_objectName.isEmpty() && m_stations.contains(aprs->m_objectName)) + { + // Add packet to existing object + station = m_stations.value(aprs->m_objectName); + station->addPacket(aprs); + } + else if (!aprs->m_objectName.isEmpty()) + { + // Add new object + station = new APRSStation(aprs->m_objectName); + station->m_isObject = true; + station->m_reportingStation = aprs->m_from; + station->addPacket(aprs); + m_stations.insert(aprs->m_objectName, station); + addToStationSel = true; + } + else if (m_stations.contains(aprs->m_from)) + { + // Add packet to existing station + station = m_stations.value(aprs->m_from); + station->addPacket(aprs); + } + else + { + // Add new station + station = new APRSStation(aprs->m_from); + station->addPacket(aprs); + m_stations.insert(aprs->m_from, station); + addToStationSel = true; + } + + // Update station + if (aprs->m_hasSymbol) + station->m_symbolImage = aprs->m_symbolImage; + if (aprs->m_hasTimestamp) + station->m_latestPacket = aprs->dateTime(); + if (aprs->m_hasStatus) + station->m_latestStatus = aprs->m_status; + if (!aprs->m_comment.isEmpty()) + station->m_latestComment = aprs->m_comment; + if (aprs->m_hasPosition) + station->m_latestPosition = aprs->position(); + if (aprs->m_hasAltitude) + station->m_latestAltitude = QString("%1").arg(aprs->m_altitudeFt); + if (aprs->m_hasCourseAndSpeed) + { + station->m_latestCourse = QString("%1").arg(aprs->m_course); + station->m_latestSpeed = QString("%1").arg(aprs->m_speed); + } + if (aprs->m_hasStationDetails) + { + station->m_powerWatts = QString("%1").arg(aprs->m_powerWatts); + station->m_antennaHeightFt = QString("%1").arg(aprs->m_antennaHeightFt); + station->m_antennaGainDB = QString("%1").arg(aprs->m_antennaGainDB); + station->m_antennaDirectivity = aprs->m_antennaDirectivity; + } + if (aprs->m_hasRadioRange) + station->m_radioRange = QString("%1").arg(aprs->m_radioRangeMiles); + if (aprs->m_hasWeather) + station->m_hasWeather = true; + if (aprs->m_hasTelemetry) + station->m_hasTelemetry = true; + if (aprs->m_hasCourseAndSpeed) + station->m_hasCourseAndSpeed = true; + + // Update messages, which aren't station specific + if (aprs->m_hasMessage) + { + int row = ui->messagesTable->rowCount(); + ui->messagesTable->setRowCount(row + 1); + QTableWidgetItem *messageDateItem = new QTableWidgetItem(); + QTableWidgetItem *messageTimeItem = new QTableWidgetItem(); + QTableWidgetItem *messageAddresseeItem = new QTableWidgetItem(); + QTableWidgetItem *messageMessageItem = new QTableWidgetItem(); + QTableWidgetItem *messageNoItem = new QTableWidgetItem(); + ui->messagesTable->setItem(row, MESSAGE_COL_DATE, messageDateItem); + ui->messagesTable->setItem(row, MESSAGE_COL_TIME, messageTimeItem); + ui->messagesTable->setItem(row, MESSAGE_COL_ADDRESSEE, messageAddresseeItem); + ui->messagesTable->setItem(row, MESSAGE_COL_MESSAGE, messageMessageItem); + ui->messagesTable->setItem(row, MESSAGE_COL_NO, messageNoItem); + messageDateItem->setData(Qt::DisplayRole, formatDate(aprs)); + messageTimeItem->setData(Qt::DisplayRole, formatTime(aprs)); + messageAddresseeItem->setText(aprs->m_addressee); + messageMessageItem->setText(aprs->m_message); + messageNoItem->setText(aprs->m_messageNo); + + // Process telemetry messages + if ((aprs->m_telemetryNames.size() > 0) || (aprs->m_telemetryLabels.size() > 0) || aprs->m_hasTelemetryCoefficients || aprs->m_hasTelemetryBitSense) + { + APRSStation *telemetryStation; + if (m_stations.contains(aprs->m_addressee)) + telemetryStation = m_stations.value(aprs->m_addressee); + else + { + telemetryStation = new APRSStation(aprs->m_addressee); + m_stations.insert(aprs->m_addressee, telemetryStation); + } + if (aprs->m_telemetryNames.size() > 0) + { + telemetryStation->m_telemetryNames = aprs->m_telemetryNames; + for (int i = 0; i < aprs->m_telemetryNames.size(); i++) + ui->telemetryPlotSelect->setItemText(i, aprs->m_telemetryNames[i]); + } + else + telemetryStation->m_telemetryLabels = aprs->m_telemetryLabels; + if (aprs->m_hasTelemetryCoefficients > 0) + { + for (int j = 0; j < 5; j++) + { + telemetryStation->m_telemetryCoefficientsA[j] = aprs->m_telemetryCoefficientsA[j]; + telemetryStation->m_telemetryCoefficientsB[j] = aprs->m_telemetryCoefficientsB[j]; + telemetryStation->m_telemetryCoefficientsC[j] = aprs->m_telemetryCoefficientsC[j]; + } + telemetryStation->m_hasTelemetryCoefficients = aprs->m_hasTelemetryCoefficients; + } + if (aprs->m_hasTelemetryBitSense) + { + for (int j = 0; j < 8; j++) + telemetryStation->m_telemetryBitSense[j] = aprs->m_telemetryBitSense[j]; + telemetryStation->m_hasTelemetryBitSense = true; + telemetryStation->m_telemetryProjectName = aprs->m_telemetryProjectName; + } + if (ui->stationSelect->currentText() == aprs->m_addressee) + { + for (int i = 0; i < station->m_telemetryNames.size(); i++) + { + QString header; + if (station->m_telemetryLabels.size() <= i) + header = station->m_telemetryNames[i]; + else + header = QString("%1 (%2)").arg(station->m_telemetryNames[i]).arg(station->m_telemetryLabels[i]); + ui->telemetryTable->horizontalHeaderItem(TELEMETRY_COL_A1+i)->setText(header); + } + } + } + } + + if (addToStationSel) + { + if (!filterStation(station)) + ui->stationSelect->addItem(station->m_station); + } + + // Refresh GUI if currently selected station + if (ui->stationSelect->currentText() == aprs->m_from) + { + updateSummary(station); + addPacketToGUI(station, aprs); + if (aprs->m_hasWeather) + plotWeather(); + if (aprs->m_hasTelemetry) + plotTelemetry(); + if (aprs->m_hasPosition || aprs->m_hasAltitude || aprs->m_hasCourseAndSpeed) + plotMotion(); + } + + // Forward to map + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_aprs, "mapitems"); + if (mapMessageQueues) + { + if (aprs->m_hasPosition && (aprs->m_from != "")) + { + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + if (!aprs->m_objectName.isEmpty()) + swgMapItem->setName(new QString(aprs->m_objectName)); + else + swgMapItem->setName(new QString(aprs->m_from)); + swgMapItem->setLatitude(aprs->m_latitude); + swgMapItem->setLongitude(aprs->m_longitude); + if (aprs->m_objectKilled) + { + swgMapItem->setImage(new QString("")); + swgMapItem->setText(new QString("")); + } + else + { + swgMapItem->setImage(new QString(QString("qrc:///%1").arg(aprs->m_symbolImage))); + swgMapItem->setText(new QString(aprs->toText())); + } + swgMapItem->setImageFixedSize(0); + + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aprs, swgMapItem); + (*it)->push(msg); + } + } + } + + } + else + { + qDebug() << "APRSGUI::handleMessage: Failed to decode as APRS"; + qDebug() << ax25.m_from << " " << ax25.m_to << " " << ax25.m_via << " " << ax25.m_type << " " << ax25.m_pid << " "<< ax25.m_dataASCII; + } + } + else + qDebug() << "APRSGUI::handleMessage: Failed to decode as AX.25"; + return true; + } + + return false; +} + +void APRSGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop())) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +void APRSGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +APRSGUI::APRSGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) : + FeatureGUI(parent), + ui(new Ui::APRSGUI), + m_pluginAPI(pluginAPI), + m_featureUISet(featureUISet), + m_doApplySettings(true), + m_lastFeatureState(0) +{ + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose, true); + setChannelWidget(false); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + m_aprs = reinterpret_cast(feature); + m_aprs->setMessageQueueToGUI(&m_inputMessageQueue); + + m_featureUISet->addRollupWidget(this); + + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(1000); + + // Resize the table using dummy data + resizeTable(); + // Allow user to reorder columns + ui->weatherTable->horizontalHeader()->setSectionsMovable(true); + ui->packetsTable->horizontalHeader()->setSectionsMovable(true); + ui->statusTable->horizontalHeader()->setSectionsMovable(true); + ui->messagesTable->horizontalHeader()->setSectionsMovable(true); + ui->telemetryTable->horizontalHeader()->setSectionsMovable(true); + ui->motionTable->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->weatherTable->setSortingEnabled(true); + ui->packetsTable->setSortingEnabled(true); + ui->statusTable->setSortingEnabled(true); + ui->messagesTable->setSortingEnabled(true); + ui->telemetryTable->setSortingEnabled(true); + ui->motionTable->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + packetsTableMenu = new QMenu(ui->packetsTable); + for (int i = 0; i < ui->packetsTable->horizontalHeader()->count(); i++) + { + QString text = ui->packetsTable->horizontalHeaderItem(i)->text(); + packetsTableMenu->addAction(packetsTable_createCheckableItem(text, i, true)); + } + ui->packetsTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->packetsTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(packetsTable_columnSelectMenu(QPoint))); + + weatherTableMenu = new QMenu(ui->weatherTable); + for (int i = 0; i < ui->weatherTable->horizontalHeader()->count(); i++) + { + QString text = ui->weatherTable->horizontalHeaderItem(i)->text(); + weatherTableMenu->addAction(weatherTable_createCheckableItem(text, i, true)); + } + ui->weatherTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->weatherTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(weatherTable_columnSelectMenu(QPoint))); + + statusTableMenu = new QMenu(ui->statusTable); + for (int i = 0; i < ui->statusTable->horizontalHeader()->count(); i++) + { + QString text = ui->statusTable->horizontalHeaderItem(i)->text(); + statusTableMenu->addAction(statusTable_createCheckableItem(text, i, true)); + } + ui->statusTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->statusTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(statusTable_columnSelectMenu(QPoint))); + + messagesTableMenu = new QMenu(ui->messagesTable); + for (int i = 0; i < ui->messagesTable->horizontalHeader()->count(); i++) + { + QString text = ui->messagesTable->horizontalHeaderItem(i)->text(); + messagesTableMenu->addAction(messagesTable_createCheckableItem(text, i, true)); + } + ui->messagesTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->messagesTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(messagesTable_columnSelectMenu(QPoint))); + + telemetryTableMenu = new QMenu(ui->telemetryTable); + for (int i = 0; i < ui->telemetryTable->horizontalHeader()->count(); i++) + { + QString text = ui->telemetryTable->horizontalHeaderItem(i)->text(); + telemetryTableMenu->addAction(telemetryTable_createCheckableItem(text, i, true)); + } + ui->telemetryTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->telemetryTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(telemetryTable_columnSelectMenu(QPoint))); + + motionTableMenu = new QMenu(ui->motionTable); + for (int i = 0; i < ui->motionTable->horizontalHeader()->count(); i++) + { + QString text = ui->motionTable->horizontalHeaderItem(i)->text(); + motionTableMenu->addAction(motionTable_createCheckableItem(text, i, true)); + } + ui->motionTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->motionTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(motionTable_columnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->packetsTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(packetsTable_sectionMoved(int, int, int))); + connect(ui->packetsTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(packetsTable_sectionResized(int, int, int))); + connect(ui->weatherTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(weatherTable_sectionMoved(int, int, int))); + connect(ui->weatherTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(weatherTable_sectionResized(int, int, int))); + connect(ui->statusTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(statusTable_sectionMoved(int, int, int))); + connect(ui->statusTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(statusTable_sectionResized(int, int, int))); + connect(ui->messagesTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(messagesTable_sectionMoved(int, int, int))); + connect(ui->messagesTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(messagesTable_sectionResized(int, int, int))); + connect(ui->telemetryTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(telemetryTable_sectionMoved(int, int, int))); + connect(ui->telemetryTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(telemetryTable_sectionResized(int, int, int))); + connect(ui->motionTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(motionTable_sectionMoved(int, int, int))); + connect(ui->motionTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(motionTable_sectionResized(int, int, int))); + + m_weatherChart.legend()->hide(); + ui->weatherChart->setChart(&m_weatherChart); + ui->weatherChart->setRenderHint(QPainter::Antialiasing); + m_weatherChart.addAxis(&m_weatherChartXAxis, Qt::AlignBottom); + m_weatherChart.addAxis(&m_weatherChartYAxis, Qt::AlignLeft); + + m_telemetryChart.legend()->hide(); + ui->telemetryChart->setChart(&m_telemetryChart); + ui->telemetryChart->setRenderHint(QPainter::Antialiasing); + m_telemetryChart.addAxis(&m_telemetryChartXAxis, Qt::AlignBottom); + m_telemetryChart.addAxis(&m_telemetryChartYAxis, Qt::AlignLeft); + + m_motionChart.legend()->hide(); + ui->motionChart->setChart(&m_motionChart); + ui->motionChart->setRenderHint(QPainter::Antialiasing); + m_motionChart.addAxis(&m_motionChartXAxis, Qt::AlignBottom); + m_motionChart.addAxis(&m_motionChartYAxis, Qt::AlignLeft); + + displaySettings(); + applySettings(true); +} + +APRSGUI::~APRSGUI() +{ + delete ui; +} + +void APRSGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +bool APRSGUI::filterStation(APRSStation *station) +{ + switch (m_settings.m_stationFilter) + { + case APRSSettings::ALL: + return false; + case APRSSettings::STATIONS: + return station->m_isObject; + case APRSSettings::OBJECTS: + return !station->m_isObject; + case APRSSettings::WEATHER: + return !station->m_hasWeather; + case APRSSettings::TELEMETRY: + return !station->m_hasTelemetry; + case APRSSettings::COURSE_AND_SPEED: + return !station->m_hasCourseAndSpeed; + } + return false; +} + +void APRSGUI::filterStations() +{ + ui->stationSelect->clear(); + QHashIterator i(m_stations); + while (i.hasNext()) + { + i.next(); + APRSStation *station = i.value(); + if (!filterStation(station)) + { + ui->stationSelect->addItem(station->m_station); + } + } +} + +void APRSGUI::displayTableSettings(QTableWidget *table, QMenu *menu, int *columnSizes, int *columnIndexes, int columns) +{ + QHeaderView *header = table->horizontalHeader(); + for (int i = 0; i < columns; i++) + { + bool hidden = columnSizes[i] == 0; + header->setSectionHidden(i, hidden); + menu->actions().at(i)->setChecked(!hidden); + if (columnSizes[i] > 0) + table->setColumnWidth(i, columnSizes[i]); + header->moveSection(header->visualIndex(i), columnIndexes[i]); + } +} + +void APRSGUI::displaySettings() +{ + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_settings.m_title); + blockApplySettings(true); + ui->igate->setChecked(m_settings.m_igateEnabled); + ui->stationFilter->setCurrentIndex((int)m_settings.m_stationFilter); + ui->filterAddressee->setText(m_settings.m_filterAddressee); + + // Order and size columns + displayTableSettings(ui->packetsTable, packetsTableMenu, m_settings.m_packetsTableColumnSizes, m_settings.m_packetsTableColumnIndexes, APRS_PACKETS_TABLE_COLUMNS); + displayTableSettings(ui->weatherTable, weatherTableMenu, m_settings.m_weatherTableColumnSizes, m_settings.m_weatherTableColumnIndexes, APRS_WEATHER_TABLE_COLUMNS); + displayTableSettings(ui->statusTable, statusTableMenu, m_settings.m_statusTableColumnSizes, m_settings.m_statusTableColumnIndexes, APRS_STATUS_TABLE_COLUMNS); + displayTableSettings(ui->messagesTable, messagesTableMenu, m_settings.m_messagesTableColumnSizes, m_settings.m_messagesTableColumnIndexes, APRS_MESSAGES_TABLE_COLUMNS); + displayTableSettings(ui->telemetryTable, telemetryTableMenu, m_settings.m_telemetryTableColumnSizes, m_settings.m_telemetryTableColumnIndexes, APRS_TELEMETRY_TABLE_COLUMNS); + displayTableSettings(ui->motionTable, motionTableMenu, m_settings.m_motionTableColumnSizes, m_settings.m_motionTableColumnIndexes, APRS_MOTION_TABLE_COLUMNS); + + blockApplySettings(false); +} + +void APRSGUI::updatePipeList() +{ + ui->sourcePipes->blockSignals(true); + ui->sourcePipes->clear(); + QList::const_iterator it = m_availablePipes.begin(); + + for (int i = 0; it != m_availablePipes.end(); ++it, i++) + { + ui->sourcePipes->addItem(it->getName()); + } + + ui->sourcePipes->blockSignals(false); +} + +void APRSGUI::leaveEvent(QEvent*) +{ +} + +void APRSGUI::enterEvent(QEvent*) +{ +} + +void APRSGUI::resizeEvent(QResizeEvent* size) +{ + // Replot graphs to ensure Axis are visible + plotWeather(); + plotTelemetry(); + plotMotion(); + FeatureGUI::resizeEvent(size); +} + +void APRSGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicFeatureSettingsDialog dialog(this); + dialog.setTitle(m_settings.m_title); + dialog.setColor(m_settings.m_rgbColor); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex); + dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = dialog.getColor().rgb(); + m_settings.m_title = dialog.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex(); + m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + + resetContextMenuType(); +} + +void APRSGUI::updateSummary(APRSStation *station) +{ + ui->station->setText(station->m_station); + ui->reportingStation->setText(station->m_reportingStation); + ui->symbol->setPixmap(QPixmap(QString(":%1").arg(station->m_symbolImage))); + ui->status->setText(station->m_latestStatus); + ui->comment->setText(station->m_latestComment); + ui->position->setText(station->m_latestPosition); + ui->altitude->setText(station->m_latestAltitude); + ui->course->setText(station->m_latestCourse); + ui->speed->setText(station->m_latestSpeed); + ui->txPower->setText(station->m_powerWatts); + ui->antennaHeight->setText(station->m_antennaHeightFt); + ui->antennaGain->setText(station->m_antennaGainDB); + ui->antennaDirectivity->setText(station->m_antennaDirectivity); + ui->radioRange->setText(station->m_radioRange); + if (!station->m_packets.isEmpty()) + ui->lastPacket->setText(station->m_packets.last()->m_dateTime.toString()); + else + ui->lastPacket->setText(""); +} + +QString APRSGUI::formatDate(APRSPacket *aprs) +{ + if (aprs->m_hasTimestamp) + return aprs->date(); + else + return aprs->m_dateTime.date().toString("yyyy/MM/dd"); +} + +QString APRSGUI::formatTime(APRSPacket *aprs) +{ + // Add suffix T to indicate timestamp used + if (aprs->m_hasTimestamp) + return QString("%1 T").arg(aprs->time()); + else + return aprs->m_dateTime.time().toString("hh:mm:ss"); +} + +double APRSGUI::applyCoefficients(int idx, int value, APRSStation *station) +{ + if (station->m_hasTelemetryCoefficients > idx) + return station->m_telemetryCoefficientsA[idx] * value * value + station->m_telemetryCoefficientsB[idx] * value + station->m_telemetryCoefficientsC[idx]; + else + return (double)idx; +} + +void APRSGUI::addPacketToGUI(APRSStation *station, APRSPacket *aprs) +{ + int row; + // Weather table + if (aprs->m_hasWeather) + { + row = ui->weatherTable->rowCount(); + ui->weatherTable->setRowCount(row + 1); + QTableWidgetItem *weatherDateItem = new QTableWidgetItem(); + QTableWidgetItem *weatherTimeItem = new QTableWidgetItem(); + QTableWidgetItem *windDirectionItem = new QTableWidgetItem(); + QTableWidgetItem *windSpeedItem = new QTableWidgetItem(); + QTableWidgetItem *gustsItem = new QTableWidgetItem(); + QTableWidgetItem *temperatureItem = new QTableWidgetItem(); + QTableWidgetItem *humidityItem = new QTableWidgetItem(); + QTableWidgetItem *pressureItem = new QTableWidgetItem(); + QTableWidgetItem *rainLastHourItem = new QTableWidgetItem(); + QTableWidgetItem *rainLast24HoursItem = new QTableWidgetItem(); + QTableWidgetItem *rainSinceMidnightItem = new QTableWidgetItem(); + QTableWidgetItem *luminosityItem = new QTableWidgetItem(); + QTableWidgetItem *snowfallItem = new QTableWidgetItem(); + QTableWidgetItem *radiationLevelItem = new QTableWidgetItem(); + QTableWidgetItem *floodLevelItem = new QTableWidgetItem(); + ui->weatherTable->setItem(row, WEATHER_COL_DATE, weatherDateItem); + ui->weatherTable->setItem(row, WEATHER_COL_TIME, weatherTimeItem); + ui->weatherTable->setItem(row, WEATHER_COL_WIND_DIRECTION, windDirectionItem); + ui->weatherTable->setItem(row, WEATHER_COL_WIND_SPEED, windSpeedItem); + ui->weatherTable->setItem(row, WEATHER_COL_GUSTS, gustsItem); + ui->weatherTable->setItem(row, WEATHER_COL_TEMPERATURE, temperatureItem); + ui->weatherTable->setItem(row, WEATHER_COL_HUMIDITY, humidityItem); + ui->weatherTable->setItem(row, WEATHER_COL_PRESSURE, pressureItem); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_LAST_HOUR, rainLastHourItem); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_LAST_24_HOURS, rainLast24HoursItem); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_SINCE_MIDNIGHT, rainSinceMidnightItem); + ui->weatherTable->setItem(row, WEATHER_COL_LUMINOSITY, luminosityItem); + ui->weatherTable->setItem(row, WEATHER_COL_SNOWFALL, snowfallItem); + ui->weatherTable->setItem(row, WEATHER_COL_RADIATION_LEVEL, radiationLevelItem); + ui->weatherTable->setItem(row, WEATHER_COL_FLOOD_LEVEL, floodLevelItem); + weatherDateItem->setData(Qt::DisplayRole, formatDate(aprs)); + weatherTimeItem->setData(Qt::DisplayRole, formatTime(aprs)); + if (aprs->m_hasWindDirection) + windDirectionItem->setData(Qt::DisplayRole, aprs->m_windDirection); + if (aprs->m_hasWindSpeed) + windSpeedItem->setData(Qt::DisplayRole, aprs->m_windSpeed); + if (aprs->m_hasGust) + gustsItem->setData(Qt::DisplayRole, aprs->m_gust); + if (aprs->m_hasTemp) + temperatureItem->setData(Qt::DisplayRole, aprs->m_temp); + if (aprs->m_hasHumidity) + humidityItem->setData(Qt::DisplayRole, aprs->m_humidity); + if (aprs->m_hasBarometricPressure) + pressureItem->setData(Qt::DisplayRole, aprs->m_barometricPressure/10.0f); + if (aprs->m_hasRainLastHr) + rainLastHourItem->setData(Qt::DisplayRole, aprs->m_rainLastHr); + if (aprs->m_hasRainLast24Hrs) + rainLast24HoursItem->setData(Qt::DisplayRole, aprs->m_rainLast24Hrs); + if (aprs->m_hasRainSinceMidnight) + rainSinceMidnightItem->setData(Qt::DisplayRole, aprs->m_rainSinceMidnight); + if (aprs->m_hasLuminsoity) + luminosityItem->setData(Qt::DisplayRole, aprs->m_luminosity); + if (aprs->m_hasSnowfallLast24Hrs) + snowfallItem->setData(Qt::DisplayRole, aprs->m_snowfallLast24Hrs); + if (aprs->m_hasRadiationLevel) + radiationLevelItem->setData(Qt::DisplayRole, aprs->m_hasRadiationLevel); + if (aprs->m_hasFloodLevel) + floodLevelItem->setData(Qt::DisplayRole, aprs->m_floodLevel); + } + + // Status table + if (aprs->m_hasStatus) + { + row = ui->statusTable->rowCount(); + ui->statusTable->setRowCount(row + 1); + QTableWidgetItem *statusDateItem = new QTableWidgetItem(); + QTableWidgetItem *statusTimeItem = new QTableWidgetItem(); + QTableWidgetItem *statusItem = new QTableWidgetItem(); + QTableWidgetItem *statusSymbolItem = new QTableWidgetItem(); + QTableWidgetItem *statusMaidenheadItem = new QTableWidgetItem(); + QTableWidgetItem *statusBeamHeadingItem = new QTableWidgetItem(); + QTableWidgetItem *statusBeamPowerItem = new QTableWidgetItem(); + ui->statusTable->setItem(row, STATUS_COL_DATE, statusDateItem); + ui->statusTable->setItem(row, STATUS_COL_TIME, statusTimeItem); + ui->statusTable->setItem(row, STATUS_COL_STATUS, statusItem); + ui->statusTable->setItem(row, STATUS_COL_SYMBOL, statusSymbolItem); + ui->statusTable->setItem(row, STATUS_COL_MAIDENHEAD, statusMaidenheadItem); + ui->statusTable->setItem(row, STATUS_COL_BEAM_HEADING, statusBeamHeadingItem); + ui->statusTable->setItem(row, STATUS_COL_BEAM_POWER, statusBeamPowerItem); + statusDateItem->setData(Qt::DisplayRole, formatDate(aprs)); + statusTimeItem->setData(Qt::DisplayRole, formatTime(aprs)); + statusItem->setText(aprs->m_status); + if (aprs->m_hasSymbol) + { + statusSymbolItem->setSizeHint(QSize(24, 24)); + statusSymbolItem->setIcon(QIcon(QString(":%1").arg(station->m_symbolImage))); + } + statusMaidenheadItem->setText(aprs->m_maidenhead); + if (aprs->m_hasBeam) + { + statusBeamHeadingItem->setData(Qt::DisplayRole, aprs->m_beamHeading); + statusBeamPowerItem->setData(Qt::DisplayRole, aprs->m_beamPower); + } + } + + // Telemetry table + if (aprs->m_hasTelemetry) + { + row = ui->telemetryTable->rowCount(); + ui->telemetryTable->setRowCount(row + 1); + QTableWidgetItem *telemetryDateItem = new QTableWidgetItem(); + QTableWidgetItem *telemetryTimeItem = new QTableWidgetItem(); + QTableWidgetItem *telemetrySeqNoItem = new QTableWidgetItem(); + QTableWidgetItem *telemetryA1Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryA2Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryA3Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryA4Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryA5Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB1Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB2Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB3Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB4Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB5Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB6Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB7Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryB8Item = new QTableWidgetItem(); + QTableWidgetItem *telemetryCommentItem = new QTableWidgetItem(); + ui->telemetryTable->setItem(row, TELEMETRY_COL_DATE, telemetryDateItem); + ui->telemetryTable->setItem(row, TELEMETRY_COL_TIME, telemetryTimeItem); + ui->telemetryTable->setItem(row, TELEMETRY_COL_SEQ_NO, telemetrySeqNoItem); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A1, telemetryA1Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A2, telemetryA2Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A3, telemetryA3Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A4, telemetryA4Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A5, telemetryA5Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B1, telemetryB1Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B2, telemetryB2Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B3, telemetryB3Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B4, telemetryB4Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B5, telemetryB5Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B6, telemetryB6Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B7, telemetryB7Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B8, telemetryB8Item); + ui->telemetryTable->setItem(row, TELEMETRY_COL_COMMENT, telemetryCommentItem); + telemetryDateItem->setData(Qt::DisplayRole, formatDate(aprs)); + telemetryTimeItem->setData(Qt::DisplayRole, formatTime(aprs)); + if (aprs->m_hasSeqNo) + telemetrySeqNoItem->setData(Qt::DisplayRole, aprs->m_seqNo); + if (aprs->m_a1HasValue) + telemetryA1Item->setData(Qt::DisplayRole, applyCoefficients(0, aprs->m_a1, station)); + if (aprs->m_a2HasValue) + telemetryA2Item->setData(Qt::DisplayRole, applyCoefficients(1, aprs->m_a2, station)); + if (aprs->m_a3HasValue) + telemetryA3Item->setData(Qt::DisplayRole, applyCoefficients(2, aprs->m_a3, station)); + if (aprs->m_a4HasValue) + telemetryA4Item->setData(Qt::DisplayRole, applyCoefficients(3, aprs->m_a4, station)); + if (aprs->m_a5HasValue) + telemetryA5Item->setData(Qt::DisplayRole, applyCoefficients(4, aprs->m_a5, station)); + if (aprs->m_bHasValue) + { + telemetryB1Item->setData(Qt::DisplayRole, aprs->m_b[0] ? 1 : 0); + telemetryB2Item->setData(Qt::DisplayRole, aprs->m_b[1] ? 1 : 0); + telemetryB3Item->setData(Qt::DisplayRole, aprs->m_b[2] ? 1 : 0); + telemetryB4Item->setData(Qt::DisplayRole, aprs->m_b[3] ? 1 : 0); + telemetryB5Item->setData(Qt::DisplayRole, aprs->m_b[4] ? 1 : 0); + telemetryB6Item->setData(Qt::DisplayRole, aprs->m_b[5] ? 1 : 0); + telemetryB7Item->setData(Qt::DisplayRole, aprs->m_b[6] ? 1 : 0); + telemetryB8Item->setData(Qt::DisplayRole, aprs->m_b[7] ? 1 : 0); + } + telemetryCommentItem->setText(aprs->m_telemetryComment); + + for (int i = 0; i < station->m_telemetryNames.size(); i++) + { + QString header; + if (station->m_telemetryLabels.size() <= i) + header = station->m_telemetryNames[i]; + else + header = QString("%1 (%2)").arg(station->m_telemetryNames[i]).arg(station->m_telemetryLabels[i]); + ui->telemetryTable->horizontalHeaderItem(TELEMETRY_COL_A1+i)->setText(header); + } + } + + // Motion table + if (aprs->m_hasPosition || aprs->m_hasAltitude || aprs->m_hasCourseAndSpeed) + { + row = ui->motionTable->rowCount(); + ui->motionTable->setRowCount(row + 1); + QTableWidgetItem *motionDateItem = new QTableWidgetItem(); + QTableWidgetItem *motionTimeItem = new QTableWidgetItem(); + QTableWidgetItem *latitudeItem = new QTableWidgetItem(); + QTableWidgetItem *longitudeItem = new QTableWidgetItem(); + QTableWidgetItem *altitudeItem = new QTableWidgetItem(); + QTableWidgetItem *courseItem = new QTableWidgetItem(); + QTableWidgetItem *speedItem = new QTableWidgetItem(); + ui->motionTable->setItem(row, MOTION_COL_DATE, motionDateItem); + ui->motionTable->setItem(row, MOTION_COL_TIME, motionTimeItem); + ui->motionTable->setItem(row, MOTION_COL_LATITUDE, latitudeItem); + ui->motionTable->setItem(row, MOTION_COL_LONGITUDE, longitudeItem); + ui->motionTable->setItem(row, MOTION_COL_ALTITUDE, altitudeItem); + ui->motionTable->setItem(row, MOTION_COL_COURSE, courseItem); + ui->motionTable->setItem(row, MOTION_COL_SPEED, speedItem); + motionDateItem->setData(Qt::DisplayRole, formatDate(aprs)); + motionTimeItem->setData(Qt::DisplayRole, formatTime(aprs)); + if (aprs->m_hasPosition) + { + latitudeItem->setData(Qt::DisplayRole, aprs->m_latitude); + longitudeItem->setData(Qt::DisplayRole, aprs->m_longitude); + } + if (aprs->m_hasAltitude) + altitudeItem->setData(Qt::DisplayRole, aprs->m_altitudeFt); + if (aprs->m_hasCourseAndSpeed) + { + courseItem->setData(Qt::DisplayRole, aprs->m_course); + speedItem->setData(Qt::DisplayRole, aprs->m_speed); + } + } + + // Packets table + row = ui->packetsTable->rowCount(); + ui->packetsTable->setRowCount(row + 1); + QTableWidgetItem *dateItem = new QTableWidgetItem(); + QTableWidgetItem *timeItem = new QTableWidgetItem(); + QTableWidgetItem *fromItem = new QTableWidgetItem(); + QTableWidgetItem *toItem = new QTableWidgetItem(); + QTableWidgetItem *viaItem = new QTableWidgetItem(); + QTableWidgetItem *dataItem = new QTableWidgetItem(); + ui->packetsTable->setItem(row, PACKETS_COL_DATE, dateItem); + ui->packetsTable->setItem(row, PACKETS_COL_TIME, timeItem); + ui->packetsTable->setItem(row, PACKETS_COL_FROM, fromItem); + ui->packetsTable->setItem(row, PACKETS_COL_TO, toItem); + ui->packetsTable->setItem(row, PACKETS_COL_VIA, viaItem); + ui->packetsTable->setItem(row, PACKETS_COL_DATA, dataItem); + dateItem->setData(Qt::DisplayRole, formatDate(aprs)); + timeItem->setData(Qt::DisplayRole, formatTime(aprs)); + fromItem->setText(aprs->m_from); + toItem->setText(aprs->m_to); + viaItem->setText(aprs->m_via); + dataItem->setText(aprs->m_data); +} + +void APRSGUI::on_stationFilter_currentIndexChanged(int index) +{ + m_settings.m_stationFilter = static_cast(index); + applySettings(); + filterStations(); +} + +void APRSGUI::on_stationSelect_currentIndexChanged(int index) +{ + (void) index; + QString stationCallsign = ui->stationSelect->currentText(); + + APRSStation *station = m_stations.value(stationCallsign); + if (station == nullptr) + { + qDebug() << "APRSGUI::on_stationSelect_currentIndexChanged - station==nullptr"; + return; + } + + // Clear tables + ui->weatherTable->setRowCount(0); + ui->packetsTable->setRowCount(0); + ui->statusTable->setRowCount(0); + ui->telemetryTable->setRowCount(0); + ui->motionTable->setRowCount(0); + + // Set telemetry plot select combo text + const char *telemetryNames[] = {"A1", "A2", "A3", "A4", "A5", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8"}; + int telemetryNamesSize = station->m_telemetryNames.size(); + for (int i = 0; i < 12; i++) + { + if (i < telemetryNamesSize) + ui->telemetryPlotSelect->setItemText(i, station->m_telemetryNames[i]); + else + ui->telemetryPlotSelect->setItemText(i, telemetryNames[i]); + } + + // Update summary table + updateSummary(station); + + // Display/hide fields depending if station or object + ui->stationObjectLabel->setText(station->m_isObject ? "Object" : "Station"); + ui->station->setToolTip(station->m_isObject ? "Object name" : "Station callsign and substation ID"); + ui->reportingStation->setVisible(station->m_isObject); + ui->reportingStationLabel->setVisible(station->m_isObject); + ui->status->setVisible(!station->m_isObject); + ui->statusLabel->setVisible(!station->m_isObject); + if (station->m_isObject) + { + if ( ui->stationsTabWidget->count() == 6) + { + ui->stationsTabWidget->removeTab(3); + ui->stationsTabWidget->removeTab(3); + } + } + else + { + if ( ui->stationsTabWidget->count() != 6) + { + ui->stationsTabWidget->insertTab(3, ui->telemetryTab, "Telemetry"); + ui->stationsTabWidget->insertTab(4, ui->statusTab, "Status"); + } + } + + // Add packets to tables + ui->packetsTable->setSortingEnabled(false); + ui->weatherTable->setSortingEnabled(false); + ui->statusTable->setSortingEnabled(false); + ui->telemetryTable->setSortingEnabled(false); + ui->motionTable->setSortingEnabled(false); + QListIterator i(station->m_packets); + while (i.hasNext()) + { + APRSPacket *aprs = i.next(); + addPacketToGUI(station, aprs); + } + ui->packetsTable->setSortingEnabled(true); + ui->weatherTable->setSortingEnabled(true); + ui->statusTable->setSortingEnabled(true); + ui->telemetryTable->setSortingEnabled(true); + ui->motionTable->setSortingEnabled(true); + plotWeather(); + plotTelemetry(); + plotMotion(); +} + +void APRSGUI::on_filterAddressee_editingFinished() +{ + m_settings.m_filterAddressee = ui->filterAddressee->text(); + filterMessages(); + applySettings(); +} + +void APRSGUI::filterMessageRow(int row) +{ + bool hidden = false; + if (m_settings.m_filterAddressee != "") + { + QRegExp re(m_settings.m_filterAddressee); + QTableWidgetItem *addressee = ui->messagesTable->item(row, MESSAGE_COL_ADDRESSEE); + if (!re.exactMatch(addressee->text())) + hidden = true; + } + ui->messagesTable->setRowHidden(row, hidden); +} + +void APRSGUI::filterMessages() +{ + for (int i = 0; i < ui->messagesTable->rowCount(); i++) + filterMessageRow(i); +} + +void APRSGUI::on_deleteMessages_clicked() +{ + QList list = ui->messagesTable->selectedItems(); + QList rows; + if (list.isEmpty()) + { + // Delete all messages + if (QMessageBox::Yes == QMessageBox::question(this, "Delete all messages", "Delete all messages?", QMessageBox::Yes|QMessageBox::No)) + ui->messagesTable->setRowCount(0); + } + else + { + // Delete selected messages (in reverse order) + for (int i = 0; i < list.size(); i++) + { + int row = list[i]->row(); + if (!rows.contains(row)) + rows.append(row); + } + std::sort(rows.begin(), rows.end(), std::greater()); + QListIterator itr(rows); + while (itr.hasNext()) + ui->messagesTable->removeRow(itr.next()); + } +} + +static void addToSeries(QLineSeries *series, const QDateTime& dt, double value, double& min, double &max) +{ + series->append(dt.toMSecsSinceEpoch(), value); + if (value < min) + min = value; + if (value > max) + max = value; +} + +void APRSGUI::plotWeather() +{ + QString stationCallsign = ui->stationSelect->currentText(); + if (stationCallsign.isEmpty()) + return; + APRSStation *station = m_stations.value(stationCallsign); + if (station == nullptr) + return; + + QLineSeries *series = new QLineSeries(); + double minValue = INFINITY; + double maxValue = -INFINITY; + + int timeSelectIdx = ui->weatherTimeSelect->currentIndex(); + int plotSelectIdx = ui->weatherPlotSelect->currentIndex(); + QDateTime limit = calcTimeLimit(timeSelectIdx); + + QListIterator i(station->m_packets); + while (i.hasNext()) + { + APRSPacket *aprs = i.next(); + if (aprs->m_hasWeather) + { + QDateTime dt; + if (aprs->m_hasTimestamp) + dt = aprs->m_timestamp; + else + dt = aprs->m_dateTime; + + if (dt >= limit) + { + if (plotSelectIdx == 0 && aprs->m_hasWindDirection) + addToSeries(series, dt, aprs->m_windDirection, minValue, maxValue); + else if (plotSelectIdx == 1 && aprs->m_hasWindSpeed) + addToSeries(series, dt, aprs->m_windSpeed, minValue, maxValue); + else if (plotSelectIdx == 2 && aprs->m_hasGust) + addToSeries(series, dt, aprs->m_gust, minValue, maxValue); + else if (plotSelectIdx == 3 && aprs->m_hasTemp) + addToSeries(series, dt, aprs->m_temp, minValue, maxValue); + else if (plotSelectIdx == 4 && aprs->m_hasHumidity) + addToSeries(series, dt, aprs->m_humidity, minValue, maxValue); + else if (plotSelectIdx == 5 && aprs->m_hasBarometricPressure) + addToSeries(series, dt, aprs->m_barometricPressure/10.0, minValue, maxValue); + else if (plotSelectIdx == 6 && aprs->m_hasRainLastHr) + addToSeries(series, dt, aprs->m_rainLastHr, minValue, maxValue); + else if (plotSelectIdx == 7 && aprs->m_hasRainLast24Hrs) + addToSeries(series, dt, aprs->m_rainLast24Hrs, minValue, maxValue); + else if (plotSelectIdx == 8 && aprs->m_hasRainSinceMidnight) + addToSeries(series, dt, aprs->m_rainSinceMidnight, minValue, maxValue); + else if (plotSelectIdx == 9 && aprs->m_hasLuminsoity) + addToSeries(series, dt, aprs->m_luminosity, minValue, maxValue); + else if (plotSelectIdx == 10 && aprs->m_hasSnowfallLast24Hrs) + addToSeries(series, dt, aprs->m_snowfallLast24Hrs, minValue, maxValue); + else if (plotSelectIdx == 11 && aprs->m_hasRadiationLevel) + addToSeries(series, dt, aprs->m_radiationLevel, minValue, maxValue); + else if (plotSelectIdx == 12 && aprs->m_hasFloodLevel) + addToSeries(series, dt, aprs->m_floodLevel, minValue, maxValue); + } + } + } + m_weatherChart.removeAllSeries(); + m_weatherChart.removeAxis(&m_weatherChartXAxis); + m_weatherChart.removeAxis(&m_weatherChartYAxis); + + m_weatherChart.addSeries(series); + + calcTimeAxis(timeSelectIdx, &m_weatherChartXAxis, series, ui->weatherChart->width()); + + m_weatherChart.addAxis(&m_weatherChartXAxis, Qt::AlignBottom); + + series->attachAxis(&m_weatherChartXAxis); + + m_weatherChartYAxis.setTitleText(ui->weatherPlotSelect->currentText()); + calcYAxis(minValue, maxValue, &m_weatherChartYAxis); + m_weatherChart.addAxis(&m_weatherChartYAxis, Qt::AlignLeft); + series->attachAxis(&m_weatherChartYAxis); +} + +void APRSGUI::on_weatherTimeSelect_currentIndexChanged(int index) +{ + (void) index; + plotWeather(); +} + +void APRSGUI::on_weatherPlotSelect_currentIndexChanged(int index) +{ + (void) index; + plotWeather(); +} + +void APRSGUI::calcTimeAxis(int timeSelectIdx, QDateTimeAxis *axis, QLineSeries *series, int width) +{ + QDateTime startX = QDateTime::currentDateTime(); + QDateTime finishX = QDateTime::currentDateTime(); + finishX.setTime(QTime(0,0,0)); + finishX = finishX.addDays(1); + int ticksScale = width < 650 ? 1 : 2; // FIXME: Should probably measure the width of some actual text + switch (timeSelectIdx) + { + case 0: // Today + startX.setTime(QTime(0,0,0)); + axis->setTickCount(6*ticksScale+1); + axis->setFormat("hh:mm"); + axis->setTitleText(QString("Time (%1)").arg(startX.date().toString())); + break; + case 1: // Last hour + startX.setTime(startX.time().addSecs(-60*60)); + finishX = QDateTime::currentDateTime(); + ticksScale = width < 750 ? 1 : 2; + axis->setTickCount(8*ticksScale+1); + axis->setFormat("hh:mm"); + axis->setTitleText(QString("Time (%1)").arg(startX.date().toString())); + break; + case 2: // Last 24h + startX.setDate(startX.date().addDays(-1)); + finishX = QDateTime::currentDateTime(); + axis->setTickCount(6*ticksScale+1); + axis->setFormat("hh:mm"); + axis->setTitleText(QString("Time (%1)").arg(startX.date().toString())); + break; + case 3: // Last 7 days + startX.setTime(QTime(0,0,0)); + startX.setDate(finishX.date().addDays(-7)); + axis->setTickCount(4*ticksScale); + axis->setFormat("MMM d"); + axis->setTitleText("Date"); + break; + case 4: // Last 30 days + startX.setTime(QTime(0,0,0)); + startX.setDate(finishX.date().addDays(-30)); + axis->setTickCount(5*ticksScale+1); + axis->setFormat("MMM d"); + axis->setTitleText("Date"); + break; + case 5: // All + startX = QDateTime::fromMSecsSinceEpoch(series->at(0).x()); + finishX = QDateTime::fromMSecsSinceEpoch(series->at(series->count()-1).x()); + // FIXME: Problem when startX == finishX - axis not drawn + if (startX.msecsTo(finishX) > 1000*60*60*24) + { + axis->setFormat("MMM d"); + axis->setTitleText("Date"); + } + else if (startX.msecsTo(finishX) > 1000*60*60) + { + axis->setFormat("hh:mm"); + axis->setTitleText(QString("Time (hours on %1)").arg(startX.date().toString())); + } + else + { + axis->setFormat("mm:ss"); + axis->setTitleText(QString("Time (minutes on %1)").arg(startX.date().toString())); + } + axis->setTickCount(5*ticksScale); + break; + } + axis->setRange(startX, finishX); +} + +void APRSGUI::calcYAxis(double minValue, double maxValue, QValueAxis *axis, bool binary, int precision) +{ + double range = std::abs(maxValue - minValue); + double ticks = binary ? 2 : 5; + + axis->setTickCount(ticks); + + if (binary) + { + minValue = 0.0; + maxValue = 1.0; + } + else if (range == 0.0) + { + // Nothing is plotted if min and max are the same, so adjust range to non-zero + if (precision == 1) + { + if ((minValue >= (ticks-1)/2) || (minValue < 0.0)) + { + maxValue += (ticks-1)/2; + minValue -= (ticks-1)/2; + } + else + maxValue = maxValue + (ticks - 1 - minValue); + } + else if (maxValue == 0.0) + maxValue = ticks-1; + else + { + minValue -= minValue * (1.0/std::pow(10.0,precision)); + maxValue += maxValue * (1.0/std::pow(10.0,precision)); + } + range = std::abs(maxValue - minValue); + } + axis->setRange(minValue, maxValue); + if (((range < (ticks-1)) || (precision > 1)) && !binary) + axis->setLabelFormat(QString("%.%1f").arg(precision)); + else + axis->setLabelFormat("%d"); +} + +QDateTime APRSGUI::calcTimeLimit(int timeSelectIdx) +{ + QDateTime limit = QDateTime::currentDateTime(); + switch (timeSelectIdx) + { + case 0: // Today + limit.setTime(QTime(0,0,0)); + break; + case 1: // Last hour + limit.setTime(limit.time().addSecs(-60*60)); + break; + case 2: // Last 24h + limit.setDate(limit.date().addDays(-1)); + break; + case 3: // Last 7 days + limit.setTime(QTime(0,0,0)); + limit.setDate(limit.date().addDays(-7)); + break; + case 4: // Last 30 days + limit.setTime(QTime(0,0,0)); + limit.setDate(limit.date().addDays(-30)); + break; + case 5: // All + limit = QDateTime(QDate(1970, 1, 1), QTime()); + break; + } + return limit; +} + +void APRSGUI::plotMotion() +{ + QString stationCallsign = ui->stationSelect->currentText(); + if (stationCallsign.isEmpty()) + return; + APRSStation *station = m_stations.value(stationCallsign); + if (station == nullptr) + return; + + QLineSeries *series = new QLineSeries(); + double minValue = INFINITY; + double maxValue = -INFINITY; + + int timeSelectIdx = ui->motionTimeSelect->currentIndex(); + int plotSelectIdx = ui->motionPlotSelect->currentIndex(); + QDateTime limit = calcTimeLimit(timeSelectIdx); + + QListIterator i(station->m_packets); + while (i.hasNext()) + { + APRSPacket *aprs = i.next(); + + if (aprs->m_hasPosition || aprs->m_hasAltitude || aprs->m_hasCourseAndSpeed) + { + QDateTime dt; + if (aprs->m_hasTimestamp) + dt = aprs->m_timestamp; + else + dt = aprs->m_dateTime; + + if (dt >= limit) + { + if (plotSelectIdx == 0 && aprs->m_hasPosition) + addToSeries(series, dt, aprs->m_latitude, minValue, maxValue); + else if (plotSelectIdx == 1 && aprs->m_hasPosition) + addToSeries(series, dt, aprs->m_longitude, minValue, maxValue); + else if (plotSelectIdx == 2 && aprs->m_hasAltitude) + addToSeries(series, dt, aprs->m_altitudeFt, minValue, maxValue); + else if (plotSelectIdx == 3 && aprs->m_hasCourseAndSpeed) + addToSeries(series, dt, aprs->m_course, minValue, maxValue); + else if (plotSelectIdx == 4 && aprs->m_hasCourseAndSpeed) + addToSeries(series, dt, aprs->m_speed, minValue, maxValue); + } + } + } + + m_motionChart.removeAllSeries(); + m_motionChart.removeAxis(&m_motionChartXAxis); + m_motionChart.removeAxis(&m_motionChartYAxis); + + m_motionChart.addSeries(series); + calcTimeAxis(timeSelectIdx, &m_motionChartXAxis, series, ui->motionChart->width()); + m_motionChart.addAxis(&m_motionChartXAxis, Qt::AlignBottom); + series->attachAxis(&m_motionChartXAxis); + + m_motionChartYAxis.setTitleText(ui->motionPlotSelect->currentText()); + calcYAxis(minValue, maxValue, &m_motionChartYAxis, false, plotSelectIdx <= 1 ? 5 : 1); + m_motionChart.addAxis(&m_motionChartYAxis, Qt::AlignLeft); + series->attachAxis(&m_motionChartYAxis); +} + +void APRSGUI::on_motionTimeSelect_currentIndexChanged(int index) +{ + (void) index; + plotMotion(); +} + +void APRSGUI::on_motionPlotSelect_currentIndexChanged(int index) +{ + (void) index; + plotMotion(); +} + +void APRSGUI::plotTelemetry() +{ + QString stationCallsign = ui->stationSelect->currentText(); + if (stationCallsign.isEmpty()) + return; + APRSStation *station = m_stations.value(stationCallsign); + if (station == nullptr) + return; + + QLineSeries *series = new QLineSeries(); + double minValue = INFINITY; + double maxValue = -INFINITY; + + int timeSelectIdx = ui->telemetryTimeSelect->currentIndex(); + int plotSelectIdx = ui->telemetryPlotSelect->currentIndex(); + QDateTime limit = calcTimeLimit(timeSelectIdx); + + QListIterator i(station->m_packets); + while (i.hasNext()) + { + APRSPacket *aprs = i.next(); + if (aprs->m_hasTelemetry) + { + if (aprs->m_dateTime >= limit) + { + if (plotSelectIdx == 0 && aprs->m_a1HasValue) + addToSeries(series, aprs->m_dateTime, applyCoefficients(0, aprs->m_a1, station), minValue, maxValue); + else if (plotSelectIdx == 1 && aprs->m_a2HasValue) + addToSeries(series, aprs->m_dateTime, applyCoefficients(1, aprs->m_a2, station), minValue, maxValue); + else if (plotSelectIdx == 2 && aprs->m_a3HasValue) + addToSeries(series, aprs->m_dateTime, applyCoefficients(2, aprs->m_a3, station), minValue, maxValue); + else if (plotSelectIdx == 3 && aprs->m_a4HasValue) + addToSeries(series, aprs->m_dateTime, applyCoefficients(3, aprs->m_a4, station), minValue, maxValue); + else if (plotSelectIdx == 4 && aprs->m_a5HasValue) + addToSeries(series, aprs->m_dateTime, applyCoefficients(4, aprs->m_a5, station), minValue, maxValue); + else if (plotSelectIdx == 5 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[0], minValue, maxValue); + else if (plotSelectIdx == 6 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[1], minValue, maxValue); + else if (plotSelectIdx == 7 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[2], minValue, maxValue); + else if (plotSelectIdx == 8 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[3], minValue, maxValue); + else if (plotSelectIdx == 9 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[4], minValue, maxValue); + else if (plotSelectIdx == 10 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[5], minValue, maxValue); + else if (plotSelectIdx == 11 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[6], minValue, maxValue); + else if (plotSelectIdx == 12 && aprs->m_bHasValue) + addToSeries(series, aprs->m_dateTime, aprs->m_b[7], minValue, maxValue); + } + } + } + + m_telemetryChart.removeAllSeries(); + m_telemetryChart.removeAxis(&m_telemetryChartXAxis); + m_telemetryChart.removeAxis(&m_telemetryChartYAxis); + + m_telemetryChart.addSeries(series); + calcTimeAxis(timeSelectIdx, &m_telemetryChartXAxis, series, ui->telemetryChart->width()); + m_telemetryChart.addAxis(&m_telemetryChartXAxis, Qt::AlignBottom); + + series->attachAxis(&m_telemetryChartXAxis); + m_telemetryChartYAxis.setTitleText(ui->telemetryPlotSelect->currentText()); + calcYAxis(minValue, maxValue, &m_telemetryChartYAxis, plotSelectIdx >= 5); + m_telemetryChart.addAxis(&m_telemetryChartYAxis, Qt::AlignLeft); + series->attachAxis(&m_telemetryChartYAxis); +} + +void APRSGUI::on_telemetryTimeSelect_currentIndexChanged(int index) +{ + (void) index; + plotTelemetry(); +} + +void APRSGUI::on_telemetryPlotSelect_currentIndexChanged(int index) +{ + (void) index; + plotTelemetry(); +} + +void APRSGUI::updateStatus() +{ + int state = m_aprs->getState(); + + if (m_lastFeatureState != state) + { + switch (state) + { + case Feature::StNotStarted: + ui->igate->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case Feature::StIdle: + ui->igate->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case Feature::StRunning: + ui->igate->setStyleSheet("QToolButton { background-color : green; }"); + break; + case Feature::StError: + ui->igate->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_aprs->getErrorMessage()); + break; + default: + break; + } + + m_lastFeatureState = state; + } +} + +void APRSGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + APRS::MsgConfigureAPRS* message = APRS::MsgConfigureAPRS::create(m_settings, force); + m_aprs->getInputMessageQueue()->push(message); + } +} + +void APRSGUI::resizeTable() +{ + int row; + + // Fill tables with a row of dummy data that will size the columns nicely + row = ui->packetsTable->rowCount(); + ui->packetsTable->setRowCount(row + 1); + ui->packetsTable->setItem(row, PACKETS_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->packetsTable->setItem(row, PACKETS_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->packetsTable->setItem(row, PACKETS_COL_FROM, new QTableWidgetItem("123456-15-")); + ui->packetsTable->setItem(row, PACKETS_COL_TO, new QTableWidgetItem("123456-15-")); + ui->packetsTable->setItem(row, PACKETS_COL_VIA, new QTableWidgetItem("123456-15-")); + ui->packetsTable->setItem(row, PACKETS_COL_DATA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ")); + ui->packetsTable->resizeColumnsToContents(); + ui->packetsTable->removeRow(row); + + row = ui->weatherTable->rowCount(); + ui->weatherTable->setRowCount(row + 1); + ui->weatherTable->setItem(row, WEATHER_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->weatherTable->setItem(row, WEATHER_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->weatherTable->setItem(row, WEATHER_COL_WIND_DIRECTION, new QTableWidgetItem("Dir (o)-")); + ui->weatherTable->setItem(row, WEATHER_COL_WIND_SPEED, new QTableWidgetItem("Speed (mph)-")); + ui->weatherTable->setItem(row, WEATHER_COL_GUSTS, new QTableWidgetItem("Gusts (mph)-")); + ui->weatherTable->setItem(row, WEATHER_COL_TEMPERATURE, new QTableWidgetItem("Temp (F)-")); + ui->weatherTable->setItem(row, WEATHER_COL_HUMIDITY, new QTableWidgetItem("Humidity (%)-")); + ui->weatherTable->setItem(row, WEATHER_COL_PRESSURE, new QTableWidgetItem("Pressure (mbar)-")); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_LAST_HOUR, new QTableWidgetItem("Rain 1h-")); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_LAST_24_HOURS, new QTableWidgetItem("Rain 24h-")); + ui->weatherTable->setItem(row, WEATHER_COL_RAIN_SINCE_MIDNIGHT, new QTableWidgetItem("Rain-")); + ui->weatherTable->setItem(row, WEATHER_COL_LUMINOSITY, new QTableWidgetItem("Luminosity-")); + ui->weatherTable->setItem(row, WEATHER_COL_SNOWFALL, new QTableWidgetItem("Snowfall-")); + ui->weatherTable->setItem(row, WEATHER_COL_RADIATION_LEVEL, new QTableWidgetItem("Radiation-")); + ui->weatherTable->setItem(row, WEATHER_COL_FLOOD_LEVEL, new QTableWidgetItem("Flood level-")); + ui->weatherTable->resizeColumnsToContents(); + ui->weatherTable->removeRow(row); + + row = ui->statusTable->rowCount(); + ui->statusTable->setRowCount(row + 1); + ui->statusTable->setIconSize(QSize(24, 24)); + ui->statusTable->setItem(row, STATUS_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->statusTable->setItem(row, STATUS_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->statusTable->setItem(row, STATUS_COL_STATUS, new QTableWidgetItem("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + ui->statusTable->setItem(row, STATUS_COL_SYMBOL, new QTableWidgetItem("WWW")); + ui->statusTable->setItem(row, STATUS_COL_MAIDENHEAD, new QTableWidgetItem("WW00WW")); + ui->statusTable->setItem(row, STATUS_COL_BEAM_HEADING, new QTableWidgetItem("359")); + ui->statusTable->setItem(row, STATUS_COL_BEAM_POWER, new QTableWidgetItem("8000")); + ui->statusTable->resizeColumnsToContents(); + ui->statusTable->removeRow(row); + + row = ui->messagesTable->rowCount(); + ui->messagesTable->setRowCount(row + 1); + ui->messagesTable->setIconSize(QSize(24, 24)); + ui->messagesTable->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->messagesTable->setItem(row, MESSAGE_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->messagesTable->setItem(row, MESSAGE_COL_ADDRESSEE, new QTableWidgetItem("WWWWWWWWW")); + ui->messagesTable->setItem(row, MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ")); + ui->messagesTable->setItem(row, MESSAGE_COL_NO, new QTableWidgetItem("Message No")); + ui->messagesTable->resizeColumnsToContents(); + ui->messagesTable->removeRow(row); + + row = ui->motionTable->rowCount(); + ui->motionTable->setRowCount(row + 1); + ui->motionTable->setItem(row, MOTION_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->motionTable->setItem(row, MOTION_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->motionTable->setItem(row, MOTION_COL_LATITUDE, new QTableWidgetItem("Latitude")); + ui->motionTable->setItem(row, MOTION_COL_LONGITUDE, new QTableWidgetItem("Longitude")); + ui->motionTable->setItem(row, MOTION_COL_ALTITUDE, new QTableWidgetItem("Message No")); + ui->motionTable->setItem(row, MOTION_COL_ALTITUDE, new QTableWidgetItem("Course")); + ui->motionTable->setItem(row, MOTION_COL_ALTITUDE, new QTableWidgetItem("Speed")); + ui->motionTable->resizeColumnsToContents(); + ui->motionTable->removeRow(row); + + row = ui->telemetryTable->rowCount(); + ui->telemetryTable->setRowCount(row + 1); + ui->telemetryTable->setItem(row, TELEMETRY_COL_DATE, new QTableWidgetItem("31/12/2020")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_TIME, new QTableWidgetItem("23:59:39 T")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_SEQ_NO, new QTableWidgetItem("Seq No")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A1, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A2, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A3, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A4, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_A5, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B1, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B2, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B3, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B4, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B5, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B6, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B7, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->setItem(row, TELEMETRY_COL_B8, new QTableWidgetItem("ABCEDF")); + ui->telemetryTable->resizeColumnsToContents(); + ui->telemetryTable->removeRow(row); +} + +// Columns in table reordered +void APRSGUI::packetsTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_packetsTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::packetsTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_packetsTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::packetsTable_columnSelectMenu(QPoint pos) +{ + packetsTableMenu->popup(ui->packetsTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::packetsTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->packetsTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Columns in table reordered +void APRSGUI::weatherTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_weatherTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::weatherTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_weatherTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::weatherTable_columnSelectMenu(QPoint pos) +{ + weatherTableMenu->popup(ui->weatherTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::weatherTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->weatherTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Columns in table reordered +void APRSGUI::statusTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_statusTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::statusTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_statusTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::statusTable_columnSelectMenu(QPoint pos) +{ + statusTableMenu->popup(ui->statusTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::statusTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->statusTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Columns in table reordered +void APRSGUI::messagesTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_messagesTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::messagesTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_messagesTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::messagesTable_columnSelectMenu(QPoint pos) +{ + messagesTableMenu->popup(ui->messagesTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::messagesTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->messagesTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Columns in table reordered +void APRSGUI::telemetryTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_telemetryTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::telemetryTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_telemetryTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::telemetryTable_columnSelectMenu(QPoint pos) +{ + telemetryTableMenu->popup(ui->telemetryTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::telemetryTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->telemetryTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Columns in table reordered +void APRSGUI::motionTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_motionTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void APRSGUI::motionTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_motionTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void APRSGUI::motionTable_columnSelectMenu(QPoint pos) +{ + motionTableMenu->popup(ui->motionTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void APRSGUI::motionTable_columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->motionTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu items + +QAction *APRSGUI::packetsTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(packetsTable_columnSelectMenuChecked())); + return action; +} + +QAction *APRSGUI::weatherTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(weatherTable_columnSelectMenuChecked())); + return action; +} + +QAction *APRSGUI::statusTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(statusTable_columnSelectMenuChecked())); + return action; +} + +QAction *APRSGUI::messagesTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(messagesTable_columnSelectMenuChecked())); + return action; +} + +QAction *APRSGUI::telemetryTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(telemetryTable_columnSelectMenuChecked())); + return action; +} + +QAction *APRSGUI::motionTable_createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(motionTable_columnSelectMenuChecked())); + return action; +} + +// Show settings dialog +void APRSGUI::on_displaySettings_clicked() +{ + APRSSettingsDialog dialog(m_settings.m_igateServer, m_settings.m_igateCallsign, + m_settings.m_igatePasscode, m_settings.m_igateFilter); + if (dialog.exec() == QDialog::Accepted) + { + m_settings.m_igateServer = dialog.m_igateServer; + m_settings.m_igateCallsign = dialog.m_igateCallsign; + m_settings.m_igatePasscode = dialog.m_igatePasscode; + m_settings.m_igateFilter = dialog.m_igateFilter; + applySettings(); + } +} + +void APRSGUI::on_igate_toggled(bool checked) +{ + m_settings.m_igateEnabled = checked; + applySettings(); +} + +// Find the selected station on the Map +void APRSGUI::on_viewOnMap_clicked() +{ + QString stationName = ui->stationSelect->currentText(); + if (!stationName.isEmpty()) + { + APRSStation *station = m_stations.value(stationName); + if (station != nullptr) + { + FeatureWebAPIUtils::mapFind(station->m_station); + } + } +} diff --git a/plugins/feature/aprs/aprsgui.h b/plugins/feature/aprs/aprsgui.h new file mode 100644 index 000000000..9523cf4ef --- /dev/null +++ b/plugins/feature/aprs/aprsgui.h @@ -0,0 +1,227 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_APRSGUI_H_ +#define INCLUDE_FEATURE_APRSGUI_H_ + +#include +#include +#include +#include +#include +#include + +#include "feature/featuregui.h" +#include "util/messagequeue.h" +#include "pipes/pipeendpoint.h" +#include "util/aprs.h" +#include "aprssettings.h" + +class PluginAPI; +class FeatureUISet; +class APRS; + +namespace Ui { + class APRSGUI; +} + +using namespace QtCharts; + +class APRSGUI; + +class APRSStation { +public: + + APRSStation(QString& station) : + m_station(station), + m_isObject(false), + m_hasWeather(false), + m_hasTelemetry(false), + m_hasCourseAndSpeed(false) + { + } + + void addPacket(APRSPacket *packet) + { + m_packets.append(packet); + } + +private: + friend APRSGUI; + QString m_station; // Station callsign + QList m_packets; // Packets received for the station + QString m_symbolImage; + QString m_latestStatus; + QString m_latestComment; + QString m_latestPosition; + QString m_latestAltitude; + QString m_latestCourse; + QString m_latestSpeed; + QString m_latestPacket; + QString m_powerWatts; + QString m_antennaHeightFt; + QString m_antennaGainDB; + QString m_antennaDirectivity; + QString m_radioRange; + bool m_isObject; // Is an object or item rather than a station + QString m_reportingStation; + QList m_telemetryNames; + QList m_telemetryLabels; + double m_telemetryCoefficientsA[5]; + double m_telemetryCoefficientsB[5]; + double m_telemetryCoefficientsC[5]; + int m_hasTelemetryCoefficients; + int m_telemetryBitSense[8]; + bool m_hasTelemetryBitSense; + QString m_telemetryProjectName; + bool m_hasWeather; + bool m_hasTelemetry; + bool m_hasCourseAndSpeed; +}; + +class APRSGUI : public FeatureGUI { + Q_OBJECT +public: + static APRSGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +protected: + void resizeEvent(QResizeEvent* size); + +private: + Ui::APRSGUI* ui; + PluginAPI* m_pluginAPI; + FeatureUISet* m_featureUISet; + APRSSettings m_settings; + bool m_doApplySettings; + QList m_availablePipes; + + APRS* m_aprs; + MessageQueue m_inputMessageQueue; + QTimer m_statusTimer; + int m_lastFeatureState; + + QHash m_stations; // All stations we've recieved packets for. Hashed on callsign + + QMenu *packetsTableMenu; // Column select context menus + QMenu *weatherTableMenu; + QMenu *statusTableMenu; + QMenu *messagesTableMenu; + QMenu *telemetryTableMenu; + QMenu *motionTableMenu; + + QChart m_weatherChart; + QDateTimeAxis m_weatherChartXAxis; + QValueAxis m_weatherChartYAxis; + + QChart m_telemetryChart; + QDateTimeAxis m_telemetryChartXAxis; + QValueAxis m_telemetryChartYAxis; + + QChart m_motionChart; + QDateTimeAxis m_motionChartXAxis; + QValueAxis m_motionChartYAxis; + + explicit APRSGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); + virtual ~APRSGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displayTableSettings(QTableWidget *table, QMenu *menu, int *columnIndexes, int *columnSizes, int columns); + bool filterStation(APRSStation *station); + void filterStations(); + void displaySettings(); + void updatePipeList(); + bool handleMessage(const Message& message); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void filterMessageRow(int row); + void filterMessages(); + void resizeTable(); + QAction *packetsTable_createCheckableItem(QString& text, int idx, bool checked); + QAction *weatherTable_createCheckableItem(QString& text, int idx, bool checked); + QAction *statusTable_createCheckableItem(QString& text, int idx, bool checked); + QAction *messagesTable_createCheckableItem(QString& text, int idx, bool checked); + QAction *telemetryTable_createCheckableItem(QString& text, int idx, bool checked); + QAction *motionTable_createCheckableItem(QString& text, int idx, bool checked); + + void updateSummary(APRSStation *station); + void addPacketToGUI(APRSStation *station, APRSPacket *aprs); + +private slots: + void onMenuDialogCalled(const QPoint &p); + void onWidgetRolled(QWidget* widget, bool rollDown); + void handleInputMessages(); + void on_stationFilter_currentIndexChanged(int index); + void on_stationSelect_currentIndexChanged(int index); + void on_filterAddressee_editingFinished(); + void on_deleteMessages_clicked(); + QDateTime calcTimeLimit(int timeSelectIdx); + void calcTimeAxis(int timeSelectIdx, QDateTimeAxis *axis, QLineSeries *series, int width); + void calcYAxis(double minValue, double maxValue, QValueAxis *axis, bool binary=false, int precision=1); + QString formatDate(APRSPacket *aprs); + QString formatTime(APRSPacket *aprs); + double applyCoefficients(int idx, int value, APRSStation *station); + void plotWeather(); + void on_weatherTimeSelect_currentIndexChanged(int index); + void on_weatherPlotSelect_currentIndexChanged(int index); + void plotTelemetry(); + void on_telemetryTimeSelect_currentIndexChanged(int index); + void on_telemetryPlotSelect_currentIndexChanged(int index); + void plotMotion(); + void on_motionTimeSelect_currentIndexChanged(int index); + void on_motionPlotSelect_currentIndexChanged(int index); + void updateStatus(); + void packetsTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void packetsTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void packetsTable_columnSelectMenu(QPoint pos); + void packetsTable_columnSelectMenuChecked(bool checked = false); + void weatherTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void weatherTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void weatherTable_columnSelectMenu(QPoint pos); + void weatherTable_columnSelectMenuChecked(bool checked = false); + void statusTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void statusTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void statusTable_columnSelectMenu(QPoint pos); + void statusTable_columnSelectMenuChecked(bool checked = false); + void messagesTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void messagesTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void messagesTable_columnSelectMenu(QPoint pos); + void messagesTable_columnSelectMenuChecked(bool checked = false); + void telemetryTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void telemetryTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void telemetryTable_columnSelectMenu(QPoint pos); + void telemetryTable_columnSelectMenuChecked(bool checked = false); + void motionTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void motionTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void motionTable_columnSelectMenu(QPoint pos); + void motionTable_columnSelectMenuChecked(bool checked = false); + void on_displaySettings_clicked(); + void on_igate_toggled(bool checked); + void on_viewOnMap_clicked(); +}; + + +#endif // INCLUDE_FEATURE_APRSGUI_H_ diff --git a/plugins/feature/aprs/aprsgui.ui b/plugins/feature/aprs/aprsgui.ui new file mode 100644 index 000000000..db0e01608 --- /dev/null +++ b/plugins/feature/aprs/aprsgui.ui @@ -0,0 +1,1514 @@ + + + APRSGUI + + + + 0 + 0 + 469 + 761 + + + + + 0 + 0 + + + + + 462 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + APRS + + + APRS + + + + + 0 + 0 + 461 + 31 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Sources + + + + + + + + 150 + 0 + + + + APRS packet source channels + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Enable APRS-IS IGate (Internet gateway) + + + ... + + + + :/txon.png:/txon.png + + + true + + + + + + + Show settings dialog + + + + + + + :/listing.png:/listing.png + + + + + + + + + + + 0 + 40 + 461 + 532 + + + + + 0 + 0 + + + + APRS + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + QTabWidget::West + + + 0 + + + + Stations and Objects + + + + + + + + + 140 + 0 + + + + Selects which stations and objects to list in the adjacent list + + + + All stations/objects + + + + + Stations + + + + + Objects + + + + + Weather stations + + + + + Telemetry + + + + + Course and speed + + + + + + + + + 100 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Find on the map + + + + + + + :/gridpolar.png:/gridpolar.png + + + + + + + + + 0 + + + + Summary + + + + + + Position + + + + + + + Radio Range + + + + + + + Last course + + + + + + + Antenna Height + + + + + + + Status + + + + + + + Antenna direction + + + + + + + Last comment for this station + + + true + + + + + + + Feet + + + + + + + Antenna Gain + + + + + + + Last position + + + true + + + + + + + Speed + + + + + + + Last status for this station + + + true + + + + + + + Altitude + + + + + + + + + + + + + + Range of radio + + + + + + + dB + + + + + + + Antenna gain + + + + + + + Time last packet was received from this station + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Last speed + + + + + + + Symbol + + + + + + + Antenna Directivity + + + + + + + ° + + + + + + + Comment + + + + + + + + + + Course + + + + + + + Watts + + + + + + + Antenna height + + + + + + + TX Power + + + + + + + Feet + + + + + + + Transmit power + + + + + + + Station callsign and substation ID + + + true + + + + + + + Last altitude + + + + + + + Station/Object + + + + + + + Knots + + + + + + + Miles + + + + + + + Last Packet: + + + + + + + Reporting Station + + + + + + + + Weather + + + + + + + 0 + 150 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + Dir (°) + + + + + Speed (mph) + + + + + Gusts (mph) + + + + + Temp (F) + + + + + Humidity (%) + + + + + Pressure (mbar) + + + + + Rain (last hour) + + + + + Rain (last 24 hours) + + + + + Rain (since midnight) + + + + + Luminosity + + + + + Snowfall + + + + + Radiation + + + + + Flood level + + + + + + + + + + + 0 + 0 + + + + Plot + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + Select data to plot + + + + Wind direction + + + + + Wind speed + + + + + Gusts + + + + + Temperature + + + + + Humidity + + + + + Pressure + + + + + Rain (last hour) + + + + + Rain (last 24 hours) + + + + + Rain (since midnight) + + + + + Luminosity + + + + + Snowfall + + + + + Radiation + + + + + Flood level + + + + + + + + Qt::Vertical + + + + + + + Time + + + + + + + + 100 + 0 + + + + Time range to plot data for + + + + Today + + + + + Last hour + + + + + Last 24 hours + + + + + Last 7 days + + + + + Last 30 days + + + + + All + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Motion + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + Latitude + + + + + Longitude + + + + + Altitude + + + + + Course + + + + + Speed + + + + + + + + + + Plot + + + + + + + + 150 + 0 + + + + Select data to plot + + + + Latitude + + + + + Longitude + + + + + Altitude + + + + + Course + + + + + Speed + + + + + + + + Time + + + + + + + + 100 + 0 + + + + Time range to plot data for + + + + Today + + + + + Last hour + + + + + Last 24 hours + + + + + Last 7 days + + + + + Last 30 days + + + + + All + + + + + + + + Qt::Horizontal + + + + 373 + 20 + + + + + + + + + + + + + + Telemetry + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + Seq No + + + + + A1 + + + + + A2 + + + + + A3 + + + + + A4 + + + + + A5 + + + + + B1 + + + + + B2 + + + + + B3 + + + + + B4 + + + + + B5 + + + + + B6 + + + + + B7 + + + + + B8 + + + + + Comment + + + + + + + + + + Plot + + + + + + + + 150 + 0 + + + + Select data to plot + + + + A1 + + + + + A2 + + + + + A3 + + + + + A4 + + + + + A5 + + + + + B1 + + + + + B2 + + + + + B3 + + + + + B4 + + + + + B5 + + + + + B6 + + + + + B7 + + + + + B8 + + + + + + + + Qt::Vertical + + + + + + + Time + + + + + + + + 100 + 0 + + + + Time range to plot data for + + + + Today + + + + + Last hour + + + + + Last 24 hours + + + + + Last 7 days + + + + + Last 30 days + + + + + All + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Status + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + Status + + + + + Symbol + + + + + Maidenhead + + + + + Beam Heading (°) + + + + + Beam Power (W) + + + + + + + + + Packets + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + From + + + + + To + + + + + Via + + + + + Data + + + + + + + + + + + + + Messages + + + + + + + + Addressee + + + + + + + Display only messages where the addressee matches the specified regular expression + + + + + + + Delete the selected message or all messages if none selected + + + + + + + :/bin.png:/bin.png + + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + + + Time + + + + + Addressee + + + + + Message + + + + + Message No + + + + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + QChartView + QGraphicsView +
QtCharts
+
+
+ + sourcePipes + igate + displaySettings + topTabWidget + stationSelect + viewOnMap + stationsTabWidget + station + reportingStation + status + comment + position + altitude + course + speed + txPower + antennaHeight + antennaGain + antennaDirectivity + radioRange + lastPacket + weatherTable + weatherPlotSelect + weatherTimeSelect + weatherChart + motionTable + motionPlotSelect + motionTimeSelect + motionChart + telemetryTable + telemetryPlotSelect + telemetryTimeSelect + telemetryChart + statusTable + packetsTable + filterAddressee + deleteMessages + messagesTable + + + + + +
diff --git a/plugins/feature/aprs/aprsplugin.cpp b/plugins/feature/aprs/aprsplugin.cpp new file mode 100644 index 000000000..737089aaa --- /dev/null +++ b/plugins/feature/aprs/aprsplugin.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "aprsgui.h" +#endif +#include "aprs.h" +#include "aprsplugin.h" +#include "aprswebapiadapter.h" + +const PluginDescriptor APRSPlugin::m_pluginDescriptor = { + APRS::m_featureId, + QStringLiteral("APRS"), + QStringLiteral("6.4.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +APRSPlugin::APRSPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(nullptr) +{ +} + +const PluginDescriptor& APRSPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void APRSPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerFeature(APRS::m_featureIdURI, APRS::m_featureId, this); +} + +#ifdef SERVER_MODE +FeatureGUI* APRSPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + (void) featureUISet; + (void) feature; + return nullptr; +} +#else +FeatureGUI* APRSPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + return APRSGUI::create(m_pluginAPI, featureUISet, feature); +} +#endif + +Feature* APRSPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const +{ + return new APRS(webAPIAdapterInterface); +} + +FeatureWebAPIAdapter* APRSPlugin::createFeatureWebAPIAdapter() const +{ + return new APRSWebAPIAdapter(); +} diff --git a/plugins/feature/aprs/aprsplugin.h b/plugins/feature/aprs/aprsplugin.h new file mode 100644 index 000000000..c2767581e --- /dev/null +++ b/plugins/feature/aprs/aprsplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_APRSPLUGIN_H +#define INCLUDE_FEATURE_APRSPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class FeatureGUI; +class WebAPIAdapterInterface; + +class APRSPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.feature.aprs") + +public: + explicit APRSPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const; + virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const; + virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FEATURE_APRSPLUGIN_H diff --git a/plugins/feature/aprs/aprssettings.cpp b/plugins/feature/aprs/aprssettings.cpp new file mode 100644 index 000000000..6219d8613 --- /dev/null +++ b/plugins/feature/aprs/aprssettings.cpp @@ -0,0 +1,210 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "aprssettings.h" + +const QStringList APRSSettings::m_pipeTypes = { + QStringLiteral("PacketDemod") +}; + +const QStringList APRSSettings::m_pipeURIs = { + QStringLiteral("sdrangel.channelrx.packetdemod"), +}; + +APRSSettings::APRSSettings() +{ + resetToDefaults(); +} + +void APRSSettings::resetToDefaults() +{ + m_igateServer = "noam.aprs2.net"; + m_igatePort = 14580; + m_igateCallsign = ""; + m_igatePasscode = ""; + m_igateFilter = "m/10"; + m_igateEnabled = false; + m_stationFilter = ALL; + m_filterAddressee = ""; + m_title = "APRS"; + m_rgbColor = QColor(225, 25, 99).rgb(); + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIFeatureSetIndex = 0; + m_reverseAPIFeatureIndex = 0; + for (int i = 0; i < APRS_PACKETS_TABLE_COLUMNS; i++) + { + m_packetsTableColumnIndexes[i] = i; + m_packetsTableColumnSizes[i] = -1; // Autosize + } + for (int i = 0; i < APRS_WEATHER_TABLE_COLUMNS; i++) + { + m_weatherTableColumnIndexes[i] = i; + m_weatherTableColumnSizes[i] = -1; // Autosize + } + for (int i = 0; i < APRS_STATUS_TABLE_COLUMNS; i++) + { + m_statusTableColumnIndexes[i] = i; + m_statusTableColumnSizes[i] = -1; // Autosize + } + for (int i = 0; i < APRS_MESSAGES_TABLE_COLUMNS; i++) + { + m_messagesTableColumnIndexes[i] = i; + m_messagesTableColumnSizes[i] = -1; // Autosize + } + for (int i = 0; i < APRS_TELEMETRY_TABLE_COLUMNS; i++) + { + m_telemetryTableColumnIndexes[i] = i; + m_telemetryTableColumnSizes[i] = -1; // Autosize + } + for (int i = 0; i < APRS_MOTION_TABLE_COLUMNS; i++) + { + m_motionTableColumnIndexes[i] = i; + m_motionTableColumnSizes[i] = -1; // Autosize + } +} + +QByteArray APRSSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_igateServer); + s.writeS32(2, m_igatePort); + s.writeString(3, m_igateCallsign); + s.writeString(4, m_igatePasscode); + s.writeString(5, m_igateFilter); + s.writeBool(6, m_igateEnabled); + s.writeS32(7, m_stationFilter); + s.writeString(8, m_filterAddressee); + s.writeString(9, m_title); + s.writeU32(10, m_rgbColor); + s.writeBool(11, m_useReverseAPI); + s.writeString(12, m_reverseAPIAddress); + s.writeU32(13, m_reverseAPIPort); + s.writeU32(14, m_reverseAPIFeatureSetIndex); + s.writeU32(15, m_reverseAPIFeatureIndex); + + for (int i = 0; i < APRS_PACKETS_TABLE_COLUMNS; i++) + s.writeS32(100 + i, m_packetsTableColumnIndexes[i]); + for (int i = 0; i < APRS_PACKETS_TABLE_COLUMNS; i++) + s.writeS32(200 + i, m_packetsTableColumnSizes[i]); + for (int i = 0; i < APRS_WEATHER_TABLE_COLUMNS; i++) + s.writeS32(300 + i, m_weatherTableColumnIndexes[i]); + for (int i = 0; i < APRS_WEATHER_TABLE_COLUMNS; i++) + s.writeS32(400 + i, m_weatherTableColumnSizes[i]); + for (int i = 0; i < APRS_STATUS_TABLE_COLUMNS; i++) + s.writeS32(500 + i, m_statusTableColumnIndexes[i]); + for (int i = 0; i < APRS_STATUS_TABLE_COLUMNS; i++) + s.writeS32(600 + i, m_statusTableColumnSizes[i]); + for (int i = 0; i < APRS_MESSAGES_TABLE_COLUMNS; i++) + s.writeS32(700 + i, m_messagesTableColumnIndexes[i]); + for (int i = 0; i < APRS_MESSAGES_TABLE_COLUMNS; i++) + s.writeS32(800 + i, m_messagesTableColumnSizes[i]); + for (int i = 0; i < APRS_TELEMETRY_TABLE_COLUMNS; i++) + s.writeS32(900 + i, m_telemetryTableColumnIndexes[i]); + for (int i = 0; i < APRS_TELEMETRY_TABLE_COLUMNS; i++) + s.writeS32(1000 + i, m_telemetryTableColumnSizes[i]); + for (int i = 0; i < APRS_MOTION_TABLE_COLUMNS; i++) + s.writeS32(1100 + i, m_motionTableColumnIndexes[i]); + for (int i = 0; i < APRS_MOTION_TABLE_COLUMNS; i++) + s.writeS32(1200 + i, m_motionTableColumnSizes[i]); + + return s.final(); +} + +bool APRSSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readString(1, &m_igateServer, "noam.aprs2.net"); + d.readS32(2, &m_igatePort, 14580); + d.readString(3, &m_igateCallsign, ""); + d.readString(4, &m_igatePasscode, ""); + d.readString(5, &m_igateFilter, ""); + d.readBool(6, &m_igateEnabled, false); + d.readS32(7, (int*)&m_stationFilter, 0); + d.readString(8, &m_filterAddressee, ""); + + d.readString(9, &m_title, "APRS"); + d.readU32(10, &m_rgbColor, QColor(225, 25, 99).rgb()); + d.readBool(11, &m_useReverseAPI, false); + d.readString(12, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(13, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(14, &utmp, 0); + m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; + d.readU32(15, &utmp, 0); + m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < APRS_PACKETS_TABLE_COLUMNS; i++) + d.readS32(100 + i, &m_packetsTableColumnIndexes[i], i); + for (int i = 0; i < APRS_PACKETS_TABLE_COLUMNS; i++) + d.readS32(200 + i, &m_packetsTableColumnSizes[i], -1); + for (int i = 0; i < APRS_WEATHER_TABLE_COLUMNS; i++) + d.readS32(300 + i, &m_weatherTableColumnIndexes[i], i); + for (int i = 0; i < APRS_WEATHER_TABLE_COLUMNS; i++) + d.readS32(400 + i, &m_weatherTableColumnSizes[i], -1); + for (int i = 0; i < APRS_STATUS_TABLE_COLUMNS; i++) + d.readS32(500 + i, &m_statusTableColumnIndexes[i], i); + for (int i = 0; i < APRS_STATUS_TABLE_COLUMNS; i++) + d.readS32(600 + i, &m_statusTableColumnSizes[i], -1); + for (int i = 0; i < APRS_MESSAGES_TABLE_COLUMNS; i++) + d.readS32(700 + i, &m_messagesTableColumnIndexes[i], i); + for (int i = 0; i < APRS_MESSAGES_TABLE_COLUMNS; i++) + d.readS32(800 + i, &m_messagesTableColumnSizes[i], -1); + for (int i = 0; i < APRS_TELEMETRY_TABLE_COLUMNS; i++) + d.readS32(900 + i, &m_telemetryTableColumnIndexes[i], i); + for (int i = 0; i < APRS_TELEMETRY_TABLE_COLUMNS; i++) + d.readS32(1000 + i, &m_telemetryTableColumnSizes[i], -1); + for (int i = 0; i < APRS_MOTION_TABLE_COLUMNS; i++) + d.readS32(1100 + i, &m_motionTableColumnIndexes[i], i); + for (int i = 0; i < APRS_MOTION_TABLE_COLUMNS; i++) + d.readS32(1200 + i, &m_motionTableColumnSizes[i], -1); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/feature/aprs/aprssettings.h b/plugins/feature/aprs/aprssettings.h new file mode 100644 index 000000000..65b4312d5 --- /dev/null +++ b/plugins/feature/aprs/aprssettings.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_APRSSETTINGS_H_ +#define INCLUDE_FEATURE_APRSSETTINGS_H_ + +#include +#include + +#include "util/message.h" + +class Serializable; + +// Number of columns in the tables +#define APRS_PACKETS_TABLE_COLUMNS 6 +#define APRS_WEATHER_TABLE_COLUMNS 15 +#define APRS_STATUS_TABLE_COLUMNS 7 +#define APRS_MESSAGES_TABLE_COLUMNS 5 +#define APRS_TELEMETRY_TABLE_COLUMNS 17 +#define APRS_MOTION_TABLE_COLUMNS 7 + +struct APRSSettings +{ + QString m_igateServer; + int m_igatePort; + QString m_igateCallsign; + QString m_igatePasscode; + QString m_igateFilter; + bool m_igateEnabled; + enum StationFilter { + ALL, STATIONS, OBJECTS, WEATHER, TELEMETRY, COURSE_AND_SPEED + } m_stationFilter; + QString m_filterAddressee; + QString m_title; + quint32 m_rgbColor; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIFeatureSetIndex; + uint16_t m_reverseAPIFeatureIndex; + + int m_packetsTableColumnIndexes[APRS_PACKETS_TABLE_COLUMNS];//!< How the columns are ordered in the table + int m_packetsTableColumnSizes[APRS_PACKETS_TABLE_COLUMNS]; //!< Size of the columns in the table + int m_weatherTableColumnIndexes[APRS_WEATHER_TABLE_COLUMNS]; + int m_weatherTableColumnSizes[APRS_WEATHER_TABLE_COLUMNS]; + int m_statusTableColumnIndexes[APRS_STATUS_TABLE_COLUMNS]; + int m_statusTableColumnSizes[APRS_STATUS_TABLE_COLUMNS]; + int m_messagesTableColumnIndexes[APRS_MESSAGES_TABLE_COLUMNS]; + int m_messagesTableColumnSizes[APRS_MESSAGES_TABLE_COLUMNS]; + int m_telemetryTableColumnIndexes[APRS_TELEMETRY_TABLE_COLUMNS]; + int m_telemetryTableColumnSizes[APRS_TELEMETRY_TABLE_COLUMNS]; + int m_motionTableColumnIndexes[APRS_MOTION_TABLE_COLUMNS]; + int m_motionTableColumnSizes[APRS_MOTION_TABLE_COLUMNS]; + + APRSSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + static const QStringList m_pipeTypes; + static const QStringList m_pipeURIs; +}; + +#endif // INCLUDE_FEATURE_APRSSETTINGS_H_ diff --git a/plugins/feature/aprs/aprssettingsdialog.cpp b/plugins/feature/aprs/aprssettingsdialog.cpp new file mode 100644 index 000000000..4dc1a8fb7 --- /dev/null +++ b/plugins/feature/aprs/aprssettingsdialog.cpp @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "aprssettingsdialog.h" + +APRSSettingsDialog::APRSSettingsDialog(QString igateServer, QString igateCallsign, QString igatePasscode, QString igateFilter, QWidget* parent) : + QDialog(parent), + ui(new Ui::APRSSettingsDialog) +{ + ui->setupUi(this); + ui->igateServer->setCurrentText(igateServer); + ui->igateCallsign->setText(igateCallsign); + ui->igatePasscode->setText(igatePasscode); + ui->igateFilter->setText(igateFilter); +} + +APRSSettingsDialog::~APRSSettingsDialog() +{ + delete ui; +} + +void APRSSettingsDialog::accept() +{ + m_igateServer = ui->igateServer->currentText(); + m_igateCallsign = ui->igateCallsign->text(); + m_igatePasscode = ui->igatePasscode->text(); + m_igateFilter = ui->igateFilter->text(); + QDialog::accept(); +} diff --git a/plugins/feature/aprs/aprssettingsdialog.h b/plugins/feature/aprs/aprssettingsdialog.h new file mode 100644 index 000000000..76dc892ef --- /dev/null +++ b/plugins/feature/aprs/aprssettingsdialog.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_APRSSETTINGSDIALOG_H +#define INCLUDE_APRSSETTINGSDIALOG_H + +#include "ui_aprssettingsdialog.h" +#include "aprssettings.h" + +class APRSSettingsDialog : public QDialog { + Q_OBJECT + +public: + explicit APRSSettingsDialog(QString igateServer, QString igateCallsign, QString igatePasscode, QString igateFilter, QWidget* parent = 0); + ~APRSSettingsDialog(); + + QString m_igateServer; + QString m_igateCallsign; + QString m_igatePasscode; + QString m_igateFilter; + +private slots: + void accept(); + +private: + Ui::APRSSettingsDialog* ui; +}; + +#endif // INCLUDE_APRSSETTINGSDIALOG_H diff --git a/plugins/feature/aprs/aprssettingsdialog.ui b/plugins/feature/aprs/aprssettingsdialog.ui new file mode 100644 index 000000000..d2ee9b07c --- /dev/null +++ b/plugins/feature/aprs/aprssettingsdialog.ui @@ -0,0 +1,173 @@ + + + APRSSettingsDialog + + + + 0 + 0 + 351 + 179 + + + + + Liberation Sans + 9 + + + + APRS Settings + + + + + + + + + Name of APRS-IS server to connect to + + + true + + + + noam.aprs2.net + + + + + soam.aprs2.net + + + + + euro.aprs2.net + + + + + asia.aprs2.net + + + + + aunz.aprs2.net + + + + + + + + IGate Server + + + + + + + IGate Passcode + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + IGate Callsign + + + + + + + Server side filter + + + + + + + IGate Filter + + + + + + + Callsign to use when connecting to APRS-IS server + + + + + + + Passcode corresponding to APRS-IS callsign + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + APRSSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + APRSSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/aprs/aprswebapiadapter.cpp b/plugins/feature/aprs/aprswebapiadapter.cpp new file mode 100644 index 000000000..9bb122fb0 --- /dev/null +++ b/plugins/feature/aprs/aprswebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGFeatureSettings.h" +#include "aprs.h" +#include "aprswebapiadapter.h" + +APRSWebAPIAdapter::APRSWebAPIAdapter() +{} + +APRSWebAPIAdapter::~APRSWebAPIAdapter() +{} + +int APRSWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings()); + response.getSimplePttSettings()->init(); + APRS::webapiFormatFeatureSettings(response, m_settings); + + return 200; +} + +int APRSWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + APRS::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response); + + return 200; +} diff --git a/plugins/feature/aprs/aprswebapiadapter.h b/plugins/feature/aprs/aprswebapiadapter.h new file mode 100644 index 000000000..e0405813f --- /dev/null +++ b/plugins/feature/aprs/aprswebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// 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_APRS_WEBAPIADAPTER_H +#define INCLUDE_APRS_WEBAPIADAPTER_H + +#include "feature/featurewebapiadapter.h" +#include "aprssettings.h" + +/** + * Standalone API adapter only for the settings + */ +class APRSWebAPIAdapter : public FeatureWebAPIAdapter { +public: + APRSWebAPIAdapter(); + virtual ~APRSWebAPIAdapter(); + + virtual QByteArray serialize() const { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + +private: + APRSSettings m_settings; +}; + +#endif // INCLUDE_APRS_WEBAPIADAPTER_H diff --git a/plugins/feature/aprs/aprsworker.cpp b/plugins/feature/aprs/aprsworker.cpp new file mode 100644 index 000000000..2764daab2 --- /dev/null +++ b/plugins/feature/aprs/aprsworker.cpp @@ -0,0 +1,254 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "webapi/webapiadapterinterface.h" +#include "webapi/webapiutils.h" +#include "maincore.h" +#include "util/ax25.h" +#include "util/aprs.h" + +#include "aprs.h" +#include "aprsworker.h" + +MESSAGE_CLASS_DEFINITION(APRSWorker::MsgConfigureAPRSWorker, Message) + +APRSWorker::APRSWorker(APRS *aprs, WebAPIAdapterInterface *webAPIAdapterInterface) : + m_aprs(aprs), + m_webAPIAdapterInterface(webAPIAdapterInterface), + m_msgQueueToFeature(nullptr), + m_msgQueueToGUI(nullptr), + m_running(false), + m_mutex(QMutex::Recursive) +{ + connect(&m_socket, SIGNAL(readyRead()),this, SLOT(recv())); + connect(&m_socket, SIGNAL(connected()), this, SLOT(connected())); + connect(&m_socket, SIGNAL(disconnected()), this, SLOT(disconnected())); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + connect(&m_socket, QOverload::of(&QAbstractSocket::error), this, &APRSWorker::errorOccurred); +#else + connect(&m_socket, &QAbstractSocket::errorOccurred, this, &APRSWorker::errorOccurred); +#endif +} + +APRSWorker::~APRSWorker() +{ + m_inputMessageQueue.clear(); +} + +void APRSWorker::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); +} + +bool APRSWorker::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; + return m_running; +} + +void APRSWorker::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = false; + // Close any existing connection + if (m_socket.isOpen()) + m_socket.close(); +} + +void APRSWorker::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool APRSWorker::handleMessage(const Message& cmd) +{ + if (MsgConfigureAPRSWorker::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureAPRSWorker& cfg = (MsgConfigureAPRSWorker&) cmd; + + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else if (MainCore::MsgPacket::match(cmd)) + { + MainCore::MsgPacket& report = (MainCore::MsgPacket&) cmd; + AX25Packet ax25; + APRSPacket *aprs = new APRSPacket(); + if (ax25.decode(report.getPacket())) + { + if (aprs->decode(ax25)) + { + // See: http://www.aprs-is.net/IGateDetails.aspx for gating rules + if (!aprs->m_via.contains("TCPIP") + && !aprs->m_via.contains("TCPXX") + && !aprs->m_via.contains("NOGATE") + && !aprs->m_via.contains("RFONLY")) + { + aprs->m_dateTime = report.getDateTime(); + QString igateMsg = aprs->toTNC2(m_settings.m_igateCallsign); + send(igateMsg.toUtf8(), igateMsg.length()); + } + } + } + return true; + } + else + { + return false; + } +} + +void APRSWorker::applySettings(const APRSSettings& settings, bool force) +{ + qDebug() << "APRSWorker::applySettings:" + << " m_igateEnabled: " << settings.m_igateEnabled + << " m_igateServer: " << settings.m_igateServer + << " m_igatePort: " << settings.m_igatePort + << " m_igateCallsign: " << settings.m_igateCallsign + << " m_igateFilter: " << settings.m_igateFilter + << " force: " << force; + + if ((settings.m_igateEnabled != m_settings.m_igateEnabled) + || (settings.m_igateServer != m_settings.m_igateServer) + || (settings.m_igatePort != m_settings.m_igatePort) + || (settings.m_igateFilter != m_settings.m_igateFilter) + || force) + { + // Close any existing connection + if (m_socket.isOpen()) + m_socket.close(); + // Open connection + if (settings.m_igateEnabled) + { + if (settings.m_igateServer.isEmpty()) + { + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("IGate server name must be specified")); + } + else if (settings.m_igateCallsign.isEmpty()) + { + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("IGate callsign must be specified")); + } + else if (settings.m_igatePasscode.isEmpty()) + { + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("IGate passcode must be specified")); + } + else + { + qDebug() << "APRSWorker::applySettings: Connecting to " << settings.m_igateServer << ":" << settings.m_igatePort; + m_socket.setSocketOption(QAbstractSocket::LowDelayOption, 1); + m_socket.connectToHost(settings.m_igateServer, settings.m_igatePort); + } + } + } + + m_settings = settings; +} + +void APRSWorker::connected() +{ + qDebug() << "APRSWorker::connected " << m_settings.m_igateServer; + m_loggedIn = false; +} + +void APRSWorker::disconnected() +{ + qDebug() << "APRSWorker::disconnected"; + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("Disconnected")); +} + +void APRSWorker::errorOccurred(QAbstractSocket::SocketError socketError) +{ + qDebug() << "APRSWorker::errorOccurred: " << socketError; + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create(m_socket.errorString() + " " + socketError)); +} + +void APRSWorker::recv() +{ + char buffer[2048]; + + while (m_socket.readLine(buffer, sizeof(buffer)) > 0) + { + QString packet(buffer); + qDebug() << "APRSWorker::recv: " << packet; + if (packet.startsWith("#")) + { + if (!m_loggedIn) + { + // Log in with callsign and passcode + QString login = QString("user %1 pass %2 vers SDRangel 6.4.0%3\r\n").arg(m_settings.m_igateCallsign).arg(m_settings.m_igatePasscode).arg(m_settings.m_igateFilter.isEmpty() ? "" : QString(" filter %1").arg(m_settings.m_igateFilter)); + send(login.toLatin1(), login.length()); + m_loggedIn = true; + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("Connected")); + } + else if (packet.indexOf(QString("logresp %1 unverified").arg(m_settings.m_igateCallsign)) >= 0) + { + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(APRS::MsgReportWorker::create("Invalid IGate callsign or passcode")); + } + } + else if (packet.length() > 0) + { + // Forward to GUI + if (getMessageQueueToGUI()) + { + // Convert TNC2 to AX25 raw bytes + QByteArray ax25Packet = APRSPacket::toByteArray(packet); + getMessageQueueToGUI()->push(MainCore::MsgPacket::create(m_aprs, ax25Packet, QDateTime::currentDateTime())); + } + } + } +} + +void APRSWorker::send(const char *data, int length) +{ + if (m_settings.m_igateEnabled) + { + // Reopen connection if it was lost + if (!m_socket.isOpen()) + { + qDebug() << "APRSWorker::send: Reconnecting to " << m_settings.m_igateServer << ":" << m_settings.m_igatePort; + m_socket.connectToHost(m_settings.m_igateServer, m_settings.m_igatePort); + } + // Send data + qDebug() << "APRSWorker::send: " << QString(QByteArray(data, length)); + m_socket.write(data, length); + } +} diff --git a/plugins/feature/aprs/aprsworker.h b/plugins/feature/aprs/aprsworker.h new file mode 100644 index 000000000..2c9c36dd1 --- /dev/null +++ b/plugins/feature/aprs/aprsworker.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_APRSWORKER_H_ +#define INCLUDE_FEATURE_APRSWORKER_H_ + +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" + +#include "aprs.h" +#include "aprssettings.h" + +class WebAPIAdapterInterface; + +class APRSWorker : public QObject +{ + Q_OBJECT +public: + class MsgConfigureAPRSWorker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const APRSSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAPRSWorker* create(const APRSSettings& settings, bool force) + { + return new MsgConfigureAPRSWorker(settings, force); + } + + private: + APRSSettings m_settings; + bool m_force; + + MsgConfigureAPRSWorker(const APRSSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + APRSWorker(APRS *m_aprs, WebAPIAdapterInterface *webAPIAdapterInterface); + ~APRSWorker(); + void reset(); + bool startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + void setMessageQueueToFeature(MessageQueue *messageQueue) { m_msgQueueToFeature = messageQueue; } + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; } + +private: + + APRS *m_aprs; + WebAPIAdapterInterface *m_webAPIAdapterInterface; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object + MessageQueue *m_msgQueueToGUI; + APRSSettings m_settings; + bool m_running; + QMutex m_mutex; + QTcpSocket m_socket; + bool m_loggedIn; + + bool handleMessage(const Message& cmd); + void applySettings(const APRSSettings& settings, bool force = false); + MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; } + void send(const char *data, int length); + +private slots: + void handleInputMessages(); + void connected(); + void disconnected(); + void errorOccurred(QAbstractSocket::SocketError socketError); + void recv(); +}; + +#endif // INCLUDE_FEATURE_APRSWORKER_H_ diff --git a/plugins/feature/aprs/readme.md b/plugins/feature/aprs/readme.md new file mode 100644 index 000000000..7271bbf8f --- /dev/null +++ b/plugins/feature/aprs/readme.md @@ -0,0 +1,47 @@ +

APRS Feature Plugin

+ +

Introduction

+ +The APRS plugin displays APRS (Automatic Packet Reporting System) packets. APRS packets can be received over RF via one or more Packet Demodulator source channels or from the Internet via an APRS-IS IGate. + +

Interface

+ +![APRS feature plugin GUI](../../../doc/img/APRS_plugin.png) + +

1: Source channels

+ +This displays a list of the Packet Demodulator channels the APRS feature is receiving packets from. + +

2: Enable APRS-IS IGate

+ +When checked, enables the APRS-IS IGate (Internet gateway). The IGate forwards packets received via the Packet Demodulators to APRS-IS internet servers. +These servers collate packets from all IGates and allow them to be viewed on web sites such as https://aprs.fi, https://www.aprsdirect.com/ and http://ariss.net/ (the latter being for packets repeated via satellites and the ISS). +It is also possible to receive packets via the IGate, which allows you to see packets that you cannot receive via RF. + +

3: Show APRS Settings

+ +Pressing this button shows the APRS Settings Dialog. This dialog allows you to enter: + +* The APRS-IS server the IGate should connect to. Please choose your local server. (noam = North America, euro = Europe, etc). +* The callsign the IGate should connect with. +* The passcode corresponding to the given callsign. +* A serverside filter, that specifies which packets should be forwarded from the internet to SDRangel. See http://www.aprs-is.net/javAPRSFilter.aspx +m/50 will send you packets within 50 km of the last known position of the station corresponding to the callsign used to log in with. +If you do not have a corresponding station, you can specify a location by passing a latitude and longitude. E.g: r/lat/lon/50 + +

Map

+ +The APRS feature can plot APRS symbols and data on the Map. To use, simply open a Map feature and the APRS plugin will display packets it receives from that point on it. +Selecting an APRS item on the map will display a text bubble containing APRS status, position and weather data. + +![APRS map](../../../doc/img/APRS_map.png) + +

Attribution

+ +APRS icons are from: https://github.com/hessu/aprs-symbols + +

API

+ +Full details of the API can be found in the Swagger documentation. Here is a quick example of how to enable the APRS-IS IGate: + + curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "APRS", "APRSSettings": { "igateCallsign": "MYCALLSIGN", "igatePasscode": "12345", "igateFilter": "r/50.2/10.2/25", "igateEnabled": 1 }}' diff --git a/plugins/feature/gs232controller/gs232controller.cpp b/plugins/feature/gs232controller/gs232controller.cpp index 6efc567f2..57e182e81 100644 --- a/plugins/feature/gs232controller/gs232controller.cpp +++ b/plugins/feature/gs232controller/gs232controller.cpp @@ -20,33 +20,42 @@ #include #include #include +#include #include "SWGFeatureSettings.h" #include "SWGFeatureReport.h" #include "SWGFeatureActions.h" -#include "SWGSimplePTTReport.h" #include "SWGDeviceState.h" +#include "SWGTargetAzimuthElevation.h" #include "dsp/dspengine.h" +#include "device/deviceset.h" +#include "channel/channelapi.h" +#include "feature/featureset.h" +#include "maincore.h" -#include "gs232controllerworker.h" #include "gs232controller.h" +#include "gs232controllerworker.h" +#include "gs232controllerreport.h" MESSAGE_CLASS_DEFINITION(GS232Controller::MsgConfigureGS232Controller, Message) MESSAGE_CLASS_DEFINITION(GS232Controller::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(GS232Controller::MsgReportWorker, Message) const char* const GS232Controller::m_featureIdURI = "sdrangel.feature.gs232controller"; const char* const GS232Controller::m_featureId = "GS232Controller"; GS232Controller::GS232Controller(WebAPIAdapterInterface *webAPIAdapterInterface) : - Feature(m_featureIdURI, webAPIAdapterInterface), - m_ptt(false) + Feature(m_featureIdURI, webAPIAdapterInterface) { qDebug("GS232Controller::GS232Controller: webAPIAdapterInterface: %p", webAPIAdapterInterface); setObjectName(m_featureId); - m_worker = new GS232ControllerWorker(webAPIAdapterInterface); + m_worker = new GS232ControllerWorker(); m_state = StIdle; m_errorMessage = "GS232Controller error"; + m_selectedPipe = nullptr; + connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes())); + m_updatePipesTimer.start(1000); } GS232Controller::~GS232Controller() @@ -105,21 +114,46 @@ bool GS232Controller::handleMessage(const Message& cmd) return true; } - else if (GS232ControllerSettings::MsgChannelIndexChange::match(cmd)) + else if (MsgReportWorker::match(cmd)) { - GS232ControllerSettings::MsgChannelIndexChange& cfg = (GS232ControllerSettings::MsgChannelIndexChange&) cmd; - int newChannelIndex = cfg.getIndex(); - qDebug() << "GS232Controller::handleMessage: MsgChannelIndexChange: " << newChannelIndex; - GS232ControllerSettings settings = m_settings; - settings.m_channelIndex = newChannelIndex; - applySettings(settings, false); - - if (getMessageQueueToGUI()) + MsgReportWorker& report = (MsgReportWorker&) cmd; + if (report.getMessage() == "Connected") + m_state = StRunning; + else if (report.getMessage() == "Disconnected") + m_state = StIdle; + else { - GS232ControllerSettings::MsgChannelIndexChange *msg = new GS232ControllerSettings::MsgChannelIndexChange(cfg); - getMessageQueueToGUI()->push(msg); + m_state = StError; + m_errorMessage = report.getMessage(); + } + return true; + } + else if (MainCore::MsgTargetAzimuthElevation::match(cmd)) + { + // New target from another plugin + if ((m_state == StRunning) && m_settings.m_track) + { + MainCore::MsgTargetAzimuthElevation& msg = (MainCore::MsgTargetAzimuthElevation&) cmd; + // Is it from the selected pipe? + if (msg.getPipeSource() == m_selectedPipe) + { + if (getMessageQueueToGUI()) + { + // Forward to GUI - which will then send us updated settings + getMessageQueueToGUI()->push(new MainCore::MsgTargetAzimuthElevation(msg)); + } + else + { + // No GUI, so save target - applySettings will propagate to worker + SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = msg.getSWGTargetAzimuthElevation(); + m_settings.m_azimuth = swgTarget->getAzimuth(); + m_settings.m_elevation = swgTarget->getElevation(); + applySettings(m_settings); + } + } + else + qDebug() << "GS232Controller::handleMessage: No match " << msg.getPipeSource() << " " << m_selectedPipe; } - return true; } else @@ -128,6 +162,23 @@ bool GS232Controller::handleMessage(const Message& cmd) } } +void GS232Controller::updatePipes() +{ + QList availablePipes = updateAvailablePipeSources("target", GS232ControllerSettings::m_pipeTypes, GS232ControllerSettings::m_pipeURIs, this); + + if (availablePipes != m_availablePipes) + { + m_availablePipes = availablePipes; + if (getMessageQueueToGUI()) + { + MsgReportPipes *msgToGUI = MsgReportPipes::create(); + QList& msgAvailablePipes = msgToGUI->getAvailablePipes(); + msgAvailablePipes.append(availablePipes); + getMessageQueueToGUI()->push(msgToGUI); + } + } +} + QByteArray GS232Controller::serialize() const { return m_settings.serialize(); @@ -158,8 +209,7 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo << " m_serialPort: " << settings.m_serialPort << " m_baudRate: " << settings.m_baudRate << " m_track: " << settings.m_track - << " m_deviceIndex: " << settings.m_deviceIndex - << " m_channelIndex: " << settings.m_channelIndex + << " m_target: " << settings.m_target << " m_title: " << settings.m_title << " m_rgbColor: " << settings.m_rgbColor << " m_useReverseAPI: " << settings.m_useReverseAPI @@ -186,11 +236,24 @@ void GS232Controller::applySettings(const GS232ControllerSettings& settings, boo if ((m_settings.m_track != settings.m_track) || force) { reverseAPIKeys.append("track"); } - if ((m_settings.m_deviceIndex != settings.m_deviceIndex) || force) { - reverseAPIKeys.append("deviceIndex"); + if ((m_settings.m_target != settings.m_target) + || (!settings.m_target.isEmpty() && (m_selectedPipe == nullptr)) // Change in available pipes + || force) + { + if (!settings.m_target.isEmpty()) + { + m_selectedPipe = getPipeEndPoint(settings.m_target, m_availablePipes); + if (m_selectedPipe == nullptr) + qDebug() << "GS232Controller::applySettings: No plugin corresponding to target " << settings.m_target; + } + + reverseAPIKeys.append("target"); } - if ((m_settings.m_channelIndex != settings.m_channelIndex) || force) { - reverseAPIKeys.append("channelIndex"); + if ((m_settings.m_azimuthOffset != settings.m_azimuthOffset) || force) { + reverseAPIKeys.append("azimuthOffset"); + } + if ((m_settings.m_elevationOffset != settings.m_elevationOffset) || force) { + reverseAPIKeys.append("elevationOffset"); } if ((m_settings.m_title != settings.m_title) || force) { reverseAPIKeys.append("title"); @@ -252,7 +315,6 @@ int GS232Controller::webapiSettingsPutPatch( MsgConfigureGS232Controller *msg = MsgConfigureGS232Controller::create(settings, force); m_inputMessageQueue.push(msg); - qDebug("GS232Controller::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { MsgConfigureGS232Controller *msgToGUI = MsgConfigureGS232Controller::create(settings, force); @@ -273,8 +335,8 @@ void GS232Controller::webapiFormatFeatureSettings( response.getGs232ControllerSettings()->setSerialPort(new QString(settings.m_serialPort)); response.getGs232ControllerSettings()->setBaudRate(settings.m_baudRate); response.getGs232ControllerSettings()->setTrack(settings.m_track); - response.getGs232ControllerSettings()->setDeviceIndex(settings.m_deviceIndex); - response.getGs232ControllerSettings()->setChannelIndex(settings.m_channelIndex); + response.getGs232ControllerSettings()->setAzimuthOffset(settings.m_azimuthOffset); + response.getGs232ControllerSettings()->setElevationOffset(settings.m_elevationOffset); if (response.getGs232ControllerSettings()->getTitle()) { *response.getGs232ControllerSettings()->getTitle() = settings.m_title; @@ -292,8 +354,6 @@ void GS232Controller::webapiFormatFeatureSettings( } response.getGs232ControllerSettings()->setReverseApiPort(settings.m_reverseAPIPort); - response.getGs232ControllerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex); - response.getGs232ControllerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex); } void GS232Controller::webapiUpdateFeatureSettings( @@ -316,11 +376,14 @@ void GS232Controller::webapiUpdateFeatureSettings( if (featureSettingsKeys.contains("track")) { settings.m_track = response.getGs232ControllerSettings()->getTrack() != 0; } - if (featureSettingsKeys.contains("deviceIndex")) { - settings.m_deviceIndex = response.getGs232ControllerSettings()->getDeviceIndex(); + if (featureSettingsKeys.contains("target")) { + settings.m_target = *response.getGs232ControllerSettings()->getTarget(); } - if (featureSettingsKeys.contains("channelIndex")) { - settings.m_channelIndex = response.getGs232ControllerSettings()->getChannelIndex(); + if (featureSettingsKeys.contains("azimuthOffset")) { + settings.m_azimuthOffset = response.getGs232ControllerSettings()->getAzimuthOffset(); + } + if (featureSettingsKeys.contains("elevationOffset")) { + settings.m_elevationOffset = response.getGs232ControllerSettings()->getElevationOffset(); } if (featureSettingsKeys.contains("title")) { settings.m_title = *response.getGs232ControllerSettings()->getTitle(); @@ -337,12 +400,6 @@ void GS232Controller::webapiUpdateFeatureSettings( if (featureSettingsKeys.contains("reverseAPIPort")) { settings.m_reverseAPIPort = response.getGs232ControllerSettings()->getReverseApiPort(); } - if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) { - settings.m_reverseAPIFeatureSetIndex = response.getGs232ControllerSettings()->getReverseApiDeviceIndex(); - } - if (featureSettingsKeys.contains("reverseAPIChannelIndex")) { - settings.m_reverseAPIFeatureIndex = response.getGs232ControllerSettings()->getReverseApiChannelIndex(); - } } void GS232Controller::webapiReverseSendSettings(QList& featureSettingsKeys, const GS232ControllerSettings& settings, bool force) @@ -371,11 +428,14 @@ void GS232Controller::webapiReverseSendSettings(QList& featureSettingsK if (featureSettingsKeys.contains("track") || force) { swgGS232ControllerSettings->setTrack(settings.m_track); } - if (featureSettingsKeys.contains("deviceIndex") || force) { - swgGS232ControllerSettings->setDeviceIndex(settings.m_deviceIndex); + if (featureSettingsKeys.contains("target") || force) { + swgGS232ControllerSettings->setTarget(new QString(settings.m_target)); } - if (featureSettingsKeys.contains("channelIndex") || force) { - swgGS232ControllerSettings->setChannelIndex(settings.m_channelIndex); + if (featureSettingsKeys.contains("azimuthOffset") || force) { + swgGS232ControllerSettings->setAzimuthOffset(settings.m_azimuthOffset); + } + if (featureSettingsKeys.contains("elevationOffset") || force) { + swgGS232ControllerSettings->setElevationOffset(settings.m_elevationOffset); } if (featureSettingsKeys.contains("title") || force) { swgGS232ControllerSettings->setTitle(new QString(settings.m_title)); diff --git a/plugins/feature/gs232controller/gs232controller.h b/plugins/feature/gs232controller/gs232controller.h index 124b5942b..740aec52b 100644 --- a/plugins/feature/gs232controller/gs232controller.h +++ b/plugins/feature/gs232controller/gs232controller.h @@ -21,6 +21,7 @@ #include #include +#include #include "feature/feature.h" #include "util/message.h" @@ -81,6 +82,25 @@ public: { } }; + class MsgReportWorker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QString getMessage() { return m_message; } + + static MsgReportWorker* create(QString message) { + return new MsgReportWorker(message); + } + + private: + QString m_message; + + MsgReportWorker(QString message) : + Message(), + m_message(message) + {} + }; + GS232Controller(WebAPIAdapterInterface *webAPIAdapterInterface); virtual ~GS232Controller(); virtual void destroy() { delete this; } @@ -122,7 +142,9 @@ private: QThread m_thread; GS232ControllerWorker *m_worker; GS232ControllerSettings m_settings; - bool m_ptt; + QList m_availablePipes; + PipeEndPoint *m_selectedPipe; + QTimer m_updatePipesTimer; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; @@ -133,6 +155,7 @@ private: void webapiReverseSendSettings(QList& featureSettingsKeys, const GS232ControllerSettings& settings, bool force); private slots: + void updatePipes(); void networkManagerFinished(QNetworkReply *reply); }; diff --git a/plugins/feature/gs232controller/gs232controllergui.cpp b/plugins/feature/gs232controller/gs232controllergui.cpp index 6d2e3abad..9ed62c6f0 100644 --- a/plugins/feature/gs232controller/gs232controllergui.cpp +++ b/plugins/feature/gs232controller/gs232controllergui.cpp @@ -21,6 +21,8 @@ #include #include +#include "SWGTargetAzimuthElevation.h" + #include "feature/featureuiset.h" #include "gui/basicfeaturesettingsdialog.h" #include "mainwindow.h" @@ -51,7 +53,6 @@ void GS232ControllerGUI::resetToDefaults() QByteArray GS232ControllerGUI::serialize() const { - qDebug("GS232ControllerGUI::serialize: %d", m_settings.m_channelIndex); return m_settings.serialize(); } @@ -59,8 +60,6 @@ bool GS232ControllerGUI::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { - qDebug("GS232ControllerGUI::deserialize: %d", m_settings.m_channelIndex); - updateDeviceSetList(); displaySettings(); applySettings(true); return true; @@ -85,30 +84,28 @@ bool GS232ControllerGUI::handleMessage(const Message& message) return true; } - else if (GS232ControllerSettings::MsgChannelIndexChange::match(message)) + else if (PipeEndPoint::MsgReportPipes::match(message)) { - const GS232ControllerSettings::MsgChannelIndexChange& cfg = (GS232ControllerSettings::MsgChannelIndexChange&) message; - int newChannelIndex = cfg.getIndex(); - qDebug("GS232ControllerGUI::handleMessage: GS232ControllerSettings::MsgChannelIndexChange: %d", newChannelIndex); - ui->channel->blockSignals(true); - ui->channel->setCurrentIndex(newChannelIndex); - m_settings.m_channelIndex = newChannelIndex; - ui->channel->blockSignals(false); - + PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message; + m_availablePipes = report.getAvailablePipes(); + updatePipeList(); return true; - } else if (GS232ControllerReport::MsgReportAzAl::match(message)) + } + else if (GS232ControllerReport::MsgReportAzAl::match(message)) { GS232ControllerReport::MsgReportAzAl& azAl = (GS232ControllerReport::MsgReportAzAl&) message; - if (azAl.getType() == GS232ControllerReport::AzAlType::TARGET) - { - ui->azimuth->setValue(round(azAl.getAzimuth())); - ui->elevation->setValue(round(azAl.getElevation())); - } - else - { - ui->azimuthCurrentText->setText(QString("%1").arg(round(azAl.getAzimuth()))); - ui->elevationCurrentText->setText(QString("%1").arg(round(azAl.getElevation()))); - } + ui->azimuthCurrentText->setText(QString("%1").arg(round(azAl.getAzimuth()))); + ui->elevationCurrentText->setText(QString("%1").arg(round(azAl.getElevation()))); + return true; + } + else if (MainCore::MsgTargetAzimuthElevation::match(message)) + { + MainCore::MsgTargetAzimuthElevation& msg = (MainCore::MsgTargetAzimuthElevation&) message; + SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = msg.getSWGTargetAzimuthElevation(); + + ui->azimuth->setValue(round(swgTarget->getAzimuth())); + ui->elevation->setValue(round(swgTarget->getElevation())); + ui->targetName->setText(*swgTarget->getName()); return true; } @@ -157,7 +154,6 @@ GS232ControllerGUI::GS232ControllerGUI(PluginAPI* pluginAPI, FeatureUISet *featu m_statusTimer.start(1000); updateSerialPortList(); - updateDeviceSetList(); displaySettings(); applySettings(true); } @@ -183,6 +179,9 @@ void GS232ControllerGUI::displaySettings() ui->serialPort->lineEdit()->setText(m_settings.m_serialPort); ui->baudRate->setCurrentText(QString("%1").arg(m_settings.m_baudRate)); ui->track->setChecked(m_settings.m_track); + ui->targets->setCurrentIndex(ui->targets->findText(m_settings.m_target)); + ui->azimuthOffset->setValue(m_settings.m_azimuthOffset); + ui->elevationOffset->setValue(m_settings.m_elevationOffset); blockApplySettings(false); } @@ -198,102 +197,34 @@ void GS232ControllerGUI::updateSerialPortList() } } -void GS232ControllerGUI::updateDeviceSetList() +void GS232ControllerGUI::updatePipeList() { - MainWindow *mainWindow = MainWindow::getInstance(); - std::vector& deviceUISets = mainWindow->getDeviceUISets(); - std::vector::const_iterator it = deviceUISets.begin(); + QString currentText = ui->targets->currentText(); + ui->targets->blockSignals(true); + ui->targets->clear(); + QList::const_iterator it = m_availablePipes.begin(); - ui->device->blockSignals(true); - - ui->device->clear(); - unsigned int deviceIndex = 0; - - for (; it != deviceUISets.end(); ++it, deviceIndex++) + for (int i = 0; it != m_availablePipes.end(); ++it, i++) { - DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine; - - if (deviceSourceEngine) { - ui->device->addItem(QString("R%1").arg(deviceIndex), deviceIndex); - } + ui->targets->addItem(it->getName()); } - int newDeviceIndex; - - if (it != deviceUISets.begin()) + if (currentText.isEmpty()) { - if (m_settings.m_deviceIndex < 0) { - ui->device->setCurrentIndex(0); - } else { - ui->device->setCurrentIndex(m_settings.m_deviceIndex); - } - - newDeviceIndex = ui->device->currentData().toInt(); + if (m_availablePipes.size() > 0) + ui->targets->setCurrentIndex(0); } else + ui->targets->setCurrentIndex(ui->targets->findText(currentText)); + ui->targets->blockSignals(false); + + QString newText = ui->targets->currentText(); + if (currentText != newText) { - newDeviceIndex = -1; + m_settings.m_target = newText; + ui->targetName->setText(""); + applySettings(); } - - - if (newDeviceIndex != m_settings.m_deviceIndex) - { - qDebug("GS232ControllerGUI::updateDeviceSetLists: device index changed: %d", newDeviceIndex); - m_settings.m_deviceIndex = newDeviceIndex; - } - - updateChannelList(); - - ui->device->blockSignals(false); -} - -bool GS232ControllerGUI::updateChannelList() -{ - int newChannelIndex; - ui->channel->blockSignals(true); - ui->channel->clear(); - - if (m_settings.m_deviceIndex < 0) - { - newChannelIndex = -1; - } - else - { - MainWindow *mainWindow = MainWindow::getInstance(); - std::vector& deviceUISets = mainWindow->getDeviceUISets(); - DeviceUISet *deviceUISet = deviceUISets[m_settings.m_deviceIndex]; - int nbChannels = deviceUISet->getNumberOfChannels(); - - for (int ch = 0; ch < nbChannels; ch++) { - ui->channel->addItem(QString("%1").arg(ch), ch); - } - - if (nbChannels > 0) - { - if (m_settings.m_channelIndex < 0) { - ui->channel->setCurrentIndex(0); - } else { - ui->channel->setCurrentIndex(m_settings.m_channelIndex); - } - - newChannelIndex = ui->channel->currentIndex(); - } - else - { - newChannelIndex = -1; - } - } - - ui->channel->blockSignals(false); - - if (newChannelIndex != m_settings.m_channelIndex) - { - qDebug("GS232ControllerGUI::updateChannelList: channel index changed: %d", newChannelIndex); - m_settings.m_channelIndex = newChannelIndex; - return true; - } - - return false; } void GS232ControllerGUI::leaveEvent(QEvent*) @@ -346,32 +277,6 @@ void GS232ControllerGUI::on_startStop_toggled(bool checked) } } -void GS232ControllerGUI::on_devicesRefresh_clicked() -{ - updateDeviceSetList(); - displaySettings(); - applySettings(); -} - -void GS232ControllerGUI::on_device_currentIndexChanged(int index) -{ - if (index >= 0) - { - m_settings.m_deviceIndex = ui->device->currentData().toInt(); - updateChannelList(); - applySettings(); - } -} - -void GS232ControllerGUI::on_channel_currentIndexChanged(int index) -{ - if (index >= 0) - { - m_settings.m_channelIndex = index; - applySettings(); - } -} - void GS232ControllerGUI::on_serialPort_currentIndexChanged(int index) { (void) index; @@ -389,23 +294,43 @@ void GS232ControllerGUI::on_baudRate_currentIndexChanged(int index) void GS232ControllerGUI::on_azimuth_valueChanged(int value) { m_settings.m_azimuth = value; + ui->targetName->setText(""); applySettings(); } void GS232ControllerGUI::on_elevation_valueChanged(int value) { m_settings.m_elevation = value; + ui->targetName->setText(""); + applySettings(); +} + +void GS232ControllerGUI::on_azimuthOffset_valueChanged(int value) +{ + m_settings.m_azimuthOffset = value; + applySettings(); +} + +void GS232ControllerGUI::on_elevationOffset_valueChanged(int value) +{ + m_settings.m_elevationOffset = value; applySettings(); } void GS232ControllerGUI::on_track_stateChanged(int state) { m_settings.m_track = state == Qt::Checked; - ui->devicesRefresh->setEnabled(m_settings.m_track); - ui->deviceLabel->setEnabled(m_settings.m_track); - ui->device->setEnabled(m_settings.m_track); - ui->channelLabel->setEnabled(m_settings.m_track); - ui->channel->setEnabled(m_settings.m_track); + ui->targetsLabel->setEnabled(m_settings.m_track); + ui->targets->setEnabled(m_settings.m_track); + if (!m_settings.m_track) + ui->targetName->setText(""); + applySettings(); +} + +void GS232ControllerGUI::on_targets_currentTextChanged(const QString& text) +{ + m_settings.m_target = text; + ui->targetName->setText(""); applySettings(); } @@ -415,15 +340,23 @@ void GS232ControllerGUI::updateStatus() if (m_lastFeatureState != state) { + // We set checked state of start/stop button, in case it was changed via API + bool oldState; switch (state) { case Feature::StNotStarted: ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); break; case Feature::StIdle: + oldState = ui->startStop->blockSignals(true); + ui->startStop->setChecked(false); + ui->startStop->blockSignals(oldState); ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); break; case Feature::StRunning: + oldState = ui->startStop->blockSignals(true); + ui->startStop->setChecked(true); + ui->startStop->blockSignals(oldState); ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); break; case Feature::StError: diff --git a/plugins/feature/gs232controller/gs232controllergui.h b/plugins/feature/gs232controller/gs232controllergui.h index a8a41f62c..b43356a3d 100644 --- a/plugins/feature/gs232controller/gs232controllergui.h +++ b/plugins/feature/gs232controller/gs232controllergui.h @@ -23,6 +23,7 @@ #include "feature/featuregui.h" #include "util/messagequeue.h" +#include "pipes/pipeendpoint.h" #include "gs232controllersettings.h" class PluginAPI; @@ -50,6 +51,7 @@ private: FeatureUISet* m_featureUISet; GS232ControllerSettings m_settings; bool m_doApplySettings; + QList m_availablePipes; GS232Controller* m_gs232Controller; MessageQueue m_inputMessageQueue; @@ -62,9 +64,8 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); + void updatePipeList(); void updateSerialPortList(); - void updateDeviceSetList(); - bool updateChannelList(); //!< true if channel index has changed bool handleMessage(const Message& message); void leaveEvent(QEvent*); @@ -75,14 +76,14 @@ private slots: void onWidgetRolled(QWidget* widget, bool rollDown); void handleInputMessages(); void on_startStop_toggled(bool checked); - void on_devicesRefresh_clicked(); - void on_device_currentIndexChanged(int index); - void on_channel_currentIndexChanged(int index); void on_serialPort_currentIndexChanged(int index); void on_baudRate_currentIndexChanged(int index); void on_track_stateChanged(int state); void on_azimuth_valueChanged(int value); void on_elevation_valueChanged(int value); + void on_targets_currentTextChanged(const QString& text); + void on_azimuthOffset_valueChanged(int value); + void on_elevationOffset_valueChanged(int value); void updateStatus(); }; diff --git a/plugins/feature/gs232controller/gs232controllergui.ui b/plugins/feature/gs232controller/gs232controllergui.ui index 5189f070b..71b594c3c 100644 --- a/plugins/feature/gs232controller/gs232controllergui.ui +++ b/plugins/feature/gs232controller/gs232controllergui.ui @@ -7,7 +7,7 @@ 0 0 320 - 187 + 175
@@ -24,7 +24,7 @@ - 320 + 350 16777215 @@ -43,7 +43,7 @@ 10 10 301 - 171 + 161 @@ -179,6 +179,74 @@
+ + + + + + Check to enable automatic tracking of azimuth and elevation from the specified channel + + + Track + + + + + + + Source + + + + + + + + 150 + 0 + + + + Target to track + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Target + + + + + + + Name of the target being tracked as indicated by the source channel / feature + + + true + + + + + @@ -271,107 +339,50 @@ - + - - - Check to enable automatic tracking of azimuth and elevation from the specified channel - + - Track + Azimuth offset - - - - 24 - 16777215 - - + - Refresh indexes of available device sets + Specify an offset angel in degrees that will be added to the target azimuth to correct for misalignment - - + + -360 - - - :/recycle.png:/recycle.png + + 360 - + - Device + Elevation offset - - - - 55 - 0 - - + - Receiver deviceset index + Specify an offset angle in degrees that will be added to the target elevation to correct for misalignment + + + -180 + + + 180 + + + 1 - - - - Channel - - - - - - - - 55 - 0 - - - - Channel index - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -390,6 +401,18 @@
gui/buttonswitch.h
+ + startStop + azimuth + elevation + track + targets + targetName + serialPort + baudRate + azimuthOffset + elevationOffset + diff --git a/plugins/feature/gs232controller/gs232controllerreport.h b/plugins/feature/gs232controller/gs232controllerreport.h index 8e175d8fc..52699961e 100644 --- a/plugins/feature/gs232controller/gs232controllerreport.h +++ b/plugins/feature/gs232controller/gs232controllerreport.h @@ -27,7 +27,6 @@ class GS232ControllerReport : public QObject { Q_OBJECT public: - enum AzAlType {TARGET, ACTUAL}; class MsgReportAzAl : public Message { MESSAGE_CLASS_DECLARATION @@ -35,23 +34,20 @@ public: public: float getAzimuth() const { return m_azimuth; } float getElevation() const { return m_elevation; } - AzAlType getType() const { return m_type; } - static MsgReportAzAl* create(float azimuth, float elevation, AzAlType type) + static MsgReportAzAl* create(float azimuth, float elevation) { - return new MsgReportAzAl(azimuth, elevation, type); + return new MsgReportAzAl(azimuth, elevation); } private: float m_azimuth; float m_elevation; - AzAlType m_type; - MsgReportAzAl(float azimuth, float elevation, AzAlType type) : + MsgReportAzAl(float azimuth, float elevation) : Message(), m_azimuth(azimuth), - m_elevation(elevation), - m_type(type) + m_elevation(elevation) { } }; diff --git a/plugins/feature/gs232controller/gs232controllersettings.cpp b/plugins/feature/gs232controller/gs232controllersettings.cpp index 66f02acdb..16cdec690 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.cpp +++ b/plugins/feature/gs232controller/gs232controllersettings.cpp @@ -23,7 +23,15 @@ #include "gs232controllersettings.h" -MESSAGE_CLASS_DEFINITION(GS232ControllerSettings::MsgChannelIndexChange, Message) +const QStringList GS232ControllerSettings::m_pipeTypes = { + QStringLiteral("ADSBDemod"), + QStringLiteral("StarTracker") +}; + +const QStringList GS232ControllerSettings::m_pipeURIs = { + QStringLiteral("sdrangel.channel.adsbdemod"), + QStringLiteral("sdrangel.feature.startracker") +}; GS232ControllerSettings::GS232ControllerSettings() { @@ -37,8 +45,7 @@ void GS232ControllerSettings::resetToDefaults() m_serialPort = ""; m_baudRate = 9600; m_track = false; - m_deviceIndex = -1; - m_channelIndex = -1; + m_target = ""; m_title = "GS-232 Rotator Controller"; m_rgbColor = QColor(225, 25, 99).rgb(); m_useReverseAPI = false; @@ -46,6 +53,8 @@ void GS232ControllerSettings::resetToDefaults() m_reverseAPIPort = 8888; m_reverseAPIFeatureSetIndex = 0; m_reverseAPIFeatureIndex = 0; + m_azimuthOffset = 0; + m_elevationOffset = 0; } QByteArray GS232ControllerSettings::serialize() const @@ -57,8 +66,7 @@ QByteArray GS232ControllerSettings::serialize() const s.writeString(3, m_serialPort); s.writeS32(4, m_baudRate); s.writeBool(5, m_track); - s.writeS32(6, m_deviceIndex); - s.writeS32(7, m_channelIndex); + s.writeString(6, m_target); s.writeString(8, m_title); s.writeU32(9, m_rgbColor); s.writeBool(10, m_useReverseAPI); @@ -66,6 +74,8 @@ QByteArray GS232ControllerSettings::serialize() const s.writeU32(12, m_reverseAPIPort); s.writeU32(13, m_reverseAPIFeatureSetIndex); s.writeU32(14, m_reverseAPIFeatureIndex); + s.writeS32(15, m_azimuthOffset); + s.writeS32(16, m_elevationOffset); return s.final(); } @@ -91,8 +101,7 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data) d.readString(3, &m_serialPort, ""); d.readS32(4, &m_baudRate, 9600); d.readBool(5, &m_track, false); - d.readS32(6, &m_deviceIndex, -1); - d.readS32(7, &m_channelIndex, -1); + d.readString(6, &m_target, ""); d.readString(8, &m_title, "GS-232 Rotator Controller"); d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb()); d.readBool(10, &m_useReverseAPI, false); @@ -109,6 +118,8 @@ bool GS232ControllerSettings::deserialize(const QByteArray& data) m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; d.readU32(14, &utmp, 0); m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + d.readS32(15, &m_azimuthOffset, 0); + d.readS32(16, &m_elevationOffset, 0); return true; } diff --git a/plugins/feature/gs232controller/gs232controllersettings.h b/plugins/feature/gs232controller/gs232controllersettings.h index 01cd25992..271f11239 100644 --- a/plugins/feature/gs232controller/gs232controllersettings.h +++ b/plugins/feature/gs232controller/gs232controllersettings.h @@ -28,32 +28,14 @@ class Serializable; struct GS232ControllerSettings { - class MsgChannelIndexChange : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool getIndex() const { return m_index; } - - static MsgChannelIndexChange* create(int index) { - return new MsgChannelIndexChange(index); - } - - protected: - int m_index; - - MsgChannelIndexChange(int index) : - Message(), - m_index(index) - { } - }; - int m_azimuth; int m_elevation; QString m_serialPort; int m_baudRate; - int m_deviceIndex; bool m_track; - int m_channelIndex; + QString m_target; // Plugin to get az/el from. E.g: "R0:0 ADSBDemod". Use a string, so can be set via WebAPI + int m_azimuthOffset; + int m_elevationOffset; QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; @@ -66,6 +48,9 @@ struct GS232ControllerSettings void resetToDefaults(); QByteArray serialize() const; bool deserialize(const QByteArray& data); + + static const QStringList m_pipeTypes; + static const QStringList m_pipeURIs; }; #endif // INCLUDE_FEATURE_GS232CONTROLLERSETTINGS_H_ diff --git a/plugins/feature/gs232controller/gs232controllerworker.cpp b/plugins/feature/gs232controller/gs232controllerworker.cpp index 59af9333b..25ba64ac0 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.cpp +++ b/plugins/feature/gs232controller/gs232controllerworker.cpp @@ -24,25 +24,14 @@ #include #include -#include "SWGDeviceState.h" -#include "SWGSuccessResponse.h" -#include "SWGErrorResponse.h" -#include "SWGDeviceSettings.h" -#include "SWGChannelSettings.h" -#include "SWGDeviceSet.h" -#include "SWGChannelReport.h" - -#include "webapi/webapiadapterinterface.h" -#include "webapi/webapiutils.h" - +#include "gs232controller.h" #include "gs232controllerworker.h" #include "gs232controllerreport.h" MESSAGE_CLASS_DEFINITION(GS232ControllerWorker::MsgConfigureGS232ControllerWorker, Message) MESSAGE_CLASS_DEFINITION(GS232ControllerReport::MsgReportAzAl, Message) -GS232ControllerWorker::GS232ControllerWorker(WebAPIAdapterInterface *webAPIAdapterInterface) : - m_webAPIAdapterInterface(webAPIAdapterInterface), +GS232ControllerWorker::GS232ControllerWorker() : m_msgQueueToFeature(nullptr), m_msgQueueToGUI(nullptr), m_running(false), @@ -113,10 +102,10 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting qDebug() << "GS232ControllerWorker::applySettings:" << " m_azimuth: " << settings.m_azimuth << " m_elevation: " << settings.m_elevation + << " m_azimuthOffset: " << settings.m_azimuthOffset + << " m_elevationOffset: " << settings.m_elevationOffset << " m_serialPort: " << settings.m_serialPort << " m_baudRate: " << settings.m_baudRate - << " m_deviceIndex: " << settings.m_deviceIndex - << " m_channelIndex: " << settings.m_channelIndex << " force: " << force; if ((settings.m_serialPort != m_settings.m_serialPort) || force) @@ -126,34 +115,57 @@ void GS232ControllerWorker::applySettings(const GS232ControllerSettings& setting m_serialPort.setPortName(settings.m_serialPort); m_serialPort.setBaudRate(settings.m_baudRate); if (!m_serialPort.open(QIODevice::ReadWrite)) + { qCritical() << "GS232ControllerWorker::applySettings: Failed to open serial port " << settings.m_serialPort << ". Error: " << m_serialPort.error(); + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Failed to open serial port %1: %2").arg(settings.m_serialPort).arg(m_serialPort.error()))); + } } else if ((settings.m_baudRate != m_settings.m_baudRate) || force) { m_serialPort.setBaudRate(settings.m_baudRate); } - if ((settings.m_elevation != m_settings.m_elevation) || force) + if ((settings.m_elevation != m_settings.m_elevation) + || (settings.m_elevationOffset != m_settings.m_elevationOffset) + || force) { - setAzimuthElevation(settings.m_azimuth, settings.m_elevation); + setAzimuthElevation(settings.m_azimuth, settings.m_elevation, settings.m_azimuthOffset, settings.m_elevationOffset); } - else if ((settings.m_azimuth != m_settings.m_azimuth) || force) + else if ((settings.m_azimuth != m_settings.m_azimuth) + || (settings.m_azimuthOffset != m_settings.m_azimuthOffset) + || force) { - setAzimuth(settings.m_azimuth); + setAzimuth(settings.m_azimuth, settings.m_azimuthOffset); } m_settings = settings; } -void GS232ControllerWorker::setAzimuth(int azimuth) +void GS232ControllerWorker::setAzimuth(int azimuth, int azimuthOffset) { + azimuth += azimuthOffset; + if (azimuth < 0) + azimuth = 0; + else if (azimuth > 450) + azimuth = 450; QString cmd = QString("M%1\r\n").arg(azimuth, 3, 10, QLatin1Char('0')); QByteArray data = cmd.toLatin1(); m_serialPort.write(data); } -void GS232ControllerWorker::setAzimuthElevation(int azimuth, int elevation) +void GS232ControllerWorker::setAzimuthElevation(int azimuth, int elevation, int azimuthOffset, int elevationOffset) { + azimuth += azimuthOffset; + if (azimuth < 0) + azimuth = 0; + else if (azimuth > 450) + azimuth = 450; + elevation += elevationOffset; + if (elevation < 0) + elevation = 0; + else if (elevation > 180) + elevation = 180; QString cmd = QString("W%1 %2\r\n").arg(azimuth, 3, 10, QLatin1Char('0')).arg(elevation, 3, 10, QLatin1Char('0')); QByteArray data = cmd.toLatin1(); m_serialPort.write(data); @@ -170,7 +182,6 @@ void GS232ControllerWorker::readSerialData() if (len != -1) { QString response = QString::fromUtf8(buf, len); - QRegularExpression re("AZ=(\\d\\d\\d)EL=(\\d\\d\\d)"); QRegularExpressionMatch match = re.match(response); if (match.hasMatch()) @@ -179,15 +190,17 @@ void GS232ControllerWorker::readSerialData() QString el = match.captured(2); //qDebug() << "GS232ControllerWorker::readSerialData read az " << az << " el " << el; if (getMessageQueueToGUI()) - { - GS232ControllerReport::MsgReportAzAl *msg = GS232ControllerReport::MsgReportAzAl::create( - az.toFloat(), el.toFloat(), GS232ControllerReport::ACTUAL); - getMessageQueueToGUI()->push(msg); - } + getMessageQueueToGUI()->push( GS232ControllerReport::MsgReportAzAl::create(az.toFloat(), el.toFloat())); + } + else if (response == "\r\n") + { + // Ignore } else { - qDebug() << "GS232ControllerWorker::readSerialData unknown response " << response; + qDebug() << "GS232ControllerWorker::readSerialData - unexpected response " << response; + if (m_msgQueueToFeature) + m_msgQueueToFeature->push(GS232Controller::MsgReportWorker::create(QString("Unexpected GS-232 serial reponse: %1").arg(response))); } } } @@ -201,48 +214,4 @@ void GS232ControllerWorker::update() QByteArray cmd("C2\r\n"); m_serialPort.write(cmd); } - - // Request target Az/EL from channel - if (m_settings.m_track) - { - SWGSDRangel::SWGChannelReport response; - SWGSDRangel::SWGErrorResponse errorResponse; - - int httpRC = m_webAPIAdapterInterface->devicesetChannelReportGet( - m_settings.m_deviceIndex, - m_settings.m_channelIndex, - response, - errorResponse - ); - - if (httpRC/100 != 2) - { - qWarning("GS232ControllerWorker::update: get channel report error %d: %s", - httpRC, qPrintable(*errorResponse.getMessage())); - } - else - { - QJsonObject *jsonObj = response.asJsonObject(); - double targetAzimuth; - double targetElevation; - bool gotElevation = false; - bool gotAzimuth = false; - - if (WebAPIUtils::getSubObjectDouble(*jsonObj, "targetAzimuth", targetAzimuth)) - gotAzimuth = true; - - if (WebAPIUtils::getSubObjectDouble(*jsonObj, "targetElevation", targetElevation)) - gotElevation = true; - - if (gotAzimuth && gotElevation) - { - if (getMessageQueueToGUI()) - { - GS232ControllerReport::MsgReportAzAl *msg = GS232ControllerReport::MsgReportAzAl::create( - targetAzimuth, targetElevation, GS232ControllerReport::TARGET); - getMessageQueueToGUI()->push(msg); - } - } - } - } } diff --git a/plugins/feature/gs232controller/gs232controllerworker.h b/plugins/feature/gs232controller/gs232controllerworker.h index 2041e17c8..3be417fe2 100644 --- a/plugins/feature/gs232controller/gs232controllerworker.h +++ b/plugins/feature/gs232controller/gs232controllerworker.h @@ -28,8 +28,6 @@ #include "gs232controllersettings.h" -class WebAPIAdapterInterface; - class GS232ControllerWorker : public QObject { Q_OBJECT @@ -57,7 +55,7 @@ public: { } }; - GS232ControllerWorker(WebAPIAdapterInterface *webAPIAdapterInterface); + GS232ControllerWorker(); ~GS232ControllerWorker(); void reset(); bool startWork(); @@ -69,7 +67,6 @@ public: private: - WebAPIAdapterInterface *m_webAPIAdapterInterface; MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object MessageQueue *m_msgQueueToGUI; @@ -82,8 +79,8 @@ private: bool handleMessage(const Message& cmd); void applySettings(const GS232ControllerSettings& settings, bool force = false); MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; } - void setAzimuth(int azimuth); - void setAzimuthElevation(int azimuth, int elevation); + void setAzimuth(int azimuth, int azimuthOffset); + void setAzimuthElevation(int azimuth, int elevation, int azimuthOffset, int elevationOffset); private slots: void handleInputMessages(); diff --git a/plugins/feature/gs232controller/readme.md b/plugins/feature/gs232controller/readme.md index 3e55f92ea..51bff3925 100644 --- a/plugins/feature/gs232controller/readme.md +++ b/plugins/feature/gs232controller/readme.md @@ -4,11 +4,11 @@ The GS-232 Rotator Controller feature plugin allows SDRangel to send commands to GS-232 rotators. This allows SDRangel to point antennas mounted on a rotator to a specified azimuth and elevation. -Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the ADS-B Demodulator, which can track a selected aircraft. +Azimuth and elevation can be set manually by a user in the GUI, via the REST API, or via another plugin, such as the ADS-B Demodulator, which can track a selected aircraft, or the Star Tracker, for radio astronomy or EME communication.

Interface

-![File source channel plugin GUI](../../../doc/img/GS232Controller_plugin.png) +![GS232 Rotator Controller feature plugin GUI](../../../doc/img/GS232Controller_plugin.png)

1: Start/Stop plugin

@@ -16,37 +16,50 @@ This button starts or stops the plugin. When the plugin is stopped, azimuth and

2: Azimuth

-Specifies the target azimuth (angle in degrees, clockwise from North) to point the antenna towards. Valid values range from 0 to 450 degrees. The value to the right of the target azimuth, is the current azimuth read from the GS-232 rotator. +Specifies the target azimuth (angle in degrees, clockwise from North) to point the antenna towards. Valid values range from 0 to 450 degrees. +The value to the right of the target azimuth, is the current azimuth read from the GS-232 rotator.

3: Elevation

-Specifies the target elevation (angle in degrees) to point the antenna towards. Valid values range from 0 to 180 degrees, where 0 and 180 point towards the horizon and 90 degrees to zenith. The value to the right of the target elevation, is the current elevation read from the GS-232 rotator. +Specifies the target elevation (angle in degrees) to point the antenna towards. Valid values range from 0 to 180 degrees, where 0 and 180 point towards the horizon and 90 degrees to zenith. +The value to the right of the target elevation, is the current elevation read from the GS-232 rotator. -

4: Serial Port

+

4: Track

+ +When checked, the target azimuth and elevation will be controlled by the Channel or Feature Source (5). +For example, this allows an aircraft to be tracked, by setting the Source to the ADS-B Demodulator plugin, or the Moon to be tracked by settng Source to the Star Tracker plugin. + +

5: Source

+ +Specify the SDRangel Channel or Feature that that will control the target aziumth and elevation values, when Track (4) is checked. + +

6: Target

+ +When tracking is enabled, this field will display a name for the target being tracked, as indicated by the selected Source plugin (5). +For example, the ADS-B plugin will display the flight number of the target aircraft. The Star Tracker plugin will display Sun, Moon or Star. + +

7: Serial Port

Specifies the serial port (E.g. COM3 on Windows or /dev/ttyS0 on Linux) that will be used to send commands to the GS-232 rotator. -

5: Baud rate

+

8: Baud rate

Specifies the baud rate that will be used to send commands to the GS-232 rotator. Typically this is 9600. -

6: Track

+

9: Azimuth Offset

-When checked, the GS-232 Rotator Controller plugin will query the channel specified by the Device (8) and Channel (9) combo boxes for the target azimuth and elevation. For example, this allows an aircraft to be tracked, by setting the Device and Channel to correspond to the ADS-B Demodulator plugin. +The azimuth offset specifies an angle in degrees that is added to the target azimuth before sending to the controller. This allows for a misalignment of the rotator to be corrected. -

7: Refresh list of devices and channels

+

10: Elevation Offset

-Use this button to refresh the list of devices (8) and channels (9) +The elevation offset specifies an angle in degrees that is added to the target elevation before sending to the controller. This allows for a misalignment of the rotator to be corrected. -

8: Select device set

+

API

-Specify the SDRangel device set containing the channel plugin that will be asked for aziumth and elevation values. Defaults to R0. +Full details of the API can be found in the Swagger documentation. Here is a quick example of how to set the azimuth and elevation from the command line: -

9: Select channel

+ curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "GS232Controller", "GS232ControllerSettings": { "azimuth": 180, "elevation": 45 }}' -The channel index specifies the SDRangel channel that will be asked for azimuth and elevation values. Defaults to 0. +To start sending commands to the rotator: -

Developer Information

- -For a channel plugin to be able to set the azimuth and elevation, its channel report should contain targetAzimuth and targetElevation. See the ADS-B plugin as an example. - \ No newline at end of file + curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/run" diff --git a/plugins/feature/map/CMakeLists.txt b/plugins/feature/map/CMakeLists.txt new file mode 100644 index 000000000..b5c87a2a2 --- /dev/null +++ b/plugins/feature/map/CMakeLists.txt @@ -0,0 +1,56 @@ +project(map) + +set(map_SOURCES + map.cpp + mapsettings.cpp + mapplugin.cpp + mapwebapiadapter.cpp +) + +set(map_HEADERS + map.h + mapsettings.h + mapplugin.h + mapreport.h + mapwebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(map_SOURCES + ${map_SOURCES} + mapgui.cpp + mapgui.ui + map.qrc + ) + set(map_HEADERS + ${map_HEADERS} + mapgui.h + ) + + set(TARGET_NAME map) + set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME mapsrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${map_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/feature/map/map.cpp b/plugins/feature/map/map.cpp new file mode 100644 index 000000000..91558ccad --- /dev/null +++ b/plugins/feature/map/map.cpp @@ -0,0 +1,346 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "SWGFeatureSettings.h" +#include "SWGFeatureReport.h" +#include "SWGFeatureActions.h" +#include "SWGDeviceState.h" + +#include "dsp/dspengine.h" + +#include "device/deviceset.h" +#include "channel/channelapi.h" +#include "feature/featureset.h" +#include "maincore.h" +#include "map.h" + +MESSAGE_CLASS_DEFINITION(Map::MsgConfigureMap, Message) +MESSAGE_CLASS_DEFINITION(Map::MsgFind, Message) + +const char* const Map::m_featureIdURI = "sdrangel.feature.map"; +const char* const Map::m_featureId = "Map"; + +Map::Map(WebAPIAdapterInterface *webAPIAdapterInterface) : + Feature(m_featureIdURI, webAPIAdapterInterface) +{ + qDebug("Map::Map: webAPIAdapterInterface: %p", webAPIAdapterInterface); + setObjectName(m_featureId); + m_state = StIdle; + m_errorMessage = "Map error"; + connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes())); + m_updatePipesTimer.start(1000); +} + +Map::~Map() +{ +} + +bool Map::handleMessage(const Message& cmd) +{ + if (MsgConfigureMap::match(cmd)) + { + MsgConfigureMap& cfg = (MsgConfigureMap&) cmd; + qDebug() << "Map::handleMessage: MsgConfigureMap"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MainCore::MsgMapItem::match(cmd)) + { + MainCore::MsgMapItem& msgMapItem = (MainCore::MsgMapItem&) cmd; + MainCore::MsgMapItem *copy = new MainCore::MsgMapItem(msgMapItem); + getMessageQueueToGUI()->push(copy); + return true; + } + else + { + return false; + } +} + +void Map::updatePipes() +{ + QList availablePipes = updateAvailablePipeSources("mapitems", MapSettings::m_pipeTypes, MapSettings::m_pipeURIs, this); + + if (availablePipes != m_availablePipes) + { + m_availablePipes = availablePipes; + if (getMessageQueueToGUI()) + { + MsgReportPipes *msgToGUI = MsgReportPipes::create(); + QList& msgAvailablePipes = msgToGUI->getAvailablePipes(); + msgAvailablePipes.append(availablePipes); + getMessageQueueToGUI()->push(msgToGUI); + } + } +} + +QByteArray Map::serialize() const +{ + return m_settings.serialize(); +} + +bool Map::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureMap *msg = MsgConfigureMap::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureMap *msg = MsgConfigureMap::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void Map::applySettings(const MapSettings& settings, bool force) +{ + qDebug() << "Map::applySettings:" + << " m_displayNames: " << settings.m_displayNames + << " m_title: " << settings.m_title + << " m_rgbColor: " << settings.m_rgbColor + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex + << " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex + << " force: " << force; + + QList reverseAPIKeys; + + if ((m_settings.m_displayNames != settings.m_displayNames) || force) { + reverseAPIKeys.append("displayNames"); + } + if ((m_settings.m_title != settings.m_title) || force) { + reverseAPIKeys.append("title"); + } + if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) { + reverseAPIKeys.append("rgbColor"); + } + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) || + (m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +int Map::webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) run; + (void) errorMessage; + getFeatureStateStr(*response.getState()); + return 202; +} + +int Map::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setMapSettings(new SWGSDRangel::SWGMapSettings()); + response.getMapSettings()->init(); + webapiFormatFeatureSettings(response, m_settings); + return 200; +} + +int Map::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + MapSettings settings = m_settings; + webapiUpdateFeatureSettings(settings, featureSettingsKeys, response); + + MsgConfigureMap *msg = MsgConfigureMap::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureMap *msgToGUI = MsgConfigureMap::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatFeatureSettings(response, settings); + + return 200; +} + +int Map::webapiActionsPost( + const QStringList& featureActionsKeys, + SWGSDRangel::SWGFeatureActions& query, + QString& errorMessage) +{ + SWGSDRangel::SWGMapActions *swgMapActions = query.getMapActions(); + + if (swgMapActions) + { + if (featureActionsKeys.contains("find")) + { + QString id = *swgMapActions->getFind(); + + if (getMessageQueueToGUI()) + getMessageQueueToGUI()->push(MsgFind::create(id)); + } + + return 202; + } + else + { + errorMessage = "Missing MapActions in query"; + return 400; + } +} + +void Map::webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const MapSettings& settings) +{ + response.getMapSettings()->setDisplayNames(settings.m_displayNames ? 1 : 0); + + if (response.getMapSettings()->getTitle()) { + *response.getMapSettings()->getTitle() = settings.m_title; + } else { + response.getMapSettings()->setTitle(new QString(settings.m_title)); + } + + response.getMapSettings()->setRgbColor(settings.m_rgbColor); + response.getMapSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getMapSettings()->getReverseApiAddress()) { + *response.getMapSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getMapSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getMapSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getMapSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex); + response.getMapSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex); +} + +void Map::webapiUpdateFeatureSettings( + MapSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response) +{ + if (featureSettingsKeys.contains("displayNames")) { + settings.m_displayNames = response.getMapSettings()->getDisplayNames(); + } + if (featureSettingsKeys.contains("title")) { + settings.m_title = *response.getMapSettings()->getTitle(); + } + if (featureSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getMapSettings()->getRgbColor(); + } + if (featureSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getMapSettings()->getUseReverseApi() != 0; + } + if (featureSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getMapSettings()->getReverseApiAddress(); + } + if (featureSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getMapSettings()->getReverseApiPort(); + } + if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIFeatureSetIndex = response.getMapSettings()->getReverseApiDeviceIndex(); + } + if (featureSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIFeatureIndex = response.getMapSettings()->getReverseApiChannelIndex(); + } +} + +void Map::webapiReverseSendSettings(QList& featureSettingsKeys, const MapSettings& settings, bool force) +{ + SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings(); + // swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet()); + // swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex()); + swgFeatureSettings->setFeatureType(new QString("Map")); + swgFeatureSettings->setMapSettings(new SWGSDRangel::SWGMapSettings()); + SWGSDRangel::SWGMapSettings *swgMapSettings = swgFeatureSettings->getMapSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (featureSettingsKeys.contains("displayNames") || force) { + swgMapSettings->setDisplayNames(settings.m_displayNames); + } + if (featureSettingsKeys.contains("title") || force) { + swgMapSettings->setTitle(new QString(settings.m_title)); + } + if (featureSettingsKeys.contains("rgbColor") || force) { + swgMapSettings->setRgbColor(settings.m_rgbColor); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIFeatureSetIndex) + .arg(settings.m_reverseAPIFeatureIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgFeatureSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgFeatureSettings; +} + +void Map::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "Map::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("Map::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/feature/map/map.h b/plugins/feature/map/map.h new file mode 100644 index 000000000..0379cef7c --- /dev/null +++ b/plugins/feature/map/map.h @@ -0,0 +1,144 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_MAP_H_ +#define INCLUDE_FEATURE_MAP_H_ + +#include +#include +#include +#include + +#include "feature/feature.h" +#include "util/message.h" + +#include "mapsettings.h" + +class WebAPIAdapterInterface; +class QNetworkAccessManager; +class QNetworkReply; + +namespace SWGSDRangel { + class SWGDeviceState; +} + +class Map : public Feature +{ + Q_OBJECT +public: + class MsgConfigureMap : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const MapSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureMap* create(const MapSettings& settings, bool force) { + return new MsgConfigureMap(settings, force); + } + + private: + MapSettings m_settings; + bool m_force; + + MsgConfigureMap(const MapSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFind : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QString getTarget() const { return m_target; } + + static MsgFind* create(const QString& target) { + return new MsgFind(target); + } + + private: + QString m_target; + + MsgFind(const QString& target) : + Message(), + m_target(target) + {} + }; + + Map(WebAPIAdapterInterface *webAPIAdapterInterface); + virtual ~Map(); + virtual void destroy() { delete this; } + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) const { id = objectName(); } + virtual void getTitle(QString& title) const { title = m_settings.m_title; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiActionsPost( + const QStringList& featureActionsKeys, + SWGSDRangel::SWGFeatureActions& query, + QString& errorMessage); + + static void webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const MapSettings& settings); + + static void webapiUpdateFeatureSettings( + MapSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response); + + static const char* const m_featureIdURI; + static const char* const m_featureId; + +private: + QThread m_thread; + MapSettings m_settings; + QList m_availablePipes; + QTimer m_updatePipesTimer; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const MapSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& featureSettingsKeys, const MapSettings& settings, bool force); + +private slots: + void updatePipes(); + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_FEATURE_MAP_H_ diff --git a/plugins/feature/map/map.qrc b/plugins/feature/map/map.qrc new file mode 100644 index 000000000..4299b3178 --- /dev/null +++ b/plugins/feature/map/map.qrc @@ -0,0 +1,7 @@ + + + map/map.qml + map/MapStation.qml + map/antenna.png + + diff --git a/plugins/feature/map/map/MapStation.qml b/plugins/feature/map/map/MapStation.qml new file mode 100644 index 000000000..a69346e46 --- /dev/null +++ b/plugins/feature/map/map/MapStation.qml @@ -0,0 +1,40 @@ +import QtQuick 2.12 +import QtLocation 5.12 +import QtPositioning 5.12 + +MapQuickItem { + id: station + property string stationName // Name of the station, E.g. Home + + coordinate: QtPositioning.coordinate(51.5, 0.125) // Location of the antenna (QTH) - London + zoomLevel: 11 + + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + + sourceItem: Grid { + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + layer.enabled: true + layer.smooth: true + Image { + id: image + source: "antenna.png" + } + Rectangle { + id: bubble + color: "lightblue" + border.width: 1 + width: text.width * 1.3 + height: text.height * 1.3 + radius: 5 + Text { + id: text + anchors.centerIn: parent + text: stationName + } + } + } + } +} diff --git a/plugins/feature/map/map/antenna.png b/plugins/feature/map/map/antenna.png new file mode 100644 index 000000000..f13c91881 Binary files /dev/null and b/plugins/feature/map/map/antenna.png differ diff --git a/plugins/feature/map/map/map.qml b/plugins/feature/map/map/map.qml new file mode 100644 index 000000000..54d78d096 --- /dev/null +++ b/plugins/feature/map/map/map.qml @@ -0,0 +1,102 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtLocation 5.12 +import QtPositioning 5.12 + +Item { + id: qmlMap + property int mapZoomLevel: 11 + + Plugin { + id: mapPlugin + name: "osm" + } + + Map { + id: map + objectName: "map" + anchors.fill: parent + plugin: mapPlugin + center: QtPositioning.coordinate(51.5, 0.125) // London + zoomLevel: 10 + + + MapItemView { + model: mapModel + delegate: mapComponent + } + + MapStation { + id: station + objectName: "station" + stationName: "Home" + coordinate: QtPositioning.coordinate(51.5, 0.125) + } + + onZoomLevelChanged: { + if (zoomLevel > 11) { + station.zoomLevel = zoomLevel + mapZoomLevel = zoomLevel + } else { + station.zoomLevel = 11 + mapZoomLevel = 11 + } + } + + } + + Component { + id: mapComponent + MapQuickItem { + id: mapElement + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + coordinate: position + zoomLevel: mapImageFixedSize ? zoomLevel : mapZoomLevel + + sourceItem: Grid { + columns: 1 + Grid { + horizontalItemAlignment: Grid.AlignHCenter + columnSpacing: 5 + layer.enabled: true + layer.smooth: true + Image { + id: image + rotation: mapImageRotation + source: mapImage + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: (mouse) => { + selected = !selected + } + } + } + Rectangle { + id: bubble + color: bubbleColour + border.width: 1 + width: text.width + 5 + height: text.height + 5 + radius: 5 + visible: mapTextVisible + Text { + id: text + anchors.centerIn: parent + text: mapText + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: (mouse) => { + selected = !selected + } + } + } + } + } + } + } + +} diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp new file mode 100644 index 000000000..bd854e44f --- /dev/null +++ b/plugins/feature/map/mapgui.cpp @@ -0,0 +1,382 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "feature/featureuiset.h" +#include "gui/basicfeaturesettingsdialog.h" +#include "mainwindow.h" +#include "device/deviceuiset.h" +#include "util/units.h" +#include "util/maidenhead.h" + +#include "ui_mapgui.h" +#include "map.h" +#include "mapgui.h" +#include "SWGMapItem.h" + +QVariant MapModel::data(const QModelIndex &index, int role) const +{ + int row = index.row(); + + if ((row < 0) || (row >= m_items.count())) + return QVariant(); + if (role == MapModel::positionRole) + { + // Coordinates to display the item at + QGeoCoordinate coords; + coords.setLatitude(m_items[row]->m_latitude); + coords.setLongitude(m_items[row]->m_longitude); + return QVariant::fromValue(coords); + } + else if (role == MapModel::mapTextRole) + { + // Create the text to go in the bubble next to the image + if (m_selected[row]) + return QVariant::fromValue(m_items[row]->m_text); + else + return QVariant::fromValue(m_items[row]->m_name); + } + else if (role == MapModel::mapTextVisibleRole) + { + return QVariant::fromValue(m_selected[row] || m_displayNames); + } + else if (role == MapModel::mapImageRole) + { + // Set an image to use + return QVariant::fromValue(m_items[row]->m_image); + } + else if (role == MapModel::mapImageRotationRole) + { + // Angle to rotate image by + return QVariant::fromValue(m_items[row]->m_imageRotation); + } + else if (role == MapModel::mapImageFixedSizeRole) + { + // Whether image changes size with zoom level + return QVariant::fromValue(m_items[row]->m_imageFixedSize); + } + else if (role == MapModel::bubbleColourRole) + { + // Select a background colour for the text bubble next to the item + if (m_selected[row]) + return QVariant::fromValue(QColor("lightgreen")); + else + return QVariant::fromValue(QColor("lightblue")); + } + else if (role == MapModel::selectedRole) + return QVariant::fromValue(m_selected[row]); + return QVariant(); +} + +bool MapModel::setData(const QModelIndex &index, const QVariant& value, int role) +{ + int row = index.row(); + if ((row < 0) || (row >= m_items.count())) + return false; + if (role == MapModel::selectedRole) + { + m_selected[row] = value.toBool(); + emit dataChanged(index, index); + return true; + } + return true; +} + +MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) +{ + MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature); + return gui; +} + +void MapGUI::destroy() +{ + delete this; +} + +void MapGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray MapGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool MapGUI::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + displaySettings(); + applySettings(true); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +bool MapGUI::handleMessage(const Message& message) +{ + if (Map::MsgConfigureMap::match(message)) + { + qDebug("MapGUI::handleMessage: Map::MsgConfigureMap"); + const Map::MsgConfigureMap& cfg = (Map::MsgConfigureMap&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (PipeEndPoint::MsgReportPipes::match(message)) + { + PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message; + m_availablePipes = report.getAvailablePipes(); + updatePipeList(); + + return true; + } + else if (Map::MsgFind::match(message)) + { + Map::MsgFind& msgFind = (Map::MsgFind&) message; + find(msgFind.getTarget()); + return true; + } + else if (MainCore::MsgMapItem::match(message)) + { + MainCore::MsgMapItem& msgMapItem = (MainCore::MsgMapItem&) message; + SWGSDRangel::SWGMapItem *swgMapItem = msgMapItem.getSWGMapItem(); + m_mapModel.update(msgMapItem.getPipeSource(), swgMapItem); + return true; + } + + return false; +} + +void MapGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop())) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +void MapGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) : + FeatureGUI(parent), + ui(new Ui::MapGUI), + m_pluginAPI(pluginAPI), + m_featureUISet(featureUISet), + m_doApplySettings(true), + m_mapModel(this) +{ + ui->setupUi(this); + + ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel); + ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml"))); + + setAttribute(Qt::WA_DeleteOnClose, true); + setChannelWidget(false); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + m_map = reinterpret_cast(feature); + m_map->setMessageQueueToGUI(&m_inputMessageQueue); + + m_featureUISet->addRollupWidget(this); + + // Get station position + float stationLatitude = MainCore::instance()->getSettings().getLatitude(); + float stationLongitude = MainCore::instance()->getSettings().getLongitude(); + float stationAltitude = MainCore::instance()->getSettings().getAltitude(); + + // Centre map at My Position + QQuickItem *item = ui->map->rootObject(); + QObject *object = item->findChild("map"); + if(object != NULL) + { + QGeoCoordinate coords = object->property("center").value(); + coords.setLatitude(stationLatitude); + coords.setLongitude(stationLongitude); + object->setProperty("center", QVariant::fromValue(coords)); + } + // Move antenna icon to My Position to start with + QObject *stationObject = item->findChild("station"); + if(stationObject != NULL) + { + QGeoCoordinate coords = stationObject->property("coordinate").value(); + coords.setLatitude(stationLatitude); + coords.setLongitude(stationLongitude); + coords.setAltitude(stationAltitude); + stationObject->setProperty("coordinate", QVariant::fromValue(coords)); + stationObject->setProperty("stationName", QVariant::fromValue(MainCore::instance()->getSettings().getStationName())); + } + + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + displaySettings(); + applySettings(true); +} + +MapGUI::~MapGUI() +{ + delete ui; +} + +void MapGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void MapGUI::displaySettings() +{ + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_settings.m_title); + blockApplySettings(true); + ui->displayNames->setChecked(m_settings.m_displayNames); + m_mapModel.setDisplayNames(m_settings.m_displayNames); + blockApplySettings(false); +} + +void MapGUI::updatePipeList() +{ + ui->pipes->blockSignals(true); + ui->pipes->clear(); + + QList::const_iterator it = m_availablePipes.begin(); + + for (int i = 0; it != m_availablePipes.end(); ++it, i++) + { + ui->pipes->addItem(it->getName()); + } + + ui->pipes->blockSignals(false); +} + +void MapGUI::leaveEvent(QEvent*) +{ +} + +void MapGUI::enterEvent(QEvent*) +{ +} + +void MapGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicFeatureSettingsDialog dialog(this); + dialog.setTitle(m_settings.m_title); + dialog.setColor(m_settings.m_rgbColor); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex); + dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = dialog.getColor().rgb(); + m_settings.m_title = dialog.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex(); + m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + + resetContextMenuType(); +} + +void MapGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + Map::MsgConfigureMap* message = Map::MsgConfigureMap::create(m_settings, force); + m_map->getInputMessageQueue()->push(message); + } +} + +void MapGUI::on_displayNames_clicked(bool checked) +{ + m_settings.m_displayNames = checked; + m_mapModel.setDisplayNames(checked); +} + +void MapGUI::on_find_returnPressed() +{ + find(ui->find->text().trimmed()); +} + +void MapGUI::find(const QString& target) +{ + if (!target.isEmpty()) + { + QQuickItem *item = ui->map->rootObject(); + QObject *map = item->findChild("map"); + if (map != NULL) + { + float latitude, longitude; + if (Units::stringToLatitudeAndLongitude(target, latitude, longitude)) + { + map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude))); + } + else if (Maidenhead::fromMaidenhead(target, latitude, longitude)) + { + map->setProperty("center", QVariant::fromValue(QGeoCoordinate(latitude, longitude))); + } + else + { + MapItem *mapItem = m_mapModel.findMapItem(target); + if (mapItem != nullptr) + map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates())); + } + } + } +} + +void MapGUI::on_deleteAll_clicked() +{ + m_mapModel.removeAll(); +} diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h new file mode 100644 index 000000000..c78c8e88b --- /dev/null +++ b/plugins/feature/map/mapgui.h @@ -0,0 +1,304 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_MAPGUI_H_ +#define INCLUDE_FEATURE_MAPGUI_H_ + +#include +#include +#include + +#include "feature/featuregui.h" +#include "util/messagequeue.h" +#include "pipes/pipeendpoint.h" +#include "mapsettings.h" +#include "SWGMapItem.h" + +class PluginAPI; +class FeatureUISet; +class Map; + +namespace Ui { + class MapGUI; +} + +class MapGUI; +class MapModel; + +// Information required about each item displayed on the map +class MapItem { + +public: + MapItem(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *mapItem) + { + m_source = source; + m_name = *mapItem->getName(); + m_latitude = mapItem->getLatitude(); + m_longitude = mapItem->getLongitude(); + m_image = *mapItem->getImage(); + m_imageRotation = mapItem->getImageRotation(); + m_imageFixedSize = mapItem->getImageFixedSize() == 1; + m_text = *mapItem->getText(); + } + + void update(SWGSDRangel::SWGMapItem *mapItem) + { + m_latitude = mapItem->getLatitude(); + m_longitude = mapItem->getLongitude(); + m_image = *mapItem->getImage(); + m_imageRotation = mapItem->getImageRotation(); + m_imageFixedSize = mapItem->getImageFixedSize() == 1; + m_text = *mapItem->getText(); + } + + QGeoCoordinate getCoordinates() + { + QGeoCoordinate coords; + coords.setLatitude(m_latitude); + coords.setLongitude(m_longitude); + return coords; + } + +private: + friend MapModel; + const PipeEndPoint *m_source; // Channel/feature that created the item + QString m_name; + float m_latitude; + float m_longitude; + QString m_image; + int m_imageRotation; + bool m_imageFixedSize; // Keep image same size when map is zoomed + QString m_text; +}; + +// Model used for each item on the map +class MapModel : public QAbstractListModel { + Q_OBJECT + +public: + using QAbstractListModel::QAbstractListModel; + enum MarkerRoles { + positionRole = Qt::UserRole + 1, + mapTextRole = Qt::UserRole + 2, + mapTextVisibleRole = Qt::UserRole + 3, + mapImageRole = Qt::UserRole + 4, + mapImageRotationRole = Qt::UserRole + 5, + mapImageFixedSizeRole = Qt::UserRole + 6, + bubbleColourRole = Qt::UserRole + 7, + selectedRole = Qt::UserRole + 8 + }; + + MapModel(MapGUI *gui) : + m_gui(gui) + { + } + + Q_INVOKABLE void add(MapItem *item) + { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_items.append(item); + m_selected.append(false); + endInsertRows(); + } + + void update(const PipeEndPoint *source, SWGSDRangel::SWGMapItem *swgMapItem) + { + QString name = *swgMapItem->getName(); + // Add, update or delete and item + MapItem *item = findMapItem(source, name); + if (item != nullptr) + { + QString image = *swgMapItem->getImage(); + if (image.isEmpty()) + { + // Delete the item + remove(item); + } + else + { + // Update the item + item->update(swgMapItem); + update(item); + } + } + else + { + // Make sure not a duplicate request to delete + QString image = *swgMapItem->getImage(); + if (!image.isEmpty()) + { + // Add new item + add(new MapItem(source, swgMapItem)); + } + } + } + + void update(MapItem *item) + { + int row = m_items.indexOf(item); + if (row >= 0) + { + QModelIndex idx = index(row); + emit dataChanged(idx, idx); + } + } + + void remove(MapItem *item) + { + int row = m_items.indexOf(item); + if (row >= 0) + { + beginRemoveRows(QModelIndex(), row, row); + m_items.removeAt(row); + m_selected.removeAt(row); + endRemoveRows(); + } + } + + MapItem *findMapItem(const PipeEndPoint *source, const QString& name) + { + // FIXME: Should consider adding a QHash for this + QListIterator i(m_items); + while (i.hasNext()) + { + MapItem *item = i.next(); + if ((item->m_name == name) && (item->m_source == source)) + return item; + } + return nullptr; + } + + MapItem *findMapItem(const QString& name) + { + QListIterator i(m_items); + while (i.hasNext()) + { + MapItem *item = i.next(); + if (item->m_name == name) + return item; + } + return nullptr; + } + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return m_items.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 allUpdated() + { + for (int i = 0; i < m_items.count(); i++) + { + QModelIndex idx = index(i); + emit dataChanged(idx, idx); + } + } + + void removeAll() + { + beginRemoveRows(QModelIndex(), 0, m_items.count()); + m_items.clear(); + m_selected.clear(); + endRemoveRows(); + } + + void setDisplayNames(bool displayNames) + { + m_displayNames = displayNames; + allUpdated(); + } + + QHash roleNames() const + { + QHash roles; + roles[positionRole] = "position"; + roles[mapTextRole] = "mapText"; + roles[mapTextVisibleRole] = "mapTextVisible"; + roles[mapImageRole] = "mapImage"; + roles[mapImageRotationRole] = "mapImageRotation"; + roles[mapImageFixedSizeRole] = "mapImageFixedSize"; + roles[bubbleColourRole] = "bubbleColour"; + roles[selectedRole] = "selected"; + return roles; + } + +private: + MapGUI *m_gui; + QList m_items; + QList m_selected; + bool m_displayNames; +}; + +class MapGUI : public FeatureGUI { + Q_OBJECT +public: + static MapGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +private: + Ui::MapGUI* ui; + PluginAPI* m_pluginAPI; + FeatureUISet* m_featureUISet; + MapSettings m_settings; + bool m_doApplySettings; + QList m_availablePipes; + + Map* m_map; + MessageQueue m_inputMessageQueue; + MapModel m_mapModel; + + explicit MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); + virtual ~MapGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void updatePipeList(); + bool handleMessage(const Message& message); + void find(const QString& target); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void onMenuDialogCalled(const QPoint &p); + void onWidgetRolled(QWidget* widget, bool rollDown); + void handleInputMessages(); + void on_displayNames_clicked(bool checked=false); + void on_find_returnPressed(); + void on_deleteAll_clicked(); +}; + + +#endif // INCLUDE_FEATURE_MAPGUI_H_ diff --git a/plugins/feature/map/mapgui.ui b/plugins/feature/map/mapgui.ui new file mode 100644 index 000000000..95ba4709f --- /dev/null +++ b/plugins/feature/map/mapgui.ui @@ -0,0 +1,250 @@ + + + MapGUI + + + + 0 + 0 + 462 + 689 + + + + + 0 + 0 + + + + + 462 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + Map + + + Map + + + + + 0 + 0 + 461 + 41 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Sources + + + + + + + + 150 + 0 + + + + Data source channels and features + + + + + + + Find + + + + + + + Enter name of object to find, latitude and longitude or Maidenhead locator + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Display names + + + ^ + + + + :/info.png:/info.png + + + true + + + true + + + + + + + Delete all items on the map + + + + + + + :/bin.png:/bin.png + + + + + + + + + + + 0 + 100 + 461 + 581 + + + + + 0 + 0 + + + + Map + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 100 + 500 + + + + Map + + + QQuickWidget::SizeRootObjectToView + + + + + + + + + + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
+ + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + map + + + + + +
diff --git a/plugins/feature/map/mapplugin.cpp b/plugins/feature/map/mapplugin.cpp new file mode 100644 index 000000000..774be51e1 --- /dev/null +++ b/plugins/feature/map/mapplugin.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "mapgui.h" +#endif +#include "map.h" +#include "mapplugin.h" +#include "mapwebapiadapter.h" + +const PluginDescriptor MapPlugin::m_pluginDescriptor = { + Map::m_featureId, + QStringLiteral("Map"), + QStringLiteral("6.4.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +MapPlugin::MapPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(nullptr) +{ +} + +const PluginDescriptor& MapPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void MapPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerFeature(Map::m_featureIdURI, Map::m_featureId, this); +} + +#ifdef SERVER_MODE +FeatureGUI* MapPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + (void) featureUISet; + (void) feature; + return nullptr; +} +#else +FeatureGUI* MapPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + return MapGUI::create(m_pluginAPI, featureUISet, feature); +} +#endif + +Feature* MapPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const +{ + return new Map(webAPIAdapterInterface); +} + +FeatureWebAPIAdapter* MapPlugin::createFeatureWebAPIAdapter() const +{ + return new MapWebAPIAdapter(); +} diff --git a/plugins/feature/map/mapplugin.h b/plugins/feature/map/mapplugin.h new file mode 100644 index 000000000..47ffe16f6 --- /dev/null +++ b/plugins/feature/map/mapplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_MAPPLUGIN_H +#define INCLUDE_FEATURE_MAPPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class FeatureGUI; +class WebAPIAdapterInterface; + +class MapPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.feature.map") + +public: + explicit MapPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const; + virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const; + virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FEATURE_MAPPLUGIN_H diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp new file mode 100644 index 000000000..5f88c43ea --- /dev/null +++ b/plugins/feature/map/mapsettings.cpp @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "mapsettings.h" + +const QStringList MapSettings::m_pipeTypes = { + QStringLiteral("ADSBDemod"), + QStringLiteral("APRS"), + QStringLiteral("StarTracker") +}; + +const QStringList MapSettings::m_pipeURIs = { + QStringLiteral("sdrangel.channel.adsbdemod"), + QStringLiteral("sdrangel.feature.aprs"), + QStringLiteral("sdrangel.feature.startracker") +}; + +MapSettings::MapSettings() +{ + resetToDefaults(); +} + +void MapSettings::resetToDefaults() +{ + m_displayNames = true; + m_title = "Map"; + m_rgbColor = QColor(225, 25, 99).rgb(); + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIFeatureSetIndex = 0; + m_reverseAPIFeatureIndex = 0; +} + +QByteArray MapSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeBool(1, m_displayNames); + s.writeString(8, m_title); + s.writeU32(9, m_rgbColor); + s.writeBool(10, m_useReverseAPI); + s.writeString(11, m_reverseAPIAddress); + s.writeU32(12, m_reverseAPIPort); + s.writeU32(13, m_reverseAPIFeatureSetIndex); + s.writeU32(14, m_reverseAPIFeatureIndex); + + return s.final(); +} + +bool MapSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readBool(1, &m_displayNames, true); + d.readString(8, &m_title, "Map"); + d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb()); + d.readBool(10, &m_useReverseAPI, false); + d.readString(11, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(12, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(13, &utmp, 0); + m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; + d.readU32(14, &utmp, 0); + m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/feature/map/mapsettings.h b/plugins/feature/map/mapsettings.h new file mode 100644 index 000000000..2550b81ef --- /dev/null +++ b/plugins/feature/map/mapsettings.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_MAPSETTINGS_H_ +#define INCLUDE_FEATURE_MAPSETTINGS_H_ + +#include +#include + +#include "util/message.h" + +class Serializable; +class PipeEndPoint; + +struct MapSettings +{ + struct AvailablePipe + { + enum {RX, TX, Feature} m_type; + int m_setIndex; + int m_index; + PipeEndPoint *m_source; + QString m_id; + + AvailablePipe() = default; + AvailablePipe(const AvailablePipe&) = default; + AvailablePipe& operator=(const AvailablePipe&) = default; + }; + + bool m_displayNames; + QString m_title; + quint32 m_rgbColor; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIFeatureSetIndex; + uint16_t m_reverseAPIFeatureIndex; + + MapSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + static const QStringList m_pipeTypes; + static const QStringList m_pipeURIs; +}; + +#endif // INCLUDE_FEATURE_MAPSETTINGS_H_ diff --git a/plugins/feature/map/mapwebapiadapter.cpp b/plugins/feature/map/mapwebapiadapter.cpp new file mode 100644 index 000000000..0ef0500b2 --- /dev/null +++ b/plugins/feature/map/mapwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGFeatureSettings.h" +#include "map.h" +#include "mapwebapiadapter.h" + +MapWebAPIAdapter::MapWebAPIAdapter() +{} + +MapWebAPIAdapter::~MapWebAPIAdapter() +{} + +int MapWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings()); + response.getSimplePttSettings()->init(); + Map::webapiFormatFeatureSettings(response, m_settings); + + return 200; +} + +int MapWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + Map::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response); + + return 200; +} diff --git a/plugins/feature/map/mapwebapiadapter.h b/plugins/feature/map/mapwebapiadapter.h new file mode 100644 index 000000000..db29b7036 --- /dev/null +++ b/plugins/feature/map/mapwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// 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_MAP_WEBAPIADAPTER_H +#define INCLUDE_MAP_WEBAPIADAPTER_H + +#include "feature/featurewebapiadapter.h" +#include "mapsettings.h" + +/** + * Standalone API adapter only for the settings + */ +class MapWebAPIAdapter : public FeatureWebAPIAdapter { +public: + MapWebAPIAdapter(); + virtual ~MapWebAPIAdapter(); + + virtual QByteArray serialize() const { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + +private: + MapSettings m_settings; +}; + +#endif // INCLUDE_MAP_WEBAPIADAPTER_H diff --git a/plugins/feature/map/readme.md b/plugins/feature/map/readme.md new file mode 100644 index 000000000..d9254cb54 --- /dev/null +++ b/plugins/feature/map/readme.md @@ -0,0 +1,47 @@ +

Map Feature Plugin

+ +

Introduction

+ +The Map Feature plugin displays a world map. On top of this, it can plot data from other plugins, such as APRS symbols from the APRS Feature, aircraft from the ADS-B Demodulator or the Sun, Moon and Stars from the Star Tracker. + +

Interface

+ +![Map feature plugin GUI](../../../doc/img/Map_plugin.png) + +

1: Source Channels

+ +This displays the list of channels the Map is displaying data from. + +

2: Find

+ +To centre the map on an object or location, enter: + +* An object name. +* Latitude and longitude. This can be in decimal degrees (E.g: -23.666413, -46.573550) or degrees, minutes and seconds (E.g: 50°40'46.461"N 95°48'26.533"W or 33d51m54.5148sS 151d12m35.6400sE). +* A Maidenhead locator (E.g: IO86av). + +

3: Display Names

+ +When checked, names of objects are displayed in a bubble next to each object. + +

4: Delete

+ +When clicked, all items will be deleted from the map. + +

Map

+ +The map displays objects reported by other SDRangel channels and features. + +* The antenna location is placed according to My Position set under the Preferences > My Position menu. The position is only updated when the Map plugin is first opened. +* To pan around the map, click the left mouse button and drag. To zoom in or out, use the mouse scroll wheel. +* Clicking on an object in the map will display a text bubble with additional information about the object. + +

API

+ +Full details of the API can be found in the Swagger documentation. Here is a quick example of how to centre the map on an object from the command line: + + curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/actions" -d '{"featureType": "Map", "MapActions": { "find": "M7RCE" }}' + +And to centre the map at a particular latitude and longitude: + + curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/actions" -d '{"featureType": "Map", "MapActions": { "find": "51.2 0.0" }}' diff --git a/plugins/feature/startracker/CMakeLists.txt b/plugins/feature/startracker/CMakeLists.txt new file mode 100644 index 000000000..8455f0819 --- /dev/null +++ b/plugins/feature/startracker/CMakeLists.txt @@ -0,0 +1,61 @@ +project(startracker) + +set(startracker_SOURCES + startracker.cpp + startrackersettings.cpp + startrackerplugin.cpp + startrackerworker.cpp + startrackerwebapiadapter.cpp +) + +set(startracker_HEADERS + startracker.h + startrackersettings.h + startrackerplugin.h + startrackerreport.h + startrackerworker.h + startrackerwebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(startracker_SOURCES + ${startracker_SOURCES} + startrackergui.cpp + startrackergui.ui + startrackersettingsdialog.cpp + startrackersettingsdialog.ui + startracker.qrc + ) + set(startracker_HEADERS + ${startracker_HEADERS} + startrackergui.h + startrackersettingsdialog.h + ) + + set(TARGET_NAME featurestartracker) + set(TARGET_LIB Qt5::Widgets Qt5::Charts) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME featurestartrackersrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${startracker_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/feature/startracker/readme.md b/plugins/feature/startracker/readme.md new file mode 100644 index 000000000..6d2a3c332 --- /dev/null +++ b/plugins/feature/startracker/readme.md @@ -0,0 +1,158 @@ +

Star Tracker Feature Plugin

+ +

Introduction

+ +The Star Tracker feature plugin is for use in radio astronomy and EME (Earth-Moon-Earth) communication. +It calculates the azimuth and elevation of celestial objects and can send them to the Rotator Controller or other plugins to point an antenna at that object. +The overhead position of the Sun, Moon and selected star can be displayed on the Map Feature. +The plugin can communicate with Stellarium, allowing Stellarium to control SDRangel as though it was a telescope and for the direction the antenna is pointing to be displayed in Stellarium. + +

Interface

+ +![Star Tracker feature plugin GUI](../../../doc/img/StarTracker_plugin.png) + +

1: Start/Stop plugin

+ +This button starts or stops the plugin. The plugin will only calculate azimuth and elevation or communicate with Stellarium when started. + +

2: Find target on map

+ +Pressing this button centres the Map Feature (if open) on the current target. + +

3: Set latitude and longitude from My Position/h3> + +When clicked, it sets the latitude, longitude and height fields to the values from SDRangel's My Position preferences. + +

4: Show settings dialog

+ +Pressing this button displays a settings dialog, that allows you to set: + +* The epoch used when entering RA and Dec. This can be either J2000 (which is used for most catalogues) or JNOW which is the current date and time. +* The units used for the display of the calculated azimuth and elevation. This can be either degrees, minutes and seconds or decimal degrees. +* Whether to correct for atmospheric refaction. You can choose either no correction, the Saemundsson algorithm, typically used for optical astronomy or the more accurate Positional Astronomy Library calculation, which can be used for >250MHz radio frequencies or light. Note that there is only a very minor difference between the two. +* Air pressure in millibars for use in refraction correction. +* Air temperature in degrees Celsius for use in refraction correction. +* Relative humidity in % for use in refraction correction. +* Height above sea level in metres for use in refraction correction. +* Temperature lapse rate in Kelvin per kilometer for use in refraction correction. +* Radio frequency being observed in MHz for use in refraction correction. +* The update period in seconds, which controls how frequently azimuth and elevation are re-calculated. +* The IP port number the Stellarium server listens on. +* Whether to start a Stellarium telescope server. +* Whether to draw the Sun in the map. +* Whether to draw the Moon on the map. +* Whether to draw the target star (or galaxy) on the map. + +

5: Latitude/h3> + +Specifies the latitude in decimal degrees of the observation point (antenna location). + +

6: Longitude

+ +Specifies the longitude in decimal degrees of the observation point (antenna location). + +

7: Time

+ +Select the date and time at which the position of the target should be calculated. Select either Now, for the current time, or Custom to manually enter a date and time. + +

8: Target

+ +Select a target object to track from the list. +To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom. +To allow Stellarium to set the RA and Dec, select Custom, and ensure the Stellarium Server option is checked in the Star Tracker Settings dialog. + +| Target | Type | Details | Flux density (Jy) | +|------------------|-------------------|------------------------------------------------|--------------------------------------------- +| Sun | Star | Targets our Sun | 10k-10M (50MHz), 500k-10M (1.4GHz) | +| Moon | Moon | Targets our Moon | 2 (50MHz), 1000 (1.4GHz) | +| PSR B0329+54 | Pulsar | Strongest in Northern hemisphere (J0332+5434) | 1.8 (50MHz), 1.5 (400MHz), 0.2 (1.4GHz) | +| PSR B0833-45 | Pulsar | Strongest in Southern hemisphere (J0835-4510) | 5.4 (150MHz), 5.0 (400MHz), 1.0 (1.4GHz) | +| Sagittarius A | Galatic centre | First detected source of extrasolar radio | ~0.5 (<1GHz) for Sgr A* | +| Cassiopeia A | Supernova | Brightest extrasolar radio source | 27k (50MHz), 10k (150MHz), 1768 (1.4GHz) | +| Cygnus A | Galaxy | First radio galaxy | 22k (50MHz), 11k (150MHz), 1579 (1.4GHz) | +| Taurus A (M1) | Supernova/Pulsar | Crab Nebular | 2008 (50MHz), 1368 (150MHz), 829 (1.4GHz) | +| Virgo A (M87) | Galaxy | | 2635 (50MHz), 1209 (150MHz), 212 (1.4GHz) | +| Custom | | Manually enter RA and Dec | | + +References: + +* ATNF Pulsar Catalogue - https://www.atnf.csiro.au/research/pulsar/psrcat/ +* Cassiopeia A, Cygnus A, Taurus A, and Virgo A at ultra-low radio frequencies - https://research.chalmers.se/publication/516438/file/516438_Fulltext.pdf +* Repeating Jansky - https://www.gb.nrao.edu/~fghigo/JanskyAntenna/RepeatingJansky_memo10.pdf + +

9: Right Ascension

+ +When target is set to Custom, you can specify the right ascension in hours of the target object. This can be specified as a decimal (E.g. 12.23, from 0 to 24) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. + +

10: Declination

+ +When target is set to Custom, you can specify the declination in degrees of the target object. This can be specified as a decimal (E.g. 34.6, from -90.0 to 90.0) or in degrees, minutes and seconds (E.g. 34d12m5.6s, 34d12'5.6" 34 12 5.6). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. + +

11: Azimuth

+ +Displays the calculated azimuth (angle in degrees, clockwise from North) to the object. + +

12: Elevation

+ +Displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object. + +

13: Elevation vs Time Plot

+ +In order to assit in determining whether and when observations of the target object may be possible, an elevation vs time plot is drawn for the 24 hours encompassing the selected date and time. +Some objects may not be visible from a particular latitude for the specified time, in which case, the grahp title will indicate the object is not visible on that particular date. + +

Map

+ +The Star Tracker feature can send the overhead position of the Sun, Moon and target Star to the Map. These can be enabled individually in the settings dialog. The Moon should be displayed with an approximate phase. Stars (or galaxies) are displayed as an image of a pulsar. + +When using the Find feature in the Map GUI, you can search for "Sun", "Moon" or "Star". + +

Stellarium Interface

+ +In Star Tracker: + +* Set target to Custom +* Press Show settings dialog and ensure Stellarium server is checked +* Press Start + +Then in Stellarium: + +* Enable Telescope Control plugin and restart +* Press the telescope button in the main toolbar +* Press "Configure telescopes..." +* Press "Add a new telescope" +* Set "Telescope controlled by" to "External softare or a remote computer" +* Set "Name" to "SDRangel" (Optional) +* Set "Coordinate system" to "J2000 (default)" +* Press OK +* Press Connect +* Enter Right Ascension/Declination or press "Current object" to get RA/Dec of currently selected object +* Press "Slew" to send the RA/Dec to Star Tracker + +Star Tracker will continually send the RA/Dec of its target to Stellarium and this should be displayed in Stellarium with a crosshair/reticle and the label SDRangel (or whatever name you entered for the telescope). + +To see the rough field of view of your antenna, open the Ocular configuration window and under Eyepieces, add a new eyepiece with name SDRangel. +Set aFOV to the half-power beam width of your antenna, focal length to 100 and field stop to 0. +Then select the SDRangel telescope reticle and press Ocular view. + +![StarTracker map](../../../doc/img/StarTracker_map.png) + +

Attribution

+ +Icons are by Adnen Kadri and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/ + +Icons are by Freepik from Flaticon https://www.flaticon.com/ + +

API

+ +Full details of the API can be found in the Swagger documentation. Here is a quick example of how to set the target to the Moon at the current time: + + curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "StarTracker", "StarTrackerSettings": { "target": "Moon", "dateTime": "" }}' + +Or to a custom RA and declination on a given date and time: + + curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "StarTracker", "StarTrackerSettings": { "target": "Custom", "ra": "03h32m59.35s", "dec": "54d34m45.05s", "dateTime": "1921-04-15T10:17:05" }}' + +To start tracking: + + curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/run" diff --git a/plugins/feature/startracker/startracker.cpp b/plugins/feature/startracker/startracker.cpp new file mode 100644 index 000000000..c817c99ae --- /dev/null +++ b/plugins/feature/startracker/startracker.cpp @@ -0,0 +1,502 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "SWGFeatureSettings.h" +#include "SWGFeatureReport.h" +#include "SWGFeatureActions.h" +#include "SWGSimplePTTReport.h" +#include "SWGDeviceState.h" + +#include "dsp/dspengine.h" + +#include "startrackerworker.h" +#include "startracker.h" + +MESSAGE_CLASS_DEFINITION(StarTracker::MsgConfigureStarTracker, Message) +MESSAGE_CLASS_DEFINITION(StarTracker::MsgStartStop, Message) + +const char* const StarTracker::m_featureIdURI = "sdrangel.feature.startracker"; +const char* const StarTracker::m_featureId = "StarTracker"; + +StarTracker::StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface) : + Feature(m_featureIdURI, webAPIAdapterInterface), + m_ptt(false) +{ + qDebug("StarTracker::StarTracker: webAPIAdapterInterface: %p", webAPIAdapterInterface); + setObjectName(m_featureId); + m_worker = new StarTrackerWorker(this, webAPIAdapterInterface); + m_state = StIdle; + m_errorMessage = "StarTracker error"; +} + +StarTracker::~StarTracker() +{ + if (m_worker->isRunning()) { + stop(); + } + + delete m_worker; +} + +void StarTracker::start() +{ + qDebug("StarTracker::start"); + + m_worker->reset(); + m_worker->setMessageQueueToFeature(getInputMessageQueue()); + m_worker->setMessageQueueToGUI(getMessageQueueToGUI()); + bool ok = m_worker->startWork(); + m_state = ok ? StRunning : StError; + m_thread.start(); + + StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::create(m_settings, true); + m_worker->getInputMessageQueue()->push(msg); +} + +void StarTracker::stop() +{ + qDebug("StarTracker::stop"); + m_worker->stopWork(); + m_state = StIdle; + m_thread.quit(); + m_thread.wait(); +} + +bool StarTracker::handleMessage(const Message& cmd) +{ + if (MsgConfigureStarTracker::match(cmd)) + { + MsgConfigureStarTracker& cfg = (MsgConfigureStarTracker&) cmd; + qDebug() << "StarTracker::handleMessage: MsgConfigureStarTracker"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (MsgStartStop::match(cmd)) + { + MsgStartStop& cfg = (MsgStartStop&) cmd; + qDebug() << "StarTracker::handleMessage: MsgStartStop: start:" << cfg.getStartStop(); + + if (cfg.getStartStop()) { + start(); + } else { + stop(); + } + + return true; + } + else + { + return false; + } +} + +QByteArray StarTracker::serialize() const +{ + return m_settings.serialize(); +} + +bool StarTracker::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void StarTracker::applySettings(const StarTrackerSettings& settings, bool force) +{ + qDebug() << "StarTracker::applySettings:" + << " m_target: " << settings.m_target + << " m_ra: " << settings.m_ra + << " m_dec: " << settings.m_dec + << " m_latitude: " << settings.m_latitude + << " m_longitude: " << settings.m_longitude + << " m_serverPort: " << settings.m_serverPort + << " m_enableServer: " << settings.m_enableServer + << " m_title: " << settings.m_title + << " m_rgbColor: " << settings.m_rgbColor + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex + << " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex + << " force: " << force; + + QList reverseAPIKeys; + + if ((m_settings.m_target != settings.m_target) || force) { + reverseAPIKeys.append("target"); + } + if ((m_settings.m_ra != settings.m_ra) || force) { + reverseAPIKeys.append("ra"); + } + if ((m_settings.m_dec != settings.m_dec) || force) { + reverseAPIKeys.append("dec"); + } + if ((m_settings.m_latitude != settings.m_latitude) || force) { + reverseAPIKeys.append("latitude"); + } + if ((m_settings.m_longitude != settings.m_longitude) || force) { + reverseAPIKeys.append("longitude"); + } + if ((m_settings.m_dateTime != settings.m_dateTime) || force) { + reverseAPIKeys.append("dateTime"); + } + if ((m_settings.m_refraction != settings.m_refraction) || force) { + reverseAPIKeys.append("refraction"); + } + if ((m_settings.m_pressure != settings.m_pressure) || force) { + reverseAPIKeys.append("pressure"); + } + if ((m_settings.m_temperature != settings.m_temperature) || force) { + reverseAPIKeys.append("temperature"); + } + if ((m_settings.m_humidity != settings.m_humidity) || force) { + reverseAPIKeys.append("humidity"); + } + if ((m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel) || force) { + reverseAPIKeys.append("heightAboveSeaLevel"); + } + if ((m_settings.m_temperatureLapseRate != settings.m_temperatureLapseRate) || force) { + reverseAPIKeys.append("temperatureLapseRate"); + } + if ((m_settings.m_frequency != settings.m_frequency) || force) { + reverseAPIKeys.append("frequency"); + } + if ((m_settings.m_serverPort != settings.m_serverPort) || force) { + reverseAPIKeys.append("stellariumPort"); + } + if ((m_settings.m_enableServer != settings.m_enableServer) || force) { + reverseAPIKeys.append("stellariumServerEnabled"); + } + if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force) { + reverseAPIKeys.append("updatePeriod"); + } + if ((m_settings.m_jnow != settings.m_jnow) || force) { + reverseAPIKeys.append("epoch"); + } + if ((m_settings.m_title != settings.m_title) || force) { + reverseAPIKeys.append("title"); + } + if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) { + reverseAPIKeys.append("rgbColor"); + } + + StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::create( + settings, force + ); + m_worker->getInputMessageQueue()->push(msg); + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) || + (m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +int StarTracker::webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + getFeatureStateStr(*response.getState()); + MsgStartStop *msg = MsgStartStop::create(run); + getInputMessageQueue()->push(msg); + return 202; +} + +int StarTracker::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings()); + response.getStarTrackerSettings()->init(); + webapiFormatFeatureSettings(response, m_settings); + return 200; +} + +int StarTracker::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + StarTrackerSettings settings = m_settings; + webapiUpdateFeatureSettings(settings, featureSettingsKeys, response); + + MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("StarTracker::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureStarTracker *msgToGUI = MsgConfigureStarTracker::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatFeatureSettings(response, settings); + return 200; +} + +void StarTracker::webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const StarTrackerSettings& settings) +{ + response.getStarTrackerSettings()->setTarget(new QString(settings.m_target)); + response.getStarTrackerSettings()->setRa(new QString(settings.m_ra)); + response.getStarTrackerSettings()->setDec(new QString(settings.m_dec)); + response.getStarTrackerSettings()->setLatitude(settings.m_latitude); + response.getStarTrackerSettings()->setLongitude(settings.m_longitude); + response.getStarTrackerSettings()->setDateTime(new QString(settings.m_dateTime)); + response.getStarTrackerSettings()->setRefraction(new QString(settings.m_refraction)); + response.getStarTrackerSettings()->setPressure(settings.m_pressure); + response.getStarTrackerSettings()->setTemperature(settings.m_temperature); + response.getStarTrackerSettings()->setHumidity(settings.m_humidity); + response.getStarTrackerSettings()->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel); + response.getStarTrackerSettings()->setTemperatureLapseRate(settings.m_temperatureLapseRate); + response.getStarTrackerSettings()->setFrequency(settings.m_frequency/1000000.0); + response.getStarTrackerSettings()->setStellariumServerEnabled(settings.m_enableServer ? 1 : 0); + response.getStarTrackerSettings()->setStellariumPort(settings.m_serverPort); + response.getStarTrackerSettings()->setUpdatePeriod(settings.m_updatePeriod); + response.getStarTrackerSettings()->setEpoch(settings.m_jnow ? new QString("JNOW") : new QString("J2000")); + + if (response.getStarTrackerSettings()->getTitle()) { + *response.getStarTrackerSettings()->getTitle() = settings.m_title; + } else { + response.getStarTrackerSettings()->setTitle(new QString(settings.m_title)); + } + + response.getStarTrackerSettings()->setRgbColor(settings.m_rgbColor); + response.getStarTrackerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getStarTrackerSettings()->getReverseApiAddress()) { + *response.getStarTrackerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getStarTrackerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getStarTrackerSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getStarTrackerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex); + response.getStarTrackerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex); +} + +void StarTracker::webapiUpdateFeatureSettings( + StarTrackerSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response) +{ + if (featureSettingsKeys.contains("target")) { + settings.m_target = *response.getStarTrackerSettings()->getTarget(); + } + if (featureSettingsKeys.contains("ra")) { + settings.m_ra = *response.getStarTrackerSettings()->getRa(); + } + if (featureSettingsKeys.contains("dec")) { + settings.m_dec = *response.getStarTrackerSettings()->getDec(); + } + if (featureSettingsKeys.contains("latitude")) { + settings.m_latitude = response.getStarTrackerSettings()->getLatitude(); + } + if (featureSettingsKeys.contains("longitude")) { + settings.m_longitude = response.getStarTrackerSettings()->getLongitude(); + } + if (featureSettingsKeys.contains("dateTime")) { + settings.m_dateTime = *response.getStarTrackerSettings()->getDateTime(); + } + if (featureSettingsKeys.contains("pressure")) { + settings.m_pressure = response.getStarTrackerSettings()->getPressure(); + } + if (featureSettingsKeys.contains("temperature")) { + settings.m_temperature = response.getStarTrackerSettings()->getTemperature(); + } + if (featureSettingsKeys.contains("humidity")) { + settings.m_humidity = response.getStarTrackerSettings()->getHumidity(); + } + if (featureSettingsKeys.contains("heightAboveSeaLevel")) { + settings.m_heightAboveSeaLevel = response.getStarTrackerSettings()->getHeightAboveSeaLevel(); + } + if (featureSettingsKeys.contains("temperatureLapseRate")) { + settings.m_temperatureLapseRate = response.getStarTrackerSettings()->getTemperatureLapseRate(); + } + if (featureSettingsKeys.contains("frequency")) { + settings.m_frequency = response.getStarTrackerSettings()->getFrequency() * 100000.0; + } + if (featureSettingsKeys.contains("stellariumServerEnabled")) { + settings.m_enableServer = response.getStarTrackerSettings()->getStellariumServerEnabled() == 1; + } + if (featureSettingsKeys.contains("stellariumPort")) { + settings.m_serverPort = response.getStarTrackerSettings()->getStellariumPort(); + } + if (featureSettingsKeys.contains("updatePeriod")) { + settings.m_updatePeriod = response.getStarTrackerSettings()->getUpdatePeriod(); + } + if (featureSettingsKeys.contains("epoch")) { + settings.m_jnow = *response.getStarTrackerSettings()->getEpoch() == "JNOW"; + } + if (featureSettingsKeys.contains("title")) { + settings.m_title = *response.getStarTrackerSettings()->getTitle(); + } + if (featureSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getStarTrackerSettings()->getRgbColor(); + } + if (featureSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getStarTrackerSettings()->getUseReverseApi() != 0; + } + if (featureSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getStarTrackerSettings()->getReverseApiAddress(); + } + if (featureSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getStarTrackerSettings()->getReverseApiPort(); + } + if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIFeatureSetIndex = response.getStarTrackerSettings()->getReverseApiDeviceIndex(); + } + if (featureSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIFeatureIndex = response.getStarTrackerSettings()->getReverseApiChannelIndex(); + } +} + +void StarTracker::webapiReverseSendSettings(QList& featureSettingsKeys, const StarTrackerSettings& settings, bool force) +{ + SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings(); + // swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet()); + // swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex()); + swgFeatureSettings->setFeatureType(new QString("StarTracker")); + swgFeatureSettings->setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings()); + SWGSDRangel::SWGStarTrackerSettings *swgStarTrackerSettings = swgFeatureSettings->getStarTrackerSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (featureSettingsKeys.contains("target") || force) { + swgStarTrackerSettings->setTarget(new QString(settings.m_target)); + } + if (featureSettingsKeys.contains("ra") || force) { + swgStarTrackerSettings->setRa(new QString(settings.m_ra)); + } + if (featureSettingsKeys.contains("dec") || force) { + swgStarTrackerSettings->setDec(new QString(settings.m_dec)); + } + if (featureSettingsKeys.contains("latitude") || force) { + swgStarTrackerSettings->setLatitude(settings.m_latitude); + } + if (featureSettingsKeys.contains("longitude") || force) { + swgStarTrackerSettings->setLongitude(settings.m_longitude); + } + if (featureSettingsKeys.contains("dateTime") || force) { + swgStarTrackerSettings->setDateTime(new QString(settings.m_dateTime)); + } + if (featureSettingsKeys.contains("pressure") || force) { + swgStarTrackerSettings->setPressure(settings.m_pressure); + } + if (featureSettingsKeys.contains("temperature") || force) { + swgStarTrackerSettings->setTemperature(settings.m_temperature); + } + if (featureSettingsKeys.contains("humidity") || force) { + swgStarTrackerSettings->setHumidity(settings.m_humidity); + } + if (featureSettingsKeys.contains("heightAboveSeaLevel") || force) { + swgStarTrackerSettings->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel); + } + if (featureSettingsKeys.contains("temperatureLapseRate") || force) { + swgStarTrackerSettings->setTemperatureLapseRate(settings.m_temperatureLapseRate); + } + if (featureSettingsKeys.contains("frequency") || force) { + swgStarTrackerSettings->setFrequency(settings.m_frequency / 1000000.0); + } + if (featureSettingsKeys.contains("stellariumServerEnabled") || force) { + swgStarTrackerSettings->setStellariumServerEnabled(settings.m_enableServer ? 1 : 0); + } + if (featureSettingsKeys.contains("stellariumPort") || force) { + swgStarTrackerSettings->setStellariumPort(settings.m_serverPort); + } + if (featureSettingsKeys.contains("updatePeriod") || force) { + swgStarTrackerSettings->setUpdatePeriod(settings.m_updatePeriod); + } + if (featureSettingsKeys.contains("epoch") || force) { + swgStarTrackerSettings->setEpoch(settings.m_jnow ? new QString("JNOW") : new QString("J2000")); + } + if (featureSettingsKeys.contains("title") || force) { + swgStarTrackerSettings->setTitle(new QString(settings.m_title)); + } + if (featureSettingsKeys.contains("rgbColor") || force) { + swgStarTrackerSettings->setRgbColor(settings.m_rgbColor); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIFeatureSetIndex) + .arg(settings.m_reverseAPIFeatureIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgFeatureSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgFeatureSettings; +} + +void StarTracker::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "StarTracker::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("StarTracker::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/feature/startracker/startracker.h b/plugins/feature/startracker/startracker.h new file mode 100644 index 000000000..4ad767403 --- /dev/null +++ b/plugins/feature/startracker/startracker.h @@ -0,0 +1,139 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_STARTRACKER_H_ +#define INCLUDE_FEATURE_STARTRACKER_H_ + +#include +#include + +#include "feature/feature.h" +#include "util/message.h" + +#include "startrackersettings.h" + +class WebAPIAdapterInterface; +class StarTrackerWorker; +class QNetworkAccessManager; +class QNetworkReply; + +namespace SWGSDRangel { + class SWGDeviceState; +} + +class StarTracker : public Feature +{ + Q_OBJECT +public: + class MsgConfigureStarTracker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const StarTrackerSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureStarTracker* create(const StarTrackerSettings& settings, bool force) { + return new MsgConfigureStarTracker(settings, force); + } + + private: + StarTrackerSettings m_settings; + bool m_force; + + MsgConfigureStarTracker(const StarTrackerSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface); + virtual ~StarTracker(); + virtual void destroy() { delete this; } + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) const { id = objectName(); } + virtual void getTitle(QString& title) const { title = m_settings.m_title; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiRun(bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + static void webapiFormatFeatureSettings( + SWGSDRangel::SWGFeatureSettings& response, + const StarTrackerSettings& settings); + + static void webapiUpdateFeatureSettings( + StarTrackerSettings& settings, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response); + + static const char* const m_featureIdURI; + static const char* const m_featureId; + +private: + QThread m_thread; + StarTrackerWorker *m_worker; + StarTrackerSettings m_settings; + bool m_ptt; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void start(); + void stop(); + void applySettings(const StarTrackerSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& featureSettingsKeys, const StarTrackerSettings& settings, bool force); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_FEATURE_STARTRACKER_H_ diff --git a/plugins/feature/startracker/startracker.qrc b/plugins/feature/startracker/startracker.qrc new file mode 100644 index 000000000..efe342020 --- /dev/null +++ b/plugins/feature/startracker/startracker.qrc @@ -0,0 +1,17 @@ + + + startracker/moon-flat-32.png + startracker/moon-full-32.png + startracker/moon-new-32.png + startracker/moon-old-32.png + startracker/moon-waning-crescent-32.png + startracker/moon-waning-gibbous-32.png + startracker/moon-third-quarter-32.png + startracker/moon-waxing-crescent-32.png + startracker/moon-waxing-gibbous-32.png + startracker/moon-first-quarter-32.png + startracker/moon-young-32.png + startracker/pulsar-32.png + startracker/sun-40.png + + diff --git a/plugins/feature/startracker/startracker/moon-32.png b/plugins/feature/startracker/startracker/moon-32.png new file mode 100644 index 000000000..ddd45cb64 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-first-quarter-32.png b/plugins/feature/startracker/startracker/moon-first-quarter-32.png new file mode 100644 index 000000000..3e52c66cd Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-first-quarter-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-flat-32.png b/plugins/feature/startracker/startracker/moon-flat-32.png new file mode 100644 index 000000000..833a84f43 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-flat-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-full-32.png b/plugins/feature/startracker/startracker/moon-full-32.png new file mode 100644 index 000000000..ddd45cb64 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-full-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-new-32.png b/plugins/feature/startracker/startracker/moon-new-32.png new file mode 100644 index 000000000..d451cde44 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-new-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-old-32.png b/plugins/feature/startracker/startracker/moon-old-32.png new file mode 100644 index 000000000..c5898a131 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-old-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-third-quarter-32.png b/plugins/feature/startracker/startracker/moon-third-quarter-32.png new file mode 100644 index 000000000..d7cc1b01b Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-third-quarter-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-waning-crescent-32.png b/plugins/feature/startracker/startracker/moon-waning-crescent-32.png new file mode 100644 index 000000000..c5898a131 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-waning-crescent-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-waning-gibbous-32.png b/plugins/feature/startracker/startracker/moon-waning-gibbous-32.png new file mode 100644 index 000000000..576cb3ae3 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-waning-gibbous-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-waxing-crescent-32.png b/plugins/feature/startracker/startracker/moon-waxing-crescent-32.png new file mode 100644 index 000000000..5218137f2 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-waxing-crescent-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-waxing-gibbous-32.png b/plugins/feature/startracker/startracker/moon-waxing-gibbous-32.png new file mode 100644 index 000000000..f28150a01 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-waxing-gibbous-32.png differ diff --git a/plugins/feature/startracker/startracker/moon-young-32.png b/plugins/feature/startracker/startracker/moon-young-32.png new file mode 100644 index 000000000..844fe4802 Binary files /dev/null and b/plugins/feature/startracker/startracker/moon-young-32.png differ diff --git a/plugins/feature/startracker/startracker/pulsar-32.png b/plugins/feature/startracker/startracker/pulsar-32.png new file mode 100644 index 000000000..0bae54abc Binary files /dev/null and b/plugins/feature/startracker/startracker/pulsar-32.png differ diff --git a/plugins/feature/startracker/startracker/sun-32.png b/plugins/feature/startracker/startracker/sun-32.png new file mode 100644 index 000000000..28ebd06ea Binary files /dev/null and b/plugins/feature/startracker/startracker/sun-32.png differ diff --git a/plugins/feature/startracker/startracker/sun-40.png b/plugins/feature/startracker/startracker/sun-40.png new file mode 100644 index 000000000..c47094e93 Binary files /dev/null and b/plugins/feature/startracker/startracker/sun-40.png differ diff --git a/plugins/feature/startracker/startrackergui.cpp b/plugins/feature/startracker/startrackergui.cpp new file mode 100644 index 000000000..8686cc4c0 --- /dev/null +++ b/plugins/feature/startracker/startrackergui.cpp @@ -0,0 +1,584 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "feature/featureuiset.h" +#include "feature/featurewebapiutils.h" +#include "gui/basicfeaturesettingsdialog.h" +#include "mainwindow.h" +#include "device/deviceuiset.h" +#include "util/units.h" +#include "util/astronomy.h" + +#include "ui_startrackergui.h" +#include "startracker.h" +#include "startrackergui.h" +#include "startrackerreport.h" +#include "startrackersettingsdialog.h" + +StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) +{ + StarTrackerGUI* gui = new StarTrackerGUI(pluginAPI, featureUISet, feature); + return gui; +} + +void StarTrackerGUI::destroy() +{ + delete this; +} + +void StarTrackerGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray StarTrackerGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool StarTrackerGUI::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + displaySettings(); + applySettings(true); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +QString StarTrackerGUI::convertDegreesToText(double degrees) +{ + if (m_settings.m_azElUnits == StarTrackerSettings::DMS) + return Units::decimalDegreesToDegreeMinutesAndSeconds(degrees); + else if (m_settings.m_azElUnits == StarTrackerSettings::DM) + return Units::decimalDegreesToDegreesAndMinutes(degrees); + else if (m_settings.m_azElUnits == StarTrackerSettings::D) + return Units::decimalDegreesToDegrees(degrees); + else + return QString("%1").arg(degrees, 0, 'f', 2); +} + +bool StarTrackerGUI::handleMessage(const Message& message) +{ + if (StarTracker::MsgConfigureStarTracker::match(message)) + { + qDebug("StarTrackerGUI::handleMessage: StarTracker::MsgConfigureStarTracker"); + const StarTracker::MsgConfigureStarTracker& cfg = (StarTracker::MsgConfigureStarTracker&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + + return true; + } + else if (StarTrackerReport::MsgReportAzAl::match(message)) + { + StarTrackerReport::MsgReportAzAl& azAl = (StarTrackerReport::MsgReportAzAl&) message; + ui->azimuth->setText(convertDegreesToText(azAl.getAzimuth())); + ui->elevation->setText(convertDegreesToText(azAl.getElevation())); + return true; + } + else if (StarTrackerReport::MsgReportRADec::match(message)) + { + StarTrackerReport::MsgReportRADec& raDec = (StarTrackerReport::MsgReportRADec&) message; + m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDec.getRA()); + m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec()); + ui->rightAscension->setText(m_settings.m_ra); + ui->declination->setText(m_settings.m_dec); + return true; + } + + return false; +} + +void StarTrackerGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop())) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +void StarTrackerGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) : + FeatureGUI(parent), + ui(new Ui::StarTrackerGUI), + m_pluginAPI(pluginAPI), + m_featureUISet(featureUISet), + m_doApplySettings(true), + m_lastFeatureState(0) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + setChannelWidget(false); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + m_starTracker = reinterpret_cast(feature); + m_starTracker->setMessageQueueToGUI(&m_inputMessageQueue); + + m_featureUISet->addRollupWidget(this); + + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(1000); + + // Intialise chart + m_chart.legend()->hide(); + ui->elevationChart->setChart(&m_chart); + ui->elevationChart->setRenderHint(QPainter::Antialiasing); + m_chart.addAxis(&m_chartXAxis, Qt::AlignBottom); + m_chart.addAxis(&m_chartYAxis, Qt::AlignLeft); + + ui->dateTime->setDateTime(QDateTime::currentDateTime()); + displaySettings(); + applySettings(true); + + // Use My Position from preferences, if none set + if ((m_settings.m_latitude == 0.0) && (m_settings.m_longitude == 0.0)) + on_useMyPosition_clicked(); + +/* + printf("saemundsson=["); + for (int i = 0; i <= 90; i+= 5) + printf("%f ", Astronomy::refractionSaemundsson(i, m_settings.m_pressure, m_settings.m_temperature)); + printf("];\n"); + printf("palRadio=["); + for (int i = 0; i <= 90; i+= 5) + printf("%f ", Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity, + 100000000, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel, + m_settings.m_temperatureLapseRate)); + printf("];\n"); + printf("palLight=["); + for (int i = 0; i <= 90; i+= 5) + printf("%f ",Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity, + 7.5e14, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel, + m_settings.m_temperatureLapseRate)); + printf("];\n"); +*/ +} + +StarTrackerGUI::~StarTrackerGUI() +{ + delete ui; +} + +void StarTrackerGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void StarTrackerGUI::displaySettings() +{ + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_settings.m_title); + blockApplySettings(true); + ui->latitude->setValue(m_settings.m_latitude); + ui->longitude->setValue(m_settings.m_longitude); + ui->target->setCurrentIndex(ui->target->findText(m_settings.m_target)); + if (m_settings.m_target == "Custom") + { + ui->rightAscension->setText(m_settings.m_ra); + ui->declination->setText(m_settings.m_dec); + } + if (m_settings.m_dateTime == "") + { + ui->dateTimeSelect->setCurrentIndex(0); + ui->dateTime->setVisible(false); + } + else + { + ui->dateTime->setDateTime(QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs)); + ui->dateTime->setVisible(true); + ui->dateTimeSelect->setCurrentIndex(1); + } + updateForTarget(); + plotChart(); + blockApplySettings(false); +} + +void StarTrackerGUI::leaveEvent(QEvent*) +{ +} + +void StarTrackerGUI::enterEvent(QEvent*) +{ +} + +void StarTrackerGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicFeatureSettingsDialog dialog(this); + dialog.setTitle(m_settings.m_title); + dialog.setColor(m_settings.m_rgbColor); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex); + dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = dialog.getColor().rgb(); + m_settings.m_title = dialog.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex(); + m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + + resetContextMenuType(); +} + +void StarTrackerGUI::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + StarTracker::MsgStartStop *message = StarTracker::MsgStartStop::create(checked); + m_starTracker->getInputMessageQueue()->push(message); + } +} + +void StarTrackerGUI::on_latitude_valueChanged(double value) +{ + m_settings.m_latitude = value; + applySettings(); + plotChart(); +} + +void StarTrackerGUI::on_longitude_valueChanged(double value) +{ + m_settings.m_longitude = value; + applySettings(); + plotChart(); +} + +void StarTrackerGUI::on_rightAscension_editingFinished() +{ + m_settings.m_ra = ui->rightAscension->text(); + applySettings(); + plotChart(); +} + +void StarTrackerGUI::on_declination_editingFinished() +{ + m_settings.m_dec = ui->declination->text(); + applySettings(); + plotChart(); +} + +void StarTrackerGUI::updateForTarget() +{ + if (m_settings.m_target == "Sun") + { + ui->rightAscension->setReadOnly(true); + ui->declination->setReadOnly(true); + ui->rightAscension->setText(""); + ui->declination->setText(""); + } + else if (m_settings.m_target == "Moon") + { + ui->rightAscension->setReadOnly(true); + ui->declination->setReadOnly(true); + ui->rightAscension->setText(""); + ui->declination->setText(""); + } + else if (m_settings.m_target == "Custom") + { + ui->rightAscension->setReadOnly(false); + ui->declination->setReadOnly(false); + } + else + { + ui->rightAscension->setReadOnly(true); + ui->declination->setReadOnly(true); + if (m_settings.m_target == "PSR B0329+54") + { + ui->rightAscension->setText("03h32m59.35s"); + ui->declination->setText(QString("54%0134'45.05\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "PSR B0833-45") + { + ui->rightAscension->setText("08h35m20.66s"); + ui->declination->setText(QString("-45%0110'35.15\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "Sagittarius A") + { + ui->rightAscension->setText("17h45m40.04s"); + ui->declination->setText(QString("-29%0100'28.17\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "Cassiopeia A") + { + ui->rightAscension->setText("23h23m24s"); + ui->declination->setText(QString("58%0148'54\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "Cygnus A") + { + ui->rightAscension->setText("19h59m28.36s"); + ui->declination->setText(QString("40%0144'02.1\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "Taurus A (M1)") + { + ui->rightAscension->setText("05h34m31.94s"); + ui->declination->setText(QString("22%0100'52.2\"").arg(QChar(0xb0))); + } + else if (m_settings.m_target == "Virgo A (M87)") + { + ui->rightAscension->setText("12h30m49.42s"); + ui->declination->setText(QString("12%0123'28.04\"").arg(QChar(0xb0))); + } + on_rightAscension_editingFinished(); + on_declination_editingFinished(); + } + // Clear as no longer valid when target has changed + ui->azimuth->setText(""); + ui->elevation->setText(""); +} + +void StarTrackerGUI::on_target_currentTextChanged(const QString &text) +{ + m_settings.m_target = text; + applySettings(); + updateForTarget(); + plotChart(); +} + +void StarTrackerGUI::updateLST() +{ + QDateTime dt; + + if (m_settings.m_dateTime.isEmpty()) + dt = QDateTime::currentDateTime(); + else + dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs); + + double lst = Astronomy::localSiderealTime(dt, m_settings.m_longitude); + + ui->lst->setText(Units::decimalHoursToHoursMinutesAndSeconds(lst/15.0, 0)); +} + +void StarTrackerGUI::updateStatus() +{ + int state = m_starTracker->getState(); + + if (m_lastFeatureState != state) + { + // We set checked state of start/stop button, in case it was changed via API + bool oldState; + switch (state) + { + case Feature::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case Feature::StIdle: + oldState = ui->startStop->blockSignals(true); + ui->startStop->setChecked(false); + ui->startStop->blockSignals(oldState); + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case Feature::StRunning: + oldState = ui->startStop->blockSignals(true); + ui->startStop->setChecked(true); + ui->startStop->blockSignals(oldState); + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case Feature::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_starTracker->getErrorMessage()); + break; + default: + break; + } + + m_lastFeatureState = state; + } + + updateLST(); +} + +void StarTrackerGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + StarTracker::MsgConfigureStarTracker* message = StarTracker::MsgConfigureStarTracker::create(m_settings, force); + m_starTracker->getInputMessageQueue()->push(message); + } +} + +void StarTrackerGUI::on_useMyPosition_clicked(bool checked) +{ + (void) checked; + double stationLatitude = MainCore::instance()->getSettings().getLatitude(); + double stationLongitude = MainCore::instance()->getSettings().getLongitude(); + double stationAltitude = MainCore::instance()->getSettings().getAltitude(); + + ui->latitude->setValue(stationLatitude); + ui->longitude->setValue(stationLongitude); + m_settings.m_heightAboveSeaLevel = stationAltitude; + applySettings(); + plotChart(); +} + +// Show settings dialog +void StarTrackerGUI::on_displaySettings_clicked() +{ + StarTrackerSettingsDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) + { + applySettings(); + } +} + +void StarTrackerGUI::on_dateTimeSelect_currentTextChanged(const QString &text) +{ + if (text == "Now") + { + m_settings.m_dateTime = ""; + ui->dateTime->setVisible(false); + } + else + { + m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs); + ui->dateTime->setVisible(true); + } + applySettings(); + plotChart(); +} + +void StarTrackerGUI::on_dateTime_dateTimeChanged(const QDateTime &datetime) +{ + (void) datetime; + if (ui->dateTimeSelect->currentIndex() == 1) + { + m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs); + applySettings(); + plotChart(); + } +} + +// Plot target elevation angle over the day +void StarTrackerGUI::plotChart() +{ + m_chart.removeAllSeries(); + double maxElevation = -90.0; + + QLineSeries *series = new QLineSeries(); + QDateTime dt; + if (m_settings.m_dateTime.isEmpty()) + dt = QDateTime::currentDateTime(); + else + dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs); + dt.setTime(QTime(0,0)); + QDateTime startTime = dt; + QDateTime endTime = dt; + for (int hour = 0; hour <= 24; hour++) + { + AzAlt aa; + RADec rd; + + // Calculate elevation of desired object + if (m_settings.m_target == "Sun") + Astronomy::sunPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt); + else if (m_settings.m_target == "Moon") + Astronomy::moonPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt); + else + { + rd.ra = Astronomy::raToDecimal(m_settings.m_ra); + rd.dec = Astronomy::decToDecimal(m_settings.m_dec); + aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow); + } + + if (aa.alt > maxElevation) + maxElevation = aa.alt; + + // Adjust for refraction + if (m_settings.m_refraction == "Positional Astronomy Library") + { + aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity, + m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel, + m_settings.m_temperatureLapseRate); + if (aa.alt > 90.0) + aa.alt = 90.0f; + } + else if (m_settings.m_refraction == "Saemundsson") + { + aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature); + if (aa.alt > 90.0) + aa.alt = 90.0f; + } + + series->append(dt.toMSecsSinceEpoch(), aa.alt); + + endTime = dt; + dt = dt.addSecs(60*60); // addSecs accounts for daylight savings jumps + } + if (maxElevation < 0) + m_chart.setTitle("Not visible from this latitude"); + else + m_chart.setTitle(""); + m_chart.addSeries(series); + series->attachAxis(&m_chartXAxis); + series->attachAxis(&m_chartYAxis); + m_chartXAxis.setTitleText(QString("%1 %2").arg(startTime.date().toString()).arg(startTime.timeZoneAbbreviation())); + m_chartXAxis.setFormat("hh"); + m_chartXAxis.setTickCount(7); + m_chartXAxis.setRange(startTime, endTime); + m_chartYAxis.setRange(0.0, 90.0); + m_chartYAxis.setTitleText(QString("Elevation (%1)").arg(QChar(0xb0))); +} + +// Find target on the Map +void StarTrackerGUI::on_viewOnMap_clicked() +{ + QString target = m_settings.m_target == "Sun" || m_settings.m_target == "Moon" ? m_settings.m_target : "Star"; + FeatureWebAPIUtils::mapFind(target); +} diff --git a/plugins/feature/startracker/startrackergui.h b/plugins/feature/startracker/startrackergui.h new file mode 100644 index 000000000..ced54e3e2 --- /dev/null +++ b/plugins/feature/startracker/startrackergui.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_STARTRACKERGUI_H_ +#define INCLUDE_FEATURE_STARTRACKERGUI_H_ + +#include +#include + +#include "feature/featuregui.h" +#include "util/messagequeue.h" +#include "startrackersettings.h" + +class PluginAPI; +class FeatureUISet; +class StarTracker; + +namespace Ui { + class StarTrackerGUI; +} + +using namespace QtCharts; + +class StarTrackerGUI : public FeatureGUI { + Q_OBJECT +public: + static StarTrackerGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +private: + Ui::StarTrackerGUI* ui; + PluginAPI* m_pluginAPI; + FeatureUISet* m_featureUISet; + StarTrackerSettings m_settings; + bool m_doApplySettings; + + StarTracker* m_starTracker; + MessageQueue m_inputMessageQueue; + QTimer m_statusTimer; + int m_lastFeatureState; + + QChart m_chart; + QDateTimeAxis m_chartXAxis; + QValueAxis m_chartYAxis; + + explicit StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); + virtual ~StarTrackerGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void updateForTarget(); + QString convertDegreesToText(double degrees); + bool handleMessage(const Message& message); + void updateLST(); + void plotChart(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void onMenuDialogCalled(const QPoint &p); + void onWidgetRolled(QWidget* widget, bool rollDown); + void handleInputMessages(); + void on_startStop_toggled(bool checked); + void on_useMyPosition_clicked(bool checked=false); + void on_latitude_valueChanged(double value); + void on_longitude_valueChanged(double value); + void on_rightAscension_editingFinished(); + void on_declination_editingFinished(); + void on_target_currentTextChanged(const QString &text); + void on_displaySettings_clicked(); + void on_dateTimeSelect_currentTextChanged(const QString &text); + void on_dateTime_dateTimeChanged(const QDateTime &datetime); + void updateStatus(); + void on_viewOnMap_clicked(); +}; + + +#endif // INCLUDE_FEATURE_STARTRACKERGUI_H_ diff --git a/plugins/feature/startracker/startrackergui.ui b/plugins/feature/startracker/startrackergui.ui new file mode 100644 index 000000000..9e859b468 --- /dev/null +++ b/plugins/feature/startracker/startrackergui.ui @@ -0,0 +1,503 @@ + + + StarTrackerGUI + + + + 0 + 0 + 337 + 568 + + + + + 0 + 0 + + + + + 320 + 100 + + + + + 16777215 + 16777215 + + + + + Liberation Sans + 9 + + + + Star Tracker + + + + + 10 + 10 + 301 + 201 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Latitude in decimal degrees (North positive) of observation point / antenna location + + + 6 + + + -90.000000000000000 + + + 90.000000000000000 + + + -90.000000000000000 + + + + + + + Right Ascension of the target object. + +This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2) + + + 23h59m59.59s + + + + + + + Computed azimuth in degrees to the target from the observation point + + + 360 + + + true + + + + + + + Time + + + + + + + Declination of the target object + +This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2" 34 12 10.2) + + + -90d59'59.59" + + + + + + + Target + + + + + + + Date and time to use when calculating target's position + + + dd/MM/yyyy HH:mm:ss + + + true + + + + + + + Latitude + + + + + + + + Now + + + + + Custom + + + + + + + + Elevation + + + + + + + RA + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Find target on the map + + + + + + + :/gridpolar.png:/gridpolar.png + + + + + + + Set latitude, longitude and height from My Position in SDRangel preferences + + + + + + + :/import.png:/import.png + + + + + + + Show settings dialog + + + + + + + :/listing.png:/listing.png + + + + + + + + + + + + 110 + 0 + + + + 0 + + + + Sun + + + + + Moon + + + + + PSR B0329+54 + + + + + PSR B0833-45 + + + + + Sagittarius A + + + + + Cassiopeia A + + + + + Cygnus A + + + + + Taurus A (M1) + + + + + Virgo A (M87) + + + + + Custom + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Longitude in decimal degress (East positive) of observation point / antenna location + + + 6 + + + -180.000000000000000 + + + 180.000000000000000 + + + -180.000000000000000 + + + + + + + Dec + + + + + + + Longitude + + + + + + + Azimuth + + + + + + + Computed elevation in degrees to the target from the observation point + + + 90 + + + true + + + + + + + LST + + + + + + + Local sidereal time for selected date, time and longitude + + + true + + + + + + + + + + + 10 + 220 + 318 + 268 + + + + + 0 + 0 + + + + + 200 + 200 + + + + Elevation vs Time + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 300 + 250 + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + QChartView + QGraphicsView +
QtCharts
+
+
+ + startStop + useMyPosition + displaySettings + latitude + longitude + target + rightAscension + declination + azimuth + elevation + + + + + +
diff --git a/plugins/feature/startracker/startrackerplugin.cpp b/plugins/feature/startracker/startrackerplugin.cpp new file mode 100644 index 000000000..a96d68484 --- /dev/null +++ b/plugins/feature/startracker/startrackerplugin.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "startrackergui.h" +#endif +#include "startracker.h" +#include "startrackerplugin.h" +#include "startrackerwebapiadapter.h" + +const PluginDescriptor StarTrackerPlugin::m_pluginDescriptor = { + StarTracker::m_featureId, + QStringLiteral("Star Tracker"), + QStringLiteral("6.4.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +StarTrackerPlugin::StarTrackerPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(nullptr) +{ +} + +const PluginDescriptor& StarTrackerPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void StarTrackerPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerFeature(StarTracker::m_featureIdURI, StarTracker::m_featureId, this); +} + +#ifdef SERVER_MODE +FeatureGUI* StarTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + (void) featureUISet; + (void) feature; + return nullptr; +} +#else +FeatureGUI* StarTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + return StarTrackerGUI::create(m_pluginAPI, featureUISet, feature); +} +#endif + +Feature* StarTrackerPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const +{ + return new StarTracker(webAPIAdapterInterface); +} + +FeatureWebAPIAdapter* StarTrackerPlugin::createFeatureWebAPIAdapter() const +{ + return new StarTrackerWebAPIAdapter(); +} diff --git a/plugins/feature/startracker/startrackerplugin.h b/plugins/feature/startracker/startrackerplugin.h new file mode 100644 index 000000000..aff585f7e --- /dev/null +++ b/plugins/feature/startracker/startrackerplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_STARTRACKERPLUGIN_H +#define INCLUDE_FEATURE_STARTRACKERPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class FeatureGUI; +class WebAPIAdapterInterface; + +class StarTrackerPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.feature.startracker") + +public: + explicit StarTrackerPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const; + virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const; + virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FEATURE_STARTRACKERPLUGIN_H diff --git a/plugins/feature/startracker/startrackerreport.h b/plugins/feature/startracker/startrackerreport.h new file mode 100644 index 000000000..0cace5aed --- /dev/null +++ b/plugins/feature/startracker/startrackerreport.h @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FEATURE_STARTRACKERREPORT_H_ +#define INCLUDE_FEATURE_STARTRACKERREPORT_H_ + +#include + +#include "util/message.h" + +class StarTrackerReport : public QObject +{ + Q_OBJECT +public: + class MsgReportAzAl : public Message { + MESSAGE_CLASS_DECLARATION + + public: + double getAzimuth() const { return m_azimuth; } + double getElevation() const { return m_elevation; } + + static MsgReportAzAl* create(double azimuth, double elevation) + { + return new MsgReportAzAl(azimuth, elevation); + } + + private: + double m_azimuth; + double m_elevation; + + MsgReportAzAl(double azimuth, double elevation) : + Message(), + m_azimuth(azimuth), + m_elevation(elevation) + { + } + }; + + class MsgReportRADec : public Message { + MESSAGE_CLASS_DECLARATION + + public: + double getRA() const { return m_ra; } + double getDec() const { return m_dec; } + + static MsgReportRADec* create(double ra, double dec) + { + return new MsgReportRADec(ra, dec); + } + + private: + double m_ra; + double m_dec; + + MsgReportRADec(double ra, double dec) : + Message(), + m_ra(ra), + m_dec(dec) + { + } + }; + +public: + StarTrackerReport() {} + ~StarTrackerReport() {} +}; + +#endif // INCLUDE_FEATURE_STARTRACKERREPORT_H_ diff --git a/plugins/feature/startracker/startrackersettings.cpp b/plugins/feature/startracker/startrackersettings.cpp new file mode 100644 index 000000000..2585a4759 --- /dev/null +++ b/plugins/feature/startracker/startrackersettings.cpp @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "startrackersettings.h" + +StarTrackerSettings::StarTrackerSettings() +{ + resetToDefaults(); +} + +void StarTrackerSettings::resetToDefaults() +{ + m_ra = ""; + m_dec = ""; + m_latitude = 0.0; + m_longitude = 0.0; + m_target = "Sun"; + m_dateTime = ""; + m_refraction = "Positional Astronomy Library"; + m_pressure = 1010; + m_temperature = 10; + m_humidity = 80.0; + m_heightAboveSeaLevel = 0.0; + m_temperatureLapseRate = 6.49; + m_frequency = 435000000; + m_enableServer = true; + m_serverPort = 10001; + m_azElUnits = DM; + m_updatePeriod = 1.0; + m_jnow = false; + m_drawSunOnMap = true; + m_drawMoonOnMap = true; + m_drawStarOnMap = true; + m_title = "Star Tracker"; + m_rgbColor = QColor(225, 25, 99).rgb(); + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIFeatureSetIndex = 0; + m_reverseAPIFeatureIndex = 0; +} + +QByteArray StarTrackerSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_ra); + s.writeString(2, m_dec); + s.writeDouble(3, m_latitude); + s.writeDouble(4, m_longitude); + s.writeString(5, m_target); + s.writeString(6, m_dateTime); + s.writeU32(7, m_enableServer); + s.writeU32(8, m_serverPort); + s.writeS32(9, m_azElUnits); + s.writeFloat(10, m_updatePeriod); + s.writeBool(11, m_jnow); + s.writeString(12, m_refraction); + s.writeDouble(13, m_pressure); + s.writeDouble(14, m_temperature); + s.writeDouble(15, m_humidity); + s.writeDouble(16, m_heightAboveSeaLevel); + s.writeDouble(17, m_temperatureLapseRate); + s.writeDouble(18, m_frequency); + s.writeBool(19, m_drawSunOnMap); + s.writeBool(20, m_drawMoonOnMap); + s.writeBool(21, m_drawStarOnMap); + s.writeString(22, m_title); + s.writeU32(23, m_rgbColor); + s.writeBool(24, m_useReverseAPI); + s.writeString(25, m_reverseAPIAddress); + s.writeU32(26, m_reverseAPIPort); + s.writeU32(27, m_reverseAPIFeatureSetIndex); + s.writeU32(28, m_reverseAPIFeatureIndex); + + return s.final(); +} + +bool StarTrackerSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readString(1, &m_ra, ""); + d.readString(2, &m_dec, ""); + d.readDouble(3, &m_latitude, 0.0); + d.readDouble(4, &m_longitude, 0.0); + d.readString(5, &m_target, "Sun"); + d.readString(6, &m_dateTime, ""); + d.readBool(7, &m_enableServer, true); + d.readU32(8, &utmp, 0); + if ((utmp > 1023) && (utmp < 65535)) { + m_serverPort = utmp; + } else { + m_serverPort = 10001; + } + d.readS32(9, (qint32 *)&m_azElUnits, DM); + d.readFloat(10, &m_updatePeriod, 1.0f); + d.readBool(11, &m_jnow, false); + d.readString(12, &m_refraction, "Positional Astronomy Library"); + d.readDouble(13, &m_pressure, 1010); + d.readDouble(14, &m_temperature, 10); + d.readDouble(15, &m_humidity, 10); + d.readDouble(16, &m_heightAboveSeaLevel, 80); + d.readDouble(17, &m_temperatureLapseRate, 6.49); + d.readDouble(18, &m_frequency, 435000000.0); + d.readBool(19, &m_drawSunOnMap, true); + d.readBool(20, &m_drawMoonOnMap, true); + d.readBool(21, &m_drawStarOnMap, true); + + d.readString(22, &m_title, "Star Tracker"); + d.readU32(23, &m_rgbColor, QColor(225, 25, 99).rgb()); + d.readBool(24, &m_useReverseAPI, false); + d.readString(25, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(26, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(27, &utmp, 0); + m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; + d.readU32(28, &utmp, 0); + m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/feature/startracker/startrackersettings.h b/plugins/feature/startracker/startrackersettings.h new file mode 100644 index 000000000..17290f603 --- /dev/null +++ b/plugins/feature/startracker/startrackersettings.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_STARTRACKERSETTINGS_H_ +#define INCLUDE_FEATURE_STARTRACKERSETTINGS_H_ + +#include +#include + +#include "util/message.h" + +class Serializable; + +struct StarTrackerSettings +{ + QString m_ra; + QString m_dec; + double m_latitude; + double m_longitude; + QString m_target; // Sun, Moon, Custom + QString m_dateTime; // Date/time for observation, or "" for now + QString m_refraction; // Refraction correction. "None", "Saemundsson" or "Positional Astronomy Library" + double m_pressure; // Air pressure in millibars + double m_temperature; // Air temperature in C + double m_humidity; // Humidity in % + double m_heightAboveSeaLevel; // In metres + double m_temperatureLapseRate; // In K/km + double m_frequency; // Observation frequency in Hz + uint16_t m_serverPort; + bool m_enableServer; // Enable Stellarium server + enum AzElUnits {DMS, DM, D, Decimal} m_azElUnits; + float m_updatePeriod; + bool m_jnow; // Use JNOW epoch rather than J2000 + bool m_drawSunOnMap; + bool m_drawMoonOnMap; + bool m_drawStarOnMap; + + QString m_title; + quint32 m_rgbColor; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIFeatureSetIndex; + uint16_t m_reverseAPIFeatureIndex; + + StarTrackerSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif // INCLUDE_FEATURE_STARTRACKERSETTINGS_H_ diff --git a/plugins/feature/startracker/startrackersettingsdialog.cpp b/plugins/feature/startracker/startrackersettingsdialog.cpp new file mode 100644 index 000000000..f5dd6cbcd --- /dev/null +++ b/plugins/feature/startracker/startrackersettingsdialog.cpp @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "startrackersettingsdialog.h" +#include + +StarTrackerSettingsDialog::StarTrackerSettingsDialog(StarTrackerSettings *settings, + QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::StarTrackerSettingsDialog) +{ + ui->setupUi(this); + ui->epoch->setCurrentIndex(settings->m_jnow ? 1 : 0); + ui->azElUnits->setCurrentIndex((int)settings->m_azElUnits); + ui->updatePeriod->setValue(settings->m_updatePeriod); + ui->serverPort->setValue(settings->m_serverPort); + ui->enableServer->setChecked(settings->m_enableServer); + ui->refraction->setCurrentIndex(ui->refraction->findText(settings->m_refraction)); + ui->pressure->setValue(settings->m_pressure); + ui->temperature->setValue(settings->m_temperature); + ui->humidity->setValue(settings->m_humidity); + ui->height->setValue(settings->m_heightAboveSeaLevel); + ui->temperatureLapseRate->setValue(settings->m_temperatureLapseRate); + ui->frequency->setValue(settings->m_frequency/1000000.0); + ui->drawSunOnMap->setChecked(settings->m_drawSunOnMap); + ui->drawMoonOnMap->setChecked(settings->m_drawMoonOnMap); + ui->drawStarOnMap->setChecked(settings->m_drawStarOnMap); +} + +StarTrackerSettingsDialog::~StarTrackerSettingsDialog() +{ + delete ui; +} + +void StarTrackerSettingsDialog::accept() +{ + m_settings->m_jnow = ui->epoch->currentIndex() == 1; + m_settings->m_azElUnits = (StarTrackerSettings::AzElUnits)ui->azElUnits->currentIndex(); + m_settings->m_updatePeriod = ui->updatePeriod->value(); + m_settings->m_serverPort = (uint16_t)ui->serverPort->value(); + m_settings->m_enableServer = ui->enableServer->isChecked(); + m_settings->m_refraction = ui->refraction->currentText(); + m_settings->m_pressure = ui->pressure->value(); + m_settings->m_temperature = ui->temperature->value(); + m_settings->m_humidity = ui->humidity->value(); + m_settings->m_heightAboveSeaLevel = ui->height->value(); + m_settings->m_temperatureLapseRate = ui->temperatureLapseRate->value(); + m_settings->m_frequency = ui->frequency->value() * 1000000.0; + m_settings->m_drawSunOnMap = ui->drawSunOnMap->isChecked(); + m_settings->m_drawMoonOnMap = ui->drawMoonOnMap->isChecked(); + m_settings->m_drawStarOnMap = ui->drawStarOnMap->isChecked(); + QDialog::accept(); +} diff --git a/plugins/feature/startracker/startrackersettingsdialog.h b/plugins/feature/startracker/startrackersettingsdialog.h new file mode 100644 index 000000000..25fe401c7 --- /dev/null +++ b/plugins/feature/startracker/startrackersettingsdialog.h @@ -0,0 +1,40 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_STARTRACKERSETTINGSDIALOG_H +#define INCLUDE_STARTRACKERSETTINGSDIALOG_H + +#include "ui_startrackersettingsdialog.h" +#include "startrackersettings.h" + +class StarTrackerSettingsDialog : public QDialog { + Q_OBJECT + +public: + explicit StarTrackerSettingsDialog(StarTrackerSettings* settings, QWidget* parent = 0); + ~StarTrackerSettingsDialog(); + + StarTrackerSettings *m_settings; + +private slots: + void accept(); + +private: + Ui::StarTrackerSettingsDialog* ui; +}; + +#endif // INCLUDE_STARTRACKERSETTINGSDIALOG_H diff --git a/plugins/feature/startracker/startrackersettingsdialog.ui b/plugins/feature/startracker/startrackersettingsdialog.ui new file mode 100644 index 000000000..8c1c15a72 --- /dev/null +++ b/plugins/feature/startracker/startrackersettingsdialog.ui @@ -0,0 +1,382 @@ + + + StarTrackerSettingsDialog + + + + 0 + 0 + 351 + 468 + + + + + Liberation Sans + 9 + + + + Star Tracker Settings + + + + + + + + + Height above sea level (m) + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Draw target star on map + + + + + + + Air temperature (C) + + + + + + + Refraction correction + + + + + + + Stellarium telescope server IP port number + + + 1024 + + + 65535 + + + 10001 + + + + + + + Radio frequency being observed + + + 1000000000 + + + 435 + + + + + + + Air pressure (mb) + + + + + + + Air pressure in millibars, for use in atmospheric refraction correction + + + 2000.000000000000000 + + + 1.000000000000000 + + + 1010.000000000000000 + + + + + + + Epoch for RA & Dec + + + + + + + Air temperature in degrees Celsius, for use in atmospheric refraction correction + + + -100 + + + 100 + + + 10 + + + + + + + Draw Sun on map + + + + + + + Draw Moon on map + + + + + + + Relative humidity in % + + + 100 + + + 80 + + + + + + + Stellarium server port + + + + + + + Update period (s) + + + + + + + Humidity (%) + + + + + + + Enter the time in seconds between each calculation of the target's position + + + 1.000000000000000 + + + + + + + Height of observation/antenna location above sea level in metres + + + -1000 + + + 20000 + + + + + + + Frequency (MHz) + + + + + + + Epoch for custom right ascension and declination + + + + J2000 + + + + + JNOW + + + + + + + + Azimuth and elevation units + + + + + + + Atmospheric refraction correction + + + 0 + + + + None + + + + + Saemundsson + + + + + Positional Astronomy Library + + + + + + + + Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium + + + Stellarium server + + + + + + + Units used for displaying azimuth and elevation. Either degrees, minutes and seconds or decimal degrees. + + + false + + + + ° ' " + + + + + ° ' + + + + + ° + + + + + Decimal + + + + + + + + Temperature lapse rate (K/m) + + + Temperature lapse rate (K/km) + + + + + + + 3 + + + 100.000000000000000 + + + 6.490000000000000 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + StarTrackerSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + StarTrackerSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/feature/startracker/startrackerwebapiadapter.cpp b/plugins/feature/startracker/startrackerwebapiadapter.cpp new file mode 100644 index 000000000..002c8c775 --- /dev/null +++ b/plugins/feature/startracker/startrackerwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGFeatureSettings.h" +#include "startracker.h" +#include "startrackerwebapiadapter.h" + +StarTrackerWebAPIAdapter::StarTrackerWebAPIAdapter() +{} + +StarTrackerWebAPIAdapter::~StarTrackerWebAPIAdapter() +{} + +int StarTrackerWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings()); + response.getSimplePttSettings()->init(); + StarTracker::webapiFormatFeatureSettings(response, m_settings); + + return 200; +} + +int StarTrackerWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + StarTracker::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response); + + return 200; +} diff --git a/plugins/feature/startracker/startrackerwebapiadapter.h b/plugins/feature/startracker/startrackerwebapiadapter.h new file mode 100644 index 000000000..6e30ee297 --- /dev/null +++ b/plugins/feature/startracker/startrackerwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB. // +// // +// 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_STARTRACKER_WEBAPIADAPTER_H +#define INCLUDE_STARTRACKER_WEBAPIADAPTER_H + +#include "feature/featurewebapiadapter.h" +#include "startrackersettings.h" + +/** + * Standalone API adapter only for the settings + */ +class StarTrackerWebAPIAdapter : public FeatureWebAPIAdapter { +public: + StarTrackerWebAPIAdapter(); + virtual ~StarTrackerWebAPIAdapter(); + + virtual QByteArray serialize() const { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& featureSettingsKeys, + SWGSDRangel::SWGFeatureSettings& response, + QString& errorMessage); + +private: + StarTrackerSettings m_settings; +}; + +#endif // INCLUDE_STARTRACKER_WEBAPIADAPTER_H diff --git a/plugins/feature/startracker/startrackerworker.cpp b/plugins/feature/startracker/startrackerworker.cpp new file mode 100644 index 000000000..268a2a3fb --- /dev/null +++ b/plugins/feature/startracker/startrackerworker.cpp @@ -0,0 +1,551 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "SWGTargetAzimuthElevation.h" +#include "SWGMapItem.h" + +#include "webapi/webapiadapterinterface.h" +#include "webapi/webapiutils.h" + +#include "util/units.h" +#include "maincore.h" + +#include "startracker.h" +#include "startrackerworker.h" +#include "startrackerreport.h" + +MESSAGE_CLASS_DEFINITION(StarTrackerWorker::MsgConfigureStarTrackerWorker, Message) +MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportAzAl, Message) +MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportRADec, Message) + +StarTrackerWorker::StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInterface *webAPIAdapterInterface) : + m_starTracker(starTracker), + m_webAPIAdapterInterface(webAPIAdapterInterface), + m_msgQueueToFeature(nullptr), + m_msgQueueToGUI(nullptr), + m_running(false), + m_mutex(QMutex::Recursive), + m_tcpServer(nullptr), + m_clientConnection(nullptr) +{ + connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update())); +} + +StarTrackerWorker::~StarTrackerWorker() +{ + m_inputMessageQueue.clear(); +} + +void StarTrackerWorker::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); +} + +bool StarTrackerWorker::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_pollTimer.start((int)round(m_settings.m_updatePeriod*1000.0)); + m_running = true; + return m_running; +} + +void StarTrackerWorker::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + restartServer(false, 0); + m_pollTimer.stop(); + m_running = false; +} + +void StarTrackerWorker::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool StarTrackerWorker::handleMessage(const Message& cmd) +{ + if (MsgConfigureStarTrackerWorker::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureStarTrackerWorker& cfg = (MsgConfigureStarTrackerWorker&) cmd; + + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else + { + return false; + } +} + +void StarTrackerWorker::applySettings(const StarTrackerSettings& settings, bool force) +{ + qDebug() << "StarTrackerWorker::applySettings:" + << " m_target: " << settings.m_target + << " m_ra: " << settings.m_ra + << " m_dec: " << settings.m_dec + << " m_time: " << settings.m_dateTime + << " m_enableServer: " << settings.m_enableServer + << " m_serverPort: " << settings.m_serverPort + << " m_updatePeriod: " << settings.m_updatePeriod + << " force: " << force; + + if ((m_settings.m_target != settings.m_target) + || (m_settings.m_latitude != settings.m_latitude) + || (m_settings.m_longitude != settings.m_longitude) + || (m_settings.m_dateTime != settings.m_dateTime) + || (m_settings.m_refraction != settings.m_refraction) + || (m_settings.m_pressure != settings.m_pressure) + || (m_settings.m_temperature != settings.m_temperature) + || (m_settings.m_ra != settings.m_ra) + || (m_settings.m_dec != settings.m_dec) || force) + { + // Recalculate immediately + QTimer::singleShot(1, this, &StarTrackerWorker::update); + m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0)); + } + else if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force) + { + m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0)); + } + + if (!settings.m_drawSunOnMap && m_settings.m_drawSunOnMap) + removeFromMap("Sun"); + if (!settings.m_drawMoonOnMap && m_settings.m_drawMoonOnMap) + removeFromMap("Moon"); + if ((!settings.m_drawStarOnMap && m_settings.m_drawStarOnMap) + || (((settings.m_target == "Sun") || (settings.m_target == "Moon")) + && ((m_settings.m_target != "Sun") && (m_settings.m_target != "Moon")))) + removeFromMap("Star"); + + if ((settings.m_serverPort != m_settings.m_serverPort) || + (settings.m_enableServer != m_settings.m_enableServer) || force) + { + restartServer(settings.m_enableServer, settings.m_serverPort); + } + + m_settings = settings; +} + +void StarTrackerWorker::restartServer(bool enabled, uint32_t port) +{ + if (m_tcpServer) + { + if (m_clientConnection) + { + m_clientConnection->close(); + delete m_clientConnection; + m_clientConnection = nullptr; + } + + disconnect(m_tcpServer, &QTcpServer::newConnection, this, &StarTrackerWorker::acceptConnection); + m_tcpServer->close(); + delete m_tcpServer; + m_tcpServer = nullptr; + } + + if (enabled) + { + qDebug() << "StarTrackerWorker::restartServer: server enabled on port " << port; + m_tcpServer = new QTcpServer(this); + + if (!m_tcpServer->listen(QHostAddress::Any, port)) { + qWarning("Star Tracker failed to listen on port %u. Check it is not already in use.", port); + } else { + connect(m_tcpServer, &QTcpServer::newConnection, this, &StarTrackerWorker::acceptConnection); + } + } +} + +void StarTrackerWorker::acceptConnection() +{ + QMutexLocker mutexLocker(&m_mutex); + m_clientConnection = m_tcpServer->nextPendingConnection(); + + if (!m_clientConnection) { + return; + } + + connect(m_clientConnection, &QIODevice::readyRead, this, &StarTrackerWorker::readStellariumCommand); + connect(m_clientConnection, SIGNAL(disconnected()), this, SLOT(disconnected())); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + connect(m_clientConnection, QOverload::of(&QAbstractSocket::error), this, &StarTrackerWorker::errorOccurred); +#else + connect(m_clientConnection, &QAbstractSocket::errorOccurred, this, &StarTrackerWorker::errorOccurred); +#endif + qDebug() << "StarTrackerWorker::acceptConnection: client connected"; +} + +void StarTrackerWorker::disconnected() +{ + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "StarTrackerWorker::disconnected"; + m_clientConnection->deleteLater(); + m_clientConnection = nullptr; + /*if (m_msgQueueToFeature) + { + StarTrackerWorker::MsgReportWorker *msg = StarTrackerWorker::MsgReportWorker::create("Disconnected"); + m_msgQueueToFeature->push(msg); + }*/ +} + +void StarTrackerWorker::errorOccurred(QAbstractSocket::SocketError socketError) +{ + qDebug() << "StarTrackerWorker::errorOccurred: " << socketError; + /*if (m_msgQueueToFeature) + { + StarTrackerWorker::MsgReportWorker *msg = StarTrackerWorker::MsgReportWorker::create(m_socket.errorString() + " " + socketError); + m_msgQueueToFeature->push(msg); + }*/ +} + +// Get RA & Dec from Stellarium +// Protocol described here: +// http://svn.code.sf.net/p/stellarium/code/trunk/telescope_server/stellarium_telescope_protocol.txt +void StarTrackerWorker::readStellariumCommand() +{ + QMutexLocker mutexLocker(&m_mutex); + + unsigned char buf[64]; + qint64 len; + + len = m_clientConnection->read((char *)buf, sizeof(buf)); + if (len != -1) + { + int msg_len; + int msg_type; + unsigned char *msg; + + // Extract length and message type + msg_len = buf[0] | (buf[1] << 8); + msg_type = buf[2] | (buf[3] << 8); + msg = &buf[4]; + + if (msg_type == 0) // MessageGoto + { + unsigned ra; + int dec; + + if (msg_len == 20) + { + // Skip time + msg += 8; + // Extract RA LSB first + ra = msg[0] | (msg[1] << 8) | (msg[2] << 16) | (msg[3] << 24); + msg += 4; + // Extract DEC LSB first + dec = msg[0] | (msg[1] << 8) | (msg[2] << 16) | (msg[3] << 24); + msg += 4; + + // Convert from integer to floating point + double raDeg = ra*(24.0/4294967296.0); // Convert to decimal hours + double decDeg = dec*(360.0/4294967296.0); // Convert to decimal degrees + + // Set as current target + m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDeg); + m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(decDeg); + + qDebug() << "StarTrackerWorker: New target from Stellarum: " << m_settings.m_ra << " " << m_settings.m_dec; + + // Forward to GUI for display + if (getMessageQueueToGUI()) + getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(raDeg, decDeg)); + } + else + { + qDebug() << "StarTrackerWorker: Unexpected number of bytes received (" << len << ") for message type: " << msg_type; + } + } + else + { + qDebug() << "StarTrackerWorker: Unsupported Stellarium message type: " << msg_type; + } + } +} + +// Send our target to Stellarium (J2000 epoch) +void StarTrackerWorker::writeStellariumTarget(double ra, double dec) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_clientConnection != nullptr) + { + unsigned char buf[24]; + + // Length + buf[0] = sizeof(buf); + buf[1] = 0; + // Type (MessageCurrentPosition) + buf[2] = 0; + buf[3] = 0; + // Time (unused) + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + buf[8] = 0; + buf[9] = 0; + buf[10] = 0; + buf[11] = 0; + // RA + unsigned raInt = ra * (4294967296.0/24.0); + buf[12] = raInt & 0xff; + buf[13] = (raInt >> 8) & 0xff; + buf[14] = (raInt >> 16) & 0xff; + buf[15] = (raInt >> 24) & 0xff; + // Dec + int decInt = dec * (4294967296.0/360.0); + buf[16] = decInt & 0xff; + buf[17] = (decInt >> 8) & 0xff; + buf[18] = (decInt >> 16) & 0xff; + buf[19] = (decInt >> 24) & 0xff; + // Status (OK) + buf[20] = 0; + buf[21] = 0; + buf[22] = 0; + buf[23] = 0; + + m_clientConnection->write((char *)buf, sizeof(buf)); + } +} + + +void StarTrackerWorker::updateRaDec(RADec rd, QDateTime dt) +{ + RADec rdJ2000; + double jd; + + jd = Astronomy::julianDate(dt); + // Precess to J2000 + rdJ2000 = Astronomy::precess(rd, jd, Astronomy::jd_j2000()); + // Send to Stellarium + writeStellariumTarget(rdJ2000.ra, rdJ2000.dec); + // Send to GUI + if (m_settings.m_target == "Sun" || m_settings.m_target == "Moon") + { + if (getMessageQueueToGUI()) + { + if (m_settings.m_jnow) + getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec)); + else + getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rdJ2000.ra, rdJ2000.dec)); + } + } +} + +void StarTrackerWorker::removeFromMap(QString id) +{ + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems"); + if (mapMessageQueues) + { + sendToMap(mapMessageQueues, id, "", "", 0.0, 0.0); + } +} + +void StarTrackerWorker::sendToMap(QList *mapMessageQueues, QString name, QString image, QString text, double lat, double lon, double rotation) +{ + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem(); + swgMapItem->setName(new QString(name)); + swgMapItem->setLatitude(lat); + swgMapItem->setLongitude(lon); + swgMapItem->setImage(new QString(image)); + swgMapItem->setImageRotation(rotation); + swgMapItem->setText(new QString(text)); + swgMapItem->setImageFixedSize(1); + + MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_starTracker, swgMapItem); + (*it)->push(msg); + } +} + +QString moonPhase(double sunLongitude, double moonLongitude, double observationLatitude, double &rotation) +{ + double difference = sunLongitude - moonLongitude; + if (difference < -180.0) + difference += 360.0; + else if (difference > 180.0) + difference -= 360.0; + + if (difference >= 0.0) + rotation = observationLatitude - 90.0; + else + rotation = 90.0 - observationLatitude; + + // These probably shouldn't be divided equally + if (difference < -157.5) + return "full"; + else if (difference < -112.5) + return "waxing-gibbous"; + else if (difference < -67.5) + return "first-quarter"; + else if (difference < -22.5) + return "waxing-crescent"; + else if (difference < 22.5) + return "new"; + else if (difference < 67.5) + return "waning-crescent"; + else if (difference < 112.5) + return "third-quarter"; + else if (difference < 157.5) + return "waning-gibbous"; + else if (difference < 202.5) + return "full"; + else + return "full"; +} + +void StarTrackerWorker::update() +{ + AzAlt aa, sunAA, moonAA; + RADec rd, sunRD, moonRD; + + QDateTime dt; + + // Get date and time to calculate position at + if (m_settings.m_dateTime == "") + dt = QDateTime::currentDateTime(); + else + dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs); + + // Calculate position + if ((m_settings.m_target == "Sun") || (m_settings.m_drawSunOnMap)) + Astronomy::sunPosition(sunAA, sunRD, m_settings.m_latitude, m_settings.m_longitude, dt); + if ((m_settings.m_target == "Moon") || (m_settings.m_drawMoonOnMap)) + Astronomy::moonPosition(moonAA, moonRD, m_settings.m_latitude, m_settings.m_longitude, dt); + + if (m_settings.m_target == "Sun") + { + rd = sunRD; + aa = sunAA; + } + else if (m_settings.m_target == "Moon") + { + rd = moonRD; + aa = moonAA; + } + else + { + // Convert RA/Dec to Alt/Az + rd.ra = Astronomy::raToDecimal(m_settings.m_ra); + rd.dec = Astronomy::decToDecimal(m_settings.m_dec); + aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow); + } + updateRaDec(rd, dt); + + // Adjust for refraction + if (m_settings.m_refraction == "Positional Astronomy Library") + { + aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity, + m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel, + m_settings.m_temperatureLapseRate); + if (aa.alt > 90.0) + aa.alt = 90.0f; + } + else if (m_settings.m_refraction == "Saemundsson") + { + aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature); + if (aa.alt > 90.0) + aa.alt = 90.0f; + } + + // Send to GUI + if (getMessageQueueToGUI()) + { + StarTrackerReport::MsgReportAzAl *msg = StarTrackerReport::MsgReportAzAl::create(aa.az, aa.alt); + getMessageQueueToGUI()->push(msg); + } + + // Send Az/El to Rotator Controllers + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "target"); + if (mapMessageQueues) + { + QList::iterator it = mapMessageQueues->begin(); + + for (; it != mapMessageQueues->end(); ++it) + { + SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation(); + swgTarget->setName(new QString(m_settings.m_target)); + swgTarget->setAzimuth(aa.az); + swgTarget->setElevation(aa.alt); + (*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_starTracker, swgTarget)); + } + } + + // Send to Map + if (m_settings.m_drawSunOnMap || m_settings.m_drawMoonOnMap || m_settings.m_drawStarOnMap) + { + mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems"); + if (mapMessageQueues) + { + // Different between GMST(Lst at Greenwich) and RA + double lst = Astronomy::localSiderealTime(dt, 0.0); + double sunLongitude; + double sunLatitude; + + if (m_settings.m_drawSunOnMap || m_settings.m_drawMoonOnMap) + { + sunLongitude = Astronomy::lstAndRAToLongitude(lst, sunRD.ra); + sunLatitude = sunRD.dec; + sendToMap(mapMessageQueues, "Sun", "qrc:///startracker/startracker/sun-40.png", "Sun", sunLatitude, sunLongitude); + } + if (m_settings.m_drawMoonOnMap) + { + double moonLongitude = Astronomy::lstAndRAToLongitude(lst, moonRD.ra); + double moonLatitude = moonRD.dec; + double moonRotation; + QString phase = moonPhase(sunLongitude, moonLongitude, m_settings.m_latitude, moonRotation); + sendToMap(mapMessageQueues, "Moon", QString("qrc:///startracker/startracker/moon-%1-32").arg(phase), "Moon", + moonLatitude, moonLongitude, moonRotation); + } + if ((m_settings.m_drawStarOnMap) && (m_settings.m_target != "Sun") && (m_settings.m_target != "Moon")) + { + double starLongitude = Astronomy::lstAndRAToLongitude(lst, rd.ra); + double starLatitude = rd.dec; + QString text = m_settings.m_target == "Custom" ? "Star" : m_settings.m_target; + sendToMap(mapMessageQueues, "Star", "qrc:///startracker/startracker/pulsar-32.png", text, starLatitude, starLongitude); + } + } + } + +} diff --git a/plugins/feature/startracker/startrackerworker.h b/plugins/feature/startracker/startrackerworker.h new file mode 100644 index 000000000..591b0cbd9 --- /dev/null +++ b/plugins/feature/startracker/startrackerworker.h @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_FEATURE_STARTRACKERWORKER_H_ +#define INCLUDE_FEATURE_STARTRACKERWORKER_H_ + +#include +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/astronomy.h" + +#include "startrackersettings.h" + +class WebAPIAdapterInterface; +class QTcpServer; +class QTcpSocket; +class StarTracker; +class QDateTime; + +class StarTrackerWorker : public QObject +{ + Q_OBJECT +public: + class MsgConfigureStarTrackerWorker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const StarTrackerSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureStarTrackerWorker* create(const StarTrackerSettings& settings, bool force) + { + return new MsgConfigureStarTrackerWorker(settings, force); + } + + private: + StarTrackerSettings m_settings; + bool m_force; + + MsgConfigureStarTrackerWorker(const StarTrackerSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInterface *webAPIAdapterInterface); + ~StarTrackerWorker(); + void reset(); + bool startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + void setMessageQueueToFeature(MessageQueue *messageQueue) { m_msgQueueToFeature = messageQueue; } + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; } + +private: + + StarTracker* m_starTracker; + WebAPIAdapterInterface *m_webAPIAdapterInterface; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object + MessageQueue *m_msgQueueToGUI; + StarTrackerSettings m_settings; + bool m_running; + QMutex m_mutex; + QTimer m_pollTimer; + QTcpServer *m_tcpServer; + QTcpSocket *m_clientConnection; + + bool handleMessage(const Message& cmd); + void applySettings(const StarTrackerSettings& settings, bool force = false); + void restartServer(bool enabled, uint32_t port); + MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; } + void setAzimuth(int azimuth); + void setAzimuthElevation(int azimuth, int elevation); + void updateRaDec(RADec rd, QDateTime dt); + void writeStellariumTarget(double ra, double dec); + void removeFromMap(QString id); + void sendToMap(QList *mapMessageQueues, QString id, QString image, QString text, double lat, double lon, double rotation=0.0); + +private slots: + void handleInputMessages(); + void update(); + void acceptConnection(); + void disconnected(); + void errorOccurred(QAbstractSocket::SocketError socketError); + void readStellariumCommand(); +}; + +#endif // INCLUDE_FEATURE_STARTRACKERWORKER_H_ diff --git a/plugins/feature/vorlocalizer/vorlocalizer.cpp b/plugins/feature/vorlocalizer/vorlocalizer.cpp index 220b5d7b7..a01a0cc82 100644 --- a/plugins/feature/vorlocalizer/vorlocalizer.cpp +++ b/plugins/feature/vorlocalizer/vorlocalizer.cpp @@ -244,8 +244,8 @@ bool VORLocalizer::handleMessage(const Message& cmd) qDebug() << "VORLocalizer::handleMessage: MsgReportChannelDeleted"; MessagePipesCommon::MsgReportChannelDeleted& report = (MessagePipesCommon::MsgReportChannelDeleted&) cmd; const MessagePipesCommon::ChannelRegistrationKey& channelKey = report.getChannelRegistrationKey(); - const ChannelAPI *channel = channelKey.m_key; - m_availableChannels.remove(const_cast(channel)); + const PipeEndPoint *channel = channelKey.m_key; + m_availableChannels.remove(const_cast(reinterpret_cast(channel))); updateChannels(); MessageQueue *messageQueue = MainCore::instance()->getMessagePipes().unregisterChannelToFeature(channel, this, "report"); disconnect(messageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleChannelMessageQueue(MessageQueue*))); diff --git a/plugins/feature/vorlocalizer/vorlocalizergui.cpp b/plugins/feature/vorlocalizer/vorlocalizergui.cpp index c25048bbc..afe9792b4 100644 --- a/plugins/feature/vorlocalizer/vorlocalizergui.cpp +++ b/plugins/feature/vorlocalizer/vorlocalizergui.cpp @@ -376,8 +376,8 @@ static bool calcIntersectionPoint(float lat1, float lon1, float bearing1, float double lat3Rad = asin(sinLat1*cos(delta13)+cosLat1*sin(delta13)*cos(theta13)); double lon3Rad = lon1Rad + atan2(sin(theta13)*sin(delta13)*cosLat1, cos(delta13)-sinLat1*sin(lat3Rad)); - intersectLat = Units::radiansToDegress(lat3Rad); - intersectLon = Units::radiansToDegress(lon3Rad); + intersectLat = Units::radiansToDegrees(lat3Rad); + intersectLon = Units::radiansToDegrees(lon3Rad); return true; } diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 1b163b135..35b21ce28 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -158,6 +158,7 @@ set(sdrbase_SOURCES feature/feature.cpp feature/featureset.cpp feature/featureutils.cpp + feature/featurewebapiutils.cpp limerfe/limerfeusbcalib.cpp @@ -167,12 +168,16 @@ set(sdrbase_SOURCES pipes/messagepipes.cpp pipes/messagepipescommon.cpp pipes/messagepipesgcworker.cpp + pipes/pipeendpoint.cpp settings/featuresetpreset.cpp settings/preferences.cpp settings/preset.cpp settings/mainsettings.cpp + util/ax25.cpp + util/aprs.cpp + util/astronomy.cpp util/azel.cpp util/crc.cpp util/CRC64.cpp @@ -181,6 +186,7 @@ set(sdrbase_SOURCES util/fixedtraits.cpp util/httpdownloadmanager.cpp util/lfsr.cpp + util/maidenhead.cpp util/message.cpp util/messagequeue.cpp util/morse.cpp @@ -338,6 +344,7 @@ set(sdrbase_HEADERS feature/feature.h feature/featureset.h feature/featureutils.h + feature/featurewebapiutils.h limerfe/limerfeusbcalib.h @@ -349,6 +356,7 @@ set(sdrbase_HEADERS pipes/messagepipes.h pipes/messagepipescommon.h pipes/messagepipesgcworker.h + pipes/pipeendpoint.h plugin/plugininterface.h plugin/pluginapi.h @@ -359,6 +367,9 @@ set(sdrbase_HEADERS settings/preset.h settings/mainsettings.h + util/ax25.h + util/aprs.h + util/astronomy.h util/azel.h util/CRC64.h util/csv.h @@ -370,6 +381,7 @@ set(sdrbase_HEADERS util/incrementalarray.h util/incrementalvector.h util/lfsr.h + util/maidenhead.h util/message.h util/messagequeue.h util/morse.h diff --git a/sdrbase/channel/channelapi.cpp b/sdrbase/channel/channelapi.cpp index 92fcccd1e..b16f8b4e1 100644 --- a/sdrbase/channel/channelapi.cpp +++ b/sdrbase/channel/channelapi.cpp @@ -19,6 +19,7 @@ #include "util/uid.h" #include "channelapi.h" +#include "maincore.h" ChannelAPI::ChannelAPI(const QString& uri, StreamType streamType) : m_streamType(streamType), diff --git a/sdrbase/channel/channelapi.h b/sdrbase/channel/channelapi.h index 6be93b38a..c9b984211 100644 --- a/sdrbase/channel/channelapi.h +++ b/sdrbase/channel/channelapi.h @@ -27,6 +27,7 @@ #include #include "export.h" +#include "pipes/pipeendpoint.h" class DeviceAPI; @@ -37,7 +38,7 @@ namespace SWGSDRangel class SWGChannelActions; } -class SDRBASE_API ChannelAPI { +class SDRBASE_API ChannelAPI : public PipeEndPoint { public: enum StreamType //!< This is the same enum as in PluginInterface { diff --git a/sdrbase/feature/feature.cpp b/sdrbase/feature/feature.cpp index 1e4940c32..51048ac4a 100644 --- a/sdrbase/feature/feature.cpp +++ b/sdrbase/feature/feature.cpp @@ -23,6 +23,7 @@ #include "SWGDeviceState.h" #include "feature.h" +#include "maincore.h" Feature::Feature(const QString& uri, WebAPIAdapterInterface *webAPIAdapterInterface) : m_webAPIAdapterInterface(webAPIAdapterInterface), @@ -45,6 +46,18 @@ void Feature::handleInputMessages() } } +void Feature::handlePipeMessageQueue(MessageQueue* messageQueue) +{ + Message* message; + + while ((message = messageQueue->pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + int Feature::webapiRunGet( SWGSDRangel::SWGDeviceState& response, QString& errorMessage) const diff --git a/sdrbase/feature/feature.h b/sdrbase/feature/feature.h index 372419e21..aeac7a18f 100644 --- a/sdrbase/feature/feature.h +++ b/sdrbase/feature/feature.h @@ -25,6 +25,7 @@ #include #include "export.h" +#include "pipes/pipeendpoint.h" #include "util/messagequeue.h" class WebAPIAdapterInterface; @@ -40,7 +41,7 @@ namespace SWGSDRangel class SWGChannelSettings; } -class SDRBASE_API Feature : public QObject { +class SDRBASE_API Feature : public QObject, public PipeEndPoint { Q_OBJECT public: enum FeatureState { @@ -155,6 +156,8 @@ protected: protected slots: void handleInputMessages(); + void handlePipeMessageQueue(MessageQueue* messageQueue); + friend PipeEndPoint; private: QString m_name; //!< Unique identifier in a device set used for sorting may change depending on relative position in device set diff --git a/sdrbase/feature/featurewebapiutils.cpp b/sdrbase/feature/featurewebapiutils.cpp new file mode 100644 index 000000000..ec8e09bd7 --- /dev/null +++ b/sdrbase/feature/featurewebapiutils.cpp @@ -0,0 +1,101 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "SWGFeatureActions.h" +#include "SWGMapActions.h" + +#include "maincore.h" +#include "feature/featureset.h" +#include "feature/feature.h" +#include "featurewebapiutils.h" + +// Find the specified target on the map +bool FeatureWebAPIUtils::mapFind(const QString& target, int featureSetIndex, int featureIndex) +{ + Feature *feature = FeatureWebAPIUtils::getFeature(featureSetIndex, featureIndex, "sdrangel.feature.map"); + if (feature != nullptr) + { + QString errorMessage; + QStringList featureActionKeys = {"find"}; + SWGSDRangel::SWGFeatureActions query; + SWGSDRangel::SWGMapActions *mapActions = new SWGSDRangel::SWGMapActions(); + + mapActions->setFind(new QString(target)); + query.setMapActions(mapActions); + + int httpRC = feature->webapiActionsPost(featureActionKeys, query, errorMessage); + if (httpRC/100 != 2) + { + qWarning() << "FeatureWebAPIUtils::mapFind: error " << httpRC << ":" << errorMessage; + return false; + } + + return true; + } + else + { + qWarning("FeatureWebAPIUtils::mapFind: no Map feature"); + return false; + } +} + +// Get first feature with the given URI +Feature* FeatureWebAPIUtils::getFeature(int featureSetIndex, int featureIndex, const QString& uri) +{ + FeatureSet *featureSet; + Feature *feature; + std::vector& featureSets = MainCore::instance()->getFeatureeSets(); + + if (featureSetIndex != -1) + { + // Find feature with specific index + if (featureSetIndex < (int)featureSets.size()) + { + featureSet = featureSets[featureSetIndex]; + if (featureIndex < featureSet->getNumberOfFeatures()) + { + feature = featureSet->getFeatureAt(featureIndex); + if (uri.isEmpty() || feature->getURI() == uri) + return feature; + else + return nullptr; + } + else + return nullptr; + } + else + return nullptr; + } + else + { + // Find first feature matching URI + for (std::vector::const_iterator it = featureSets.begin(); it != featureSets.end(); ++it, featureIndex++) + { + for (int fi = 0; fi < (*it)->getNumberOfFeatures(); fi++) + { + feature = (*it)->getFeatureAt(fi); + if (feature->getURI() == uri) + { + return feature; + } + } + } + return nullptr; + } +} diff --git a/sdrbase/feature/featurewebapiutils.h b/sdrbase/feature/featurewebapiutils.h new file mode 100644 index 000000000..808ac6665 --- /dev/null +++ b/sdrbase/feature/featurewebapiutils.h @@ -0,0 +1,32 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_ +#define SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_ + +#include "export.h" + +class Feature; + +class SDRBASE_API FeatureWebAPIUtils +{ +public: + static bool mapFind(const QString& target, int featureSetIndex=-1, int featureIndex=-1); + static Feature *getFeature(int featureSetIndex, int featureIndex, const QString& uri); +}; + +#endif // SDRBASE_FEATURE_FEATUREWEBAPIUTILS_H_ diff --git a/sdrbase/maincore.cpp b/sdrbase/maincore.cpp index 87cec9ae3..c84adfc0c 100644 --- a/sdrbase/maincore.cpp +++ b/sdrbase/maincore.cpp @@ -48,6 +48,9 @@ MESSAGE_CLASS_DEFINITION(MainCore::MsgDeleteFeature, Message) MESSAGE_CLASS_DEFINITION(MainCore::MsgChannelReport, Message) MESSAGE_CLASS_DEFINITION(MainCore::MsgChannelSettings, Message) MESSAGE_CLASS_DEFINITION(MainCore::MsgChannelDemodReport, Message) +MESSAGE_CLASS_DEFINITION(MainCore::MsgMapItem, Message) +MESSAGE_CLASS_DEFINITION(MainCore::MsgPacket, Message) +MESSAGE_CLASS_DEFINITION(MainCore::MsgTargetAzimuthElevation, Message) MainCore::MainCore() { diff --git a/sdrbase/maincore.h b/sdrbase/maincore.h index be6119abf..1fc77834d 100644 --- a/sdrbase/maincore.h +++ b/sdrbase/maincore.h @@ -22,16 +22,17 @@ #include #include +#include #include "export.h" #include "settings/mainsettings.h" #include "util/message.h" #include "pipes/messagepipes.h" #include "pipes/datapipes.h" +#include "channel/channelapi.h" class DeviceSet; class FeatureSet; -class ChannelAPI; class Feature; class PluginManager; class MessageQueue; @@ -44,6 +45,8 @@ namespace SWGSDRangel { class SWGChannelReport; class SWGChannelSettings; + class SWGMapItem; + class SWGTargetAzimuthElevation; } class SDRBASE_API MainCore @@ -498,6 +501,89 @@ public: { } }; + // Message to Map feature to display an item on the map + class SDRBASE_API MsgMapItem : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PipeEndPoint *getPipeSource() const { return m_pipeSource; } + SWGSDRangel::SWGMapItem *getSWGMapItem() const { return m_swgMapItem; } + + static MsgMapItem* create(const PipeEndPoint *pipeSource, SWGSDRangel::SWGMapItem *swgMapItem) + { + return new MsgMapItem(pipeSource, swgMapItem); + } + + private: + const PipeEndPoint *m_pipeSource; + SWGSDRangel::SWGMapItem *m_swgMapItem; + + MsgMapItem(const PipeEndPoint *pipeSource, SWGSDRangel::SWGMapItem *swgMapItem) : + Message(), + m_pipeSource(pipeSource), + m_swgMapItem(swgMapItem) + { } + }; + + // Message to pass received packets between channels and features + class SDRBASE_API MsgPacket : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PipeEndPoint *getPipeSource() const { return m_pipeSource; } + QByteArray getPacket() const { return m_packet; } + QDateTime getDateTime() const { return m_dateTime; } + + static MsgPacket* create(const PipeEndPoint *pipeSource, QByteArray packet) + { + return new MsgPacket(pipeSource, packet, QDateTime::currentDateTime()); + } + + static MsgPacket* create(const PipeEndPoint *pipeSource, QByteArray packet, QDateTime dateTime) + { + return new MsgPacket(pipeSource, packet, dateTime); + } + + private: + const PipeEndPoint *m_pipeSource; + QByteArray m_packet; + QDateTime m_dateTime; + + MsgPacket(const PipeEndPoint *pipeSource, QByteArray packet, QDateTime dateTime) : + Message(), + m_pipeSource(pipeSource), + m_packet(packet), + m_dateTime(dateTime) + { + } + }; + + // Message to pass target azimuth and elevation between channels & features + class SDRBASE_API MsgTargetAzimuthElevation : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PipeEndPoint *getPipeSource() const { return m_pipeSource; } + SWGSDRangel::SWGTargetAzimuthElevation *getSWGTargetAzimuthElevation() const { return m_swgTargetAzimuthElevation; } + + static MsgTargetAzimuthElevation* create(const PipeEndPoint *pipeSource, SWGSDRangel::SWGTargetAzimuthElevation *swgTargetAzimuthElevation) + { + return new MsgTargetAzimuthElevation(pipeSource, swgTargetAzimuthElevation); + } + + private: + const PipeEndPoint *m_pipeSource; + SWGSDRangel::SWGTargetAzimuthElevation *m_swgTargetAzimuthElevation; + + MsgTargetAzimuthElevation(const PipeEndPoint *pipeSource, SWGSDRangel::SWGTargetAzimuthElevation *swgTargetAzimuthElevation) : + Message(), + m_pipeSource(pipeSource), + m_swgTargetAzimuthElevation(swgTargetAzimuthElevation) + { } + }; + + + MainCore(); ~MainCore(); static MainCore *instance(); diff --git a/sdrbase/pipes/messagepipes.cpp b/sdrbase/pipes/messagepipes.cpp index 9f997d5da..7977bd011 100644 --- a/sdrbase/pipes/messagepipes.cpp +++ b/sdrbase/pipes/messagepipes.cpp @@ -21,6 +21,7 @@ #include "messagepipesgcworker.h" #include "messagepipes.h" +#include "pipeendpoint.h" MessagePipes::MessagePipes() { @@ -41,19 +42,19 @@ MessagePipes::~MessagePipes() } } -MessageQueue *MessagePipes::registerChannelToFeature(const ChannelAPI *source, Feature *feature, const QString& type) +MessageQueue *MessagePipes::registerChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type) { return m_registrations.registerProducerToConsumer(source, feature, type); } -MessageQueue *MessagePipes::unregisterChannelToFeature(const ChannelAPI *source, Feature *feature, const QString& type) +MessageQueue *MessagePipes::unregisterChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type) { MessageQueue *messageQueue = m_registrations.unregisterProducerToConsumer(source, feature, type); m_gcWorker->addMessageQueueToDelete(messageQueue); return messageQueue; } -QList* MessagePipes::getMessageQueues(const ChannelAPI *source, const QString& type) +QList* MessagePipes::getMessageQueues(const PipeEndPoint *source, const QString& type) { return m_registrations.getElements(source, type); } diff --git a/sdrbase/pipes/messagepipes.h b/sdrbase/pipes/messagepipes.h index 7b66f945d..0848adc13 100644 --- a/sdrbase/pipes/messagepipes.h +++ b/sdrbase/pipes/messagepipes.h @@ -29,7 +29,7 @@ #include "messagepipescommon.h" #include "elementpipesregistrations.h" -class ChannelAPI; +class PipeEndPoint; class Feature; class MessagePipesGCWorker; class MessageQueue; @@ -43,12 +43,12 @@ public: MessagePipes& operator=(const MessagePipes&) = delete; ~MessagePipes(); - MessageQueue *registerChannelToFeature(const ChannelAPI *source, Feature *feature, const QString& type); - MessageQueue *unregisterChannelToFeature(const ChannelAPI *source, Feature *feature, const QString& type); - QList* getMessageQueues(const ChannelAPI *source, const QString& type); + MessageQueue *registerChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type); + MessageQueue *unregisterChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type); + QList* getMessageQueues(const PipeEndPoint *source, const QString& type); private: - ElementPipesRegistrations m_registrations; + ElementPipesRegistrations m_registrations; QThread m_gcThread; //!< Garbage collector thread MessagePipesGCWorker *m_gcWorker; //!< Garbage collector diff --git a/sdrbase/pipes/messagepipescommon.h b/sdrbase/pipes/messagepipescommon.h index d51b05e50..42e7dbb2c 100644 --- a/sdrbase/pipes/messagepipescommon.h +++ b/sdrbase/pipes/messagepipescommon.h @@ -26,14 +26,14 @@ #include "util/message.h" #include "elementpipescommon.h" -class ChannelAPI; +class PipeEndPoint; class Feature; class MessageQueue; class SDRBASE_API MessagePipesCommon { public: - typedef ElementPipesCommon::RegistrationKey ChannelRegistrationKey; + typedef ElementPipesCommon::RegistrationKey ChannelRegistrationKey; /** Send this message to stakeholders when the garbage collector finds that a channel was deleted */ class SDRBASE_API MsgReportChannelDeleted : public Message { diff --git a/sdrbase/pipes/messagepipesgcworker.cpp b/sdrbase/pipes/messagepipesgcworker.cpp index 53b877022..572da409c 100644 --- a/sdrbase/pipes/messagepipesgcworker.cpp +++ b/sdrbase/pipes/messagepipesgcworker.cpp @@ -15,15 +15,20 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include "channel/channelapi.h" #include "feature/feature.h" #include "util/messagequeue.h" #include "maincore.h" #include "messagepipescommon.h" #include "messagepipesgcworker.h" -bool MessagePipesGCWorker::MessagePipesGC::existsProducer(const ChannelAPI *channel) +bool MessagePipesGCWorker::MessagePipesGC::existsProducer(const PipeEndPoint *pipeEndPoint) { - return MainCore::instance()->existsChannel(channel); + // Not overly sure about casting to both types here, but currently safeish as the + // existing functions only use the pointer address - and I presume these + // may be pointers to deleted objects anyway? + return MainCore::instance()->existsChannel((const ChannelAPI *)pipeEndPoint) + || MainCore::instance()->existsFeature((const Feature *)pipeEndPoint); } bool MessagePipesGCWorker::MessagePipesGC::existsConsumer(const Feature *feature) diff --git a/sdrbase/pipes/messagepipesgcworker.h b/sdrbase/pipes/messagepipesgcworker.h index 0af0ecfde..5b9ce1543 100644 --- a/sdrbase/pipes/messagepipesgcworker.h +++ b/sdrbase/pipes/messagepipesgcworker.h @@ -50,10 +50,10 @@ public: bool isRunning() const { return m_running; } private: - class MessagePipesGC : public ElementPipesGC + class MessagePipesGC : public ElementPipesGC { private: - virtual bool existsProducer(const ChannelAPI *channelAPI); + virtual bool existsProducer(const PipeEndPoint *pipeEndPoint); virtual bool existsConsumer(const Feature *feature); virtual void sendMessageToConsumer(const MessageQueue *messageQueue, MessagePipesCommon::ChannelRegistrationKey key, Feature *feature); }; diff --git a/sdrbase/pipes/pipeendpoint.cpp b/sdrbase/pipes/pipeendpoint.cpp new file mode 100644 index 000000000..bf5ed4f2b --- /dev/null +++ b/sdrbase/pipes/pipeendpoint.cpp @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#include "dsp/dspengine.h" +#include "device/deviceset.h" +#include "channel/channelapi.h" +#include "feature/featureset.h" +#include "feature/feature.h" +#include "maincore.h" + +#include "pipeendpoint.h" + +MESSAGE_CLASS_DEFINITION(PipeEndPoint::MsgReportPipes, Message) + +QList PipeEndPoint::updateAvailablePipeSources(QString pipeName, QStringList pipeTypes, QStringList pipeURIs, Feature *destinationFeature) +{ + MainCore *mainCore = MainCore::instance(); + MessagePipes& messagePipes = mainCore->getMessagePipes(); + std::vector& deviceSets = mainCore->getDeviceSets(); + QHash availablePipes; + + int deviceIndex = 0; + for (std::vector::const_iterator it = deviceSets.begin(); it != deviceSets.end(); ++it, deviceIndex++) + { + DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine; + DSPDeviceSinkEngine *deviceSinkEngine = (*it)->m_deviceSinkEngine; + + if (deviceSourceEngine || deviceSinkEngine) + { + for (int chi = 0; chi < (*it)->getNumberOfChannels(); chi++) + { + ChannelAPI *channel = (*it)->getChannelAt(chi); + int i = pipeURIs.indexOf(channel->getURI()); + + if (i >= 0) + { + if (!availablePipes.contains(channel)) + { + MessageQueue *messageQueue = messagePipes.registerChannelToFeature(channel, destinationFeature, pipeName); + QObject::connect( + messageQueue, + &MessageQueue::messageEnqueued, + destinationFeature, + [=](){ destinationFeature->handlePipeMessageQueue(messageQueue); }, + Qt::QueuedConnection + ); + } + + AvailablePipeSource availablePipe = + AvailablePipeSource{ + deviceSinkEngine != nullptr ? AvailablePipeSource::TX : AvailablePipeSource::RX, + deviceIndex, + chi, + channel, + pipeTypes.at(i) + }; + availablePipes[channel] = availablePipe; + } + } + } + } + + std::vector& featureSets = mainCore->getFeatureeSets(); + int featureIndex = 0; + for (std::vector::const_iterator it = featureSets.begin(); it != featureSets.end(); ++it, featureIndex++) + { + for (int fi = 0; fi < (*it)->getNumberOfFeatures(); fi++) + { + Feature *feature = (*it)->getFeatureAt(fi); + int i = pipeURIs.indexOf(feature->getURI()); + + if (i >= 0) + { + if (!availablePipes.contains(feature)) + { + MessageQueue *messageQueue = messagePipes.registerChannelToFeature(feature, destinationFeature, pipeName); + QObject::connect( + messageQueue, + &MessageQueue::messageEnqueued, + destinationFeature, + [=](){ destinationFeature->handlePipeMessageQueue(messageQueue); }, + Qt::QueuedConnection + ); + } + + AvailablePipeSource availablePipe = + AvailablePipeSource{ + AvailablePipeSource::Feature, + featureIndex, + fi, + feature, + pipeTypes.at(i) + }; + availablePipes[feature] = availablePipe; + } + } + } + + QList availablePipeList; + QHash::iterator it = availablePipes.begin(); + + for (; it != availablePipes.end(); ++it) { + availablePipeList.push_back(*it); + } + return availablePipeList; +} + +PipeEndPoint *PipeEndPoint::getPipeEndPoint(const QString name, const QList &availablePipeSources) +{ + QRegExp re("([TRF])([0-9]+):([0-9]+) ([a-zA-Z0-9]+)"); + if (re.exactMatch(name)) + { + QString type = re.capturedTexts()[1]; + int setIndex = re.capturedTexts()[2].toInt(); + int index = re.capturedTexts()[3].toInt(); + QString id = re.capturedTexts()[4]; + + QListIterator itr(availablePipeSources); + while (itr.hasNext()) { + AvailablePipeSource p = itr.next(); + if ((p.m_setIndex == setIndex) && (p.m_index == index) && (id == p.m_id)) + return p.m_source; + } + } + else + qDebug() << "PipeEndPoint::getPipeEndPoint: " << name << " is malformed"; + return nullptr; +} diff --git a/sdrbase/pipes/pipeendpoint.h b/sdrbase/pipes/pipeendpoint.h new file mode 100644 index 000000000..df510f3c4 --- /dev/null +++ b/sdrbase/pipes/pipeendpoint.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Parent for ChannelAPI and Features, where either can be used. // +// // +// 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 SDRBASE_PIPES_PIPEENDPOINT_H_ +#define SDRBASE_PIPES_PIPEENDPOINT_H_ + +#include +#include +#include + +#include "util/message.h" +#include "export.h" + +class Feature; + +class SDRBASE_API PipeEndPoint { +public: + + // Used by pipe sinks (features) to record details about available pipe sources (channels or features) + struct AvailablePipeSource + { + enum {RX, TX, Feature} m_type; + int m_setIndex; + int m_index; + PipeEndPoint *m_source; + QString m_id; + + AvailablePipeSource() = default; + AvailablePipeSource(const AvailablePipeSource&) = default; + AvailablePipeSource& operator=(const AvailablePipeSource&) = default; + friend bool operator==(const AvailablePipeSource &lhs, const AvailablePipeSource &rhs) + { + return (lhs.m_type == rhs.m_type) + && (lhs.m_setIndex == rhs.m_setIndex) + && (lhs.m_source == rhs.m_source) + && (lhs.m_id == rhs.m_id); + } + + QString getTypeName() const + { + QStringList typeNames = {"R", "T", "F"}; + return typeNames[m_type]; + } + + // Name for use in GUI combo boxes and WebAPI + QString getName() const + { + QString type; + + return QString("%1%2:%3 %4").arg(getTypeName()) + .arg(m_setIndex) + .arg(m_index) + .arg(m_id); + } + }; + + class SDRBASE_API MsgReportPipes : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QList& getAvailablePipes() { return m_availablePipes; } + + static MsgReportPipes* create() { + return new MsgReportPipes(); + } + + private: + QList m_availablePipes; + + MsgReportPipes() : + Message() + {} + }; + + +protected: + + // Utility functions for pipe sinks to manage list of sources + QList updateAvailablePipeSources(QString pipeName, QStringList pipeTypes, QStringList pipeURIs, Feature *destinationFeature); + PipeEndPoint *getPipeEndPoint(const QString name, const QList &availablePipeSources); + +}; + +#endif // SDRBASE_PIPES_PIPEENDPOINT_H_ diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 6887654e4..9fff6eac4 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1062,6 +1062,51 @@ margin-bottom: 20px; } }, "description" : "AMMod" +}; + defs.APRSSettings = { + "properties" : { + "igateServer" : { + "type" : "string" + }, + "igatePort" : { + "type" : "integer" + }, + "igateCallsign" : { + "type" : "string" + }, + "igatePasscode" : { + "type" : "string" + }, + "igateFilter" : { + "type" : "string" + }, + "igateEnabled" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "APRS settings" }; defs.ATVDemodSettings = { "properties" : { @@ -2674,6 +2719,9 @@ margin-bottom: 20px; "LocalSourceSettings" : { "$ref" : "#/definitions/LocalSourceSettings" }, + "PacketDemodSettings" : { + "$ref" : "#/definitions/PacketDemodSettings" + }, "PacketModSettings" : { "$ref" : "#/definitions/PacketModSettings" }, @@ -4018,6 +4066,9 @@ margin-bottom: 20px; "AFCActions" : { "$ref" : "#/definitions/AFCActions" }, + "MapActions" : { + "$ref" : "#/definitions/MapActions" + }, "SimplePTTActions" : { "$ref" : "#/definitions/SimplePTTActions" } @@ -4122,15 +4173,24 @@ margin-bottom: 20px; "AFCSettings" : { "$ref" : "#/definitions/AFCSettings" }, + "APRSSettings" : { + "$ref" : "#/definitions/APRSSettings" + }, "DemodAnalyzerSettings" : { "$ref" : "#/definitions/DemodAnalyzerSettings" }, "GS232ControllerSettings" : { "$ref" : "#/definitions/GS232ControllerSettings" }, + "MapSettings" : { + "$ref" : "#/definitions/MapSettings" + }, "RigCtlServerSettings" : { "$ref" : "#/definitions/RigCtlServerSettings" }, + "StarTrackerSettings" : { + "$ref" : "#/definitions/StarTrackerSettings" + }, "SimplePTTSettings" : { "$ref" : "#/definitions/SimplePTTSettings" }, @@ -4867,7 +4927,20 @@ margin-bottom: 20px; "description" : "The baud rate to use for the serial connection to the GS-232 controller" }, "track" : { - "type" : "integer" + "type" : "integer", + "description" : "Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no)" + }, + "target" : { + "type" : "string", + "description" : "Identifier of the channel or feature plugin providing target azimuth and elevation (E.g. R0:0 ADSBDemod)" + }, + "azimuthOffset" : { + "type" : "integer", + "description" : "Azimuth offset in degrees" + }, + "elevationOffset" : { + "type" : "integer", + "description" : "Elevation offset in degrees" }, "title" : { "type" : "string" @@ -4875,12 +4948,6 @@ margin-bottom: 20px; "rgbColor" : { "type" : "integer" }, - "deviceIndex" : { - "type" : "integer" - }, - "channelIndex" : { - "type" : "integer" - }, "useReverseAPI" : { "type" : "integer", "description" : "Synchronize with reverse API (1 for yes, 0 for no)" @@ -6156,7 +6223,7 @@ margin-bottom: 20px; "latitude" : { "type" : "number", "format" : "float", - "description" : "Lautitude in decimal degrees positive to the north" + "description" : "Latitude in decimal degrees positive to the north" }, "longitude" : { "type" : "number", @@ -6186,6 +6253,116 @@ margin-bottom: 20px; } }, "description" : "Logging parameters setting" +}; + defs.MapActions = { + "properties" : { + "find" : { + "type" : "string", + "description" : "The name of the item or the location to centre the map on" + } + }, + "description" : "Map" +}; + defs.MapItem = { + "required" : [ "name" ], + "properties" : { + "name" : { + "type" : "string", + "description" : "A name for the item" + }, + "image" : { + "type" : "string", + "description" : "Filename or URL of image to draw on the map" + }, + "imageRotation" : { + "type" : "integer", + "description" : "Angle to rotate the image by" + }, + "imageFixedSize" : { + "type" : "integer", + "description" : "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + }, + "text" : { + "type" : "string" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees, positive to the north" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees, positive to the east" + } + }, + "description" : "An item to draw on the map. Set image to an empty string to remove item from the map." +}; + defs.MapItem_2 = { + "required" : [ "name" ], + "properties" : { + "name" : { + "type" : "string", + "description" : "A name for the item" + }, + "image" : { + "type" : "string", + "description" : "Filename or URL of image to draw on the map" + }, + "imageRotation" : { + "type" : "integer", + "description" : "Angle to rotate the image by" + }, + "imageFixedSize" : { + "type" : "integer", + "description" : "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + }, + "text" : { + "type" : "string" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees, positive to the north" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees, positive to the east" + } + }, + "description" : "An item to draw on the map. Set image to an empty string to remove item from the map." +}; + defs.MapSettings = { + "properties" : { + "displayNames" : { + "type" : "integer", + "description" : "Display object names on the map (1 for yes, 0 for no)" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "Map" }; defs.MetisMISOSettings = { "properties" : { @@ -6554,6 +6731,53 @@ margin-bottom: 20px; } }, "description" : "Enumeration with name for values" +}; + defs.PacketDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "mode" : { + "type" : "string", + "description" : "Transmission mode\n * \"1200 AFSK\"\n" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "streamIndex" : { + "type" : "integer", + "description" : "MIMO channel. Not relevant when connected to SI (single Rx)." + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "PacketDemod" }; defs.PacketModActions = { "properties" : { @@ -8751,6 +8975,121 @@ margin-bottom: 20px; "type" : "integer" } } +}; + defs.StarTrackerSettings = { + "properties" : { + "target" : { + "type" : "string", + "description" : "Target object (Sun, Moon or Custom)" + }, + "ra" : { + "type" : "string", + "description" : "Right ascension of custom target" + }, + "dec" : { + "type" : "string", + "description" : "Declination of custom target" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees (North positive) of observation/antenna location" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees (East positive) of observation/antenna location" + }, + "dateTime" : { + "type" : "string", + "description" : "Date and time of observation. ISO 8601 extended format: yyyy-MM-ddTHH:mm:ss with Z suffix for UTC. Empty string for current time." + }, + "refraction" : { + "type" : "string", + "description" : "Atmospheric refraction correction (None or Saemundsson)" + }, + "pressure" : { + "type" : "number", + "format" : "float", + "description" : "Air pressure in millibars, for refraction" + }, + "temperature" : { + "type" : "number", + "format" : "float", + "description" : "Air temperature in Celsuis, for refraction" + }, + "humidity" : { + "type" : "number", + "format" : "float", + "description" : "Relative humidity in %, for refraction" + }, + "heightAboveSeaLevel" : { + "type" : "number", + "format" : "float", + "description" : "Height above sea level in metres of observation/antenna location" + }, + "temperatureLapseRate" : { + "type" : "number", + "format" : "float", + "description" : "Temperature lapse rate in K/km" + }, + "frequency" : { + "type" : "number", + "format" : "float", + "description" : "Frequency of radio waves being observed in MHz" + }, + "stellariumServerEnabled" : { + "type" : "integer" + }, + "stellariumPort" : { + "type" : "integer", + "description" : "IP port number for Stellarium server to listen on (Default is 10001)." + }, + "updatePeriod" : { + "type" : "number", + "format" : "float", + "description" : "Time in seconds between each calculation of the target's position" + }, + "epoch" : { + "type" : "string", + "description" : "Epoch for RA and Dec (J2000 or JNOW)" + }, + "drawSunOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the Sun on the Map (1 for yes, 0 for no)" + }, + "drawMoonOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the Moon on the Map (1 for yes, 0 for no)" + }, + "drawStarOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the target Star on the Map (1 for yes, 0 for no)" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "Star Tracker settings" }; defs.SuccessResponse = { "required" : [ "message" ], @@ -8759,6 +9098,22 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.TargetAzimuthElevation = { + "properties" : { + "name" : { + "type" : "string" + }, + "azimuth" : { + "type" : "number", + "format" : "float" + }, + "elevation" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "A target azimuth and elevation" }; defs.TestMISettings = { "properties" : { @@ -44917,7 +45272,7 @@ except ApiException as e:
- Generated 2020-12-20T18:34:47.837+01:00 + Generated 2021-01-13T17:40:49.583+01:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/APRS.yaml b/sdrbase/resources/webapi/doc/swagger/include/APRS.yaml new file mode 100644 index 000000000..6a65d4aea --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/APRS.yaml @@ -0,0 +1,30 @@ +APRSSettings: + description: "APRS settings" + properties: + igateServer: + type: string + igatePort: + type: integer + igateCallsign: + type: string + igatePasscode: + type: string + igateFilter: + type: string + igateEnabled: + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml index 7e32be1a1..9f914cd10 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml @@ -63,6 +63,8 @@ ChannelSettings: $ref: "/doc/swagger/include/LocalSink.yaml#/LocalSinkSettings" LocalSourceSettings: $ref: "/doc/swagger/include/LocalSource.yaml#/LocalSourceSettings" + PacketDemodSettings: + $ref: "/doc/swagger/include/PacketDemod.yaml#/PacketDemodSettings" PacketModSettings: $ref: "/doc/swagger/include/PacketMod.yaml#/PacketModSettings" RemoteSinkSettings: diff --git a/sdrbase/resources/webapi/doc/swagger/include/FeatureActions.yaml b/sdrbase/resources/webapi/doc/swagger/include/FeatureActions.yaml index 1dd79cbbf..ac7afa647 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/FeatureActions.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/FeatureActions.yaml @@ -15,5 +15,7 @@ FeatureActions: type: integer AFCActions: $ref: "/doc/swagger/include/AFC.yaml#/AFCActions" + MapActions: + $ref: "/doc/swagger/include/Map.yaml#/MapActions" SimplePTTActions: $ref: "/doc/swagger/include/SimplePTT.yaml#/SimplePTTActions" diff --git a/sdrbase/resources/webapi/doc/swagger/include/FeatureSettings.yaml b/sdrbase/resources/webapi/doc/swagger/include/FeatureSettings.yaml index d0a0a76ab..7da04ce4f 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/FeatureSettings.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/FeatureSettings.yaml @@ -15,12 +15,18 @@ FeatureSettings: type: integer AFCSettings: $ref: "/doc/swagger/include/AFC.yaml#/AFCSettings" + APRSSettings: + $ref: "/doc/swagger/include/APRS.yaml#/APRSSettings" DemodAnalyzerSettings: $ref: "/doc/swagger/include/DemodAnalyzer.yaml#/DemodAnalyzerSettings" GS232ControllerSettings: $ref: "/doc/swagger/include/GS232Controller.yaml#/GS232ControllerSettings" + MapSettings: + $ref: "/doc/swagger/include/Map.yaml#/MapSettings" RigCtlServerSettings: $ref: "/doc/swagger/include/RigCtlServer.yaml#/RigCtlServerSettings" + StarTrackerSettings: + $ref: "/doc/swagger/include/StarTracker.yaml#/StarTrackerSettings" SimplePTTSettings: $ref: "/doc/swagger/include/SimplePTT.yaml#/SimplePTTSettings" VORLocalizerSettings: diff --git a/sdrbase/resources/webapi/doc/swagger/include/GS232Controller.yaml b/sdrbase/resources/webapi/doc/swagger/include/GS232Controller.yaml index 7c8b2ed12..3ffbbad48 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/GS232Controller.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/GS232Controller.yaml @@ -14,15 +14,21 @@ GS232ControllerSettings: description: The baud rate to use for the serial connection to the GS-232 controller type: integer track: + description: Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no) + type: integer + target: + description: "Identifier of the channel or feature plugin providing target azimuth and elevation (E.g. R0:0 ADSBDemod)" + type: string + azimuthOffset: + description: Azimuth offset in degrees + type: integer + elevationOffset: + description: Elevation offset in degrees type: integer title: type: string rgbColor: type: integer - deviceIndex: - type: integer - channelIndex: - type: integer useReverseAPI: description: Synchronize with reverse API (1 for yes, 0 for no) type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/Map.yaml b/sdrbase/resources/webapi/doc/swagger/include/Map.yaml new file mode 100644 index 000000000..ca50b0b58 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/Map.yaml @@ -0,0 +1,57 @@ +MapSettings: + description: Map + properties: + displayNames: + description: Display object names on the map (1 for yes, 0 for no) + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer + +MapActions: + description: Map + properties: + find: + description: "The name of the item or the location to centre the map on" + type: string + +MapItem: + description: "An item to draw on the map. Set image to an empty string to remove item from the map." + required: + - name + properties: + name: + description: "A name for the item" + type: string + image: + description: "Filename or URL of image to draw on the map" + type: string + imageRotation: + description: "Angle to rotate the image by" + type: integer + imageFixedSize: + description: "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + type: integer + text: + descrption: "Text to draw on the map when item is selected" + type: string + latitude: + description: "Latitude in decimal degrees, positive to the north" + type: number + format: float + longitude: + description: "Longitude in decimal degrees, positive to the east" + type: number + format: float diff --git a/sdrbase/resources/webapi/doc/swagger/include/PacketDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/PacketDemod.yaml new file mode 100644 index 000000000..fe919fdfd --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/PacketDemod.yaml @@ -0,0 +1,35 @@ +PacketDemodSettings: + description: PacketDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + mode: + type: string + description: > + Transmission mode + * "1200 AFSK" + rfBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + rgbColor: + type: integer + title: + type: string + streamIndex: + description: MIMO channel. Not relevant when connected to SI (single Rx). + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/StarTracker.yaml b/sdrbase/resources/webapi/doc/swagger/include/StarTracker.yaml new file mode 100644 index 000000000..99403d533 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/StarTracker.yaml @@ -0,0 +1,87 @@ +StarTrackerSettings: + description: "Star Tracker settings" + properties: + target: + description: "Target object (Sun, Moon or Custom)" + type: string + ra: + description: "Right ascension of custom target" + type: string + dec: + description: "Declination of custom target" + type: string + latitude: + description: "Latitude in decimal degrees (North positive) of observation/antenna location" + type: number + format: float + longitude: + description: "Longitude in decimal degrees (East positive) of observation/antenna location" + type: number + format: float + dateTime: + description: "Date and time of observation. ISO 8601 extended format: yyyy-MM-ddTHH:mm:ss with Z suffix for UTC. Empty string for current time." + type: string + refraction: + description: "Atmospheric refraction correction (None or Saemundsson)" + type: string + pressure: + description: "Air pressure in millibars, for refraction" + type: number + format: float + temperature: + description: "Air temperature in Celsuis, for refraction" + type: number + format: float + humidity: + description: "Relative humidity in %, for refraction" + type: number + format: float + heightAboveSeaLevel: + description: "Height above sea level in metres of observation/antenna location" + type: number + format: float + temperatureLapseRate: + description: "Temperature lapse rate in K/km" + type: number + format: float + frequency: + description: "Frequency of radio waves being observed in MHz" + type: number + format: float + stellariumServerEnabled: + descrption: "Enable Stellarium server (1 for yes, 0 for no)" + type: integer + stellariumPort: + description: "IP port number for Stellarium server to listen on (Default is 10001)." + type: integer + updatePeriod: + description: "Time in seconds between each calculation of the target's position" + type: number + format: float + epoch: + description: "Epoch for RA and Dec (J2000 or JNOW)" + type: string + drawSunOnMap: + description: "Draw the overhead position of the Sun on the Map (1 for yes, 0 for no)" + type: integer + drawMoonOnMap: + description: "Draw the overhead position of the Moon on the Map (1 for yes, 0 for no)" + type: integer + drawStarOnMap: + description: "Draw the overhead position of the target Star on the Map (1 for yes, 0 for no)" + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/swagger.yaml b/sdrbase/resources/webapi/doc/swagger/swagger.yaml index bdf005dde..a7c33c47e 100644 --- a/sdrbase/resources/webapi/doc/swagger/swagger.yaml +++ b/sdrbase/resources/webapi/doc/swagger/swagger.yaml @@ -2744,7 +2744,7 @@ definitions: - longitude properties: latitude: - description: "Lautitude in decimal degrees positive to the north" + description: "Latitude in decimal degrees positive to the north" type: number format: float longitude: @@ -2818,6 +2818,25 @@ definitions: description: "Serial device name or server address" type: string + MapItem: + $ref: "/doc/swagger/include/Map.yaml#/MapItem" + + # This isn't in GS232Controller, as it may eventually be used by other controllers or features + TargetAzimuthElevation: + description: "A target azimuth and elevation" + properties: + name: + descrption: "The name of the target" + type: string + azimuth: + descrption: "The azimuth angle in degrees to the target" + type: number + format: float + elevation: + descrption: "The elevation angle in degrees to the target" + type: number + format: float + Presets: description: "Settings presets" required: diff --git a/sdrbase/util/aprs.cpp b/sdrbase/util/aprs.cpp new file mode 100644 index 000000000..ee2029e13 --- /dev/null +++ b/sdrbase/util/aprs.cpp @@ -0,0 +1,990 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "aprs.h" + +// See: http://www.aprs.org/doc/APRS101.PDF + +// Currently we only decode what we want to display on the map +bool APRSPacket::decode(AX25Packet packet) +{ + // Check type, PID and length of packet + if ((packet.m_type == "UI") && (packet.m_pid == "f0") && (packet.m_dataASCII.length() >= 1)) + { + // Check destination address + QRegExp re("^(AIR.*|ALL.*|AP.*|BEACON|CQ.*|GPS.*|DF.*|DGPS.*|DRILL.*|DX.*|ID.*|JAVA.*|MAIL.*|MICE.*|QST.*|QTH.*|RTCM.*|SKY.*|SPACE.*|SPC.*|SYM.*|TEL.*|TEST.*|TLM.*|WX.*|ZIP.*)"); + if (re.exactMatch(packet.m_to)) + { + m_from = packet.m_from; + m_to = packet.m_to; + m_via = packet.m_via; + m_data = packet.m_dataASCII; + + if (packet.m_to.startsWith("GPS") || packet.m_to.startsWith("SPC") || packet.m_to.startsWith("SYM")) + { + // FIXME: Trailing letters xyz specify a symbol + } + + // Source address SSID can be used to specify a symbol + + // First byte of information field is data type ID + char dataType = packet.m_dataASCII[0].toLatin1(); + int idx = 1; + switch (dataType) + { + case '!': // Position without timestamp or Ultimeter 2000 WX Station + parsePosition(packet.m_dataASCII, idx); + if (m_symbolCode == '_') + parseWeather(packet.m_dataASCII, idx, false); + else if (m_symbolCode == '@') + parseStorm(packet.m_dataASCII, idx); + else + { + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + } + break; + case '#': // Peet Bros U-II Weather Station + case '$': // Raw GPS data or Ultimeter 2000 + case '%': // Agrelo DFJr / MicroFinder + break; + case ')': // Item + parseItem(packet.m_dataASCII, idx); + parsePosition(packet.m_dataASCII, idx); + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + break; + case '*': // Peet Bros U-II Weather Station + break; + case '/': // Position with timestamp (no APRS messaging) + parseTime(packet.m_dataASCII, idx); + parsePosition(packet.m_dataASCII, idx); + if (m_symbolCode == '_') + parseWeather(packet.m_dataASCII, idx, false); + else if (m_symbolCode == '@') + parseStorm(packet.m_dataASCII, idx); + else + { + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + } + break; + case ':': // Message + parseMessage(packet.m_dataASCII, idx); + break; + case ';': // Object + parseObject(packet.m_dataASCII, idx); + parseTime(packet.m_dataASCII, idx); + parsePosition(packet.m_dataASCII, idx); + if (m_symbolCode == '_') + parseWeather(packet.m_dataASCII, idx, false); + else if (m_symbolCode == '@') + parseStorm(packet.m_dataASCII, idx); + else + { + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + } + break; + case '<': // Station Capabilities + break; + case '=': // Position without timestamp (with APRS messaging) + parsePosition(packet.m_dataASCII, idx); + if (m_symbolCode == '_') + parseWeather(packet.m_dataASCII, idx, false); + else if (m_symbolCode == '@') + parseStorm(packet.m_dataASCII, idx); + else + { + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + } + break; + case '>': // Status + parseStatus(packet.m_dataASCII, idx); + break; + case '?': // Query + break; + case '@': // Position with timestamp (with APRS messaging) + parseTime(packet.m_dataASCII, idx); + parsePosition(packet.m_dataASCII, idx); + if (m_symbolCode == '_') + parseWeather(packet.m_dataASCII, idx, false); + else if (m_symbolCode == '@') + parseStorm(packet.m_dataASCII, idx); + else + { + parseDataExension(packet.m_dataASCII, idx); + parseComment(packet.m_dataASCII, idx); + } + break; + case 'T': // Telemetry data + parseTelemetry(packet.m_dataASCII, idx); + break; + case '_': // Weather report (without position) + parseTimeMDHM(packet.m_dataASCII, idx); + parseWeather(packet.m_dataASCII, idx, true); + break; + case '{': // User-defined APRS packet format + break; + default: + return false; + } + + if (m_hasSymbol) + { + int num = m_symbolCode - '!'; + m_symbolImage = QString("aprs/aprs/aprs-symbols-24-%1-%2.png").arg(m_symbolTable == '/' ? 0 : 1).arg(num, 2, 10, QChar('0')); + } + + return true; + } + } + + return false; +} + +int APRSPacket::charToInt(QString&s, int idx) +{ + char c = s[idx].toLatin1(); + return c == ' ' ? 0 : c - '0'; +} + +bool APRSPacket::parseTime(QString& info, int& idx) +{ + if (info.length() < idx+7) + return false; + + QDateTime currentDateTime; + + if (info[idx+6]=='h') + { + // HMS format + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit()) + { + int hour = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int min = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int sec = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + + if (hour > 23) + return false; + if (min > 59) + return false; + if (sec > 60) // Can have 60 seconds when there's a leap second + return false; + + m_utc = true; + m_timestamp = QDateTime(QDate::currentDate(), QTime(hour, min, sec)); + m_hasTimestamp = true; + + idx += 7; + return true; + } + else + return false; + } + else if ((info[idx+6]=='z') || (info[idx+6]=='/')) + { + // DHM format + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit()) + { + int day = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int hour = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int min = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + + if (day > 31) + return false; + if (hour > 23) + return false; + if (min > 59) + return false; + + m_utc = info[idx+6]=='z'; + currentDateTime = m_utc ? QDateTime::currentDateTimeUtc() : QDateTime::currentDateTime(); + m_timestamp = QDateTime(QDate(currentDateTime.date().year(), currentDateTime.date().month(), day), QTime(hour, min, 0)); + m_hasTimestamp = true; + + idx += 7; + return true; + } + else + return false; + } + else + return false; +} + +// Time format used in weather reports without position +bool APRSPacket::parseTimeMDHM(QString& info, int& idx) +{ + if (info.length() < idx+8) + return false; + + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && info[idx+3].isDigit() + && info[idx+4].isDigit() + && info[idx+5].isDigit() + && info[idx+6].isDigit() + && info[idx+7].isDigit()) + { + int month = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int day = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int hour = charToInt(info, idx+4) * 10 + charToInt(info, idx+5); + int min = charToInt(info, idx+6) * 10 + charToInt(info, idx+7); + + if (month > 12) + return false; + if (day > 31) + return false; + if (hour > 23) + return false; + if (min > 59) + return false; + + m_utc = true; + QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); + m_timestamp = QDateTime(QDate(currentDateTime.date().year(), month, day), QTime(hour, min, 0)); + m_hasTimestamp = true; + + return true; + } + else + return false; +} + +// Position ambigutiy can be specified by using spaces instead of digits in lats and longs +bool APRSPacket::isLatLongChar(QCharRef c) +{ + return (c.isDigit() || c == ' '); +} + +bool APRSPacket::parsePosition(QString& info, int& idx) +{ + float latitude; + float longitude; + char table; + char code; + + if (info.length() < idx+8+1+9+1) + return false; + + // Latitude + if (info[idx].isDigit() + && info[idx+1].isDigit() + && isLatLongChar(info[idx+2]) + && isLatLongChar(info[idx+3]) + && (info[idx+4]=='.') + && isLatLongChar(info[idx+5]) + && isLatLongChar(info[idx+6]) + && ((info[idx+7]=='N') || (info[idx+7]=='S'))) + { + int deg = charToInt(info, idx) * 10 + charToInt(info, idx+1); + int min = charToInt(info, idx+2) * 10 + charToInt(info, idx+3); + int hundreths = charToInt(info, idx+5) * 10 + charToInt(info, idx+6); + bool north = (info[idx+7]=='N'); + if (deg > 90) + return false; + else if ((deg == 90) && ((min != 0) || (hundreths != 0))) + return false; + latitude = ((float)deg) + min/60.0 + hundreths/60.0/100.0; + if (!north) + latitude = -latitude; + idx += 8; + } + else + return false; + + // Symbol table identifier + table = info[idx++].toLatin1(); + + // Longitude + if (info[idx].isDigit() + && info[idx+1].isDigit() + && info[idx+2].isDigit() + && isLatLongChar(info[idx+3]) + && isLatLongChar(info[idx+4]) + && (info[idx+5]=='.') + && isLatLongChar(info[idx+6]) + && isLatLongChar(info[idx+7]) + && ((info[idx+8]=='E') || (info[idx+8]=='W'))) + { + int deg = charToInt(info, idx) * 100 + charToInt(info, idx+1) * 10 + charToInt(info, idx+2); + int min = charToInt(info, idx+3) * 10 + charToInt(info, idx+4); + int hundreths = charToInt(info, idx+6) * 10 + charToInt(info, idx+7); + bool east = (info[idx+8]=='E'); + if (deg > 180) + return false; + else if ((deg == 180) && ((min != 0) || (hundreths != 0))) + return false; + longitude = ((float)deg) + min/60.0 + hundreths/60.0/100.0; + if (!east) + longitude = -longitude; + idx += 9; + } + else + return false; + + // Symbol table code + code = info[idx++].toLatin1(); + + // Update state as we have a valid position + m_latitude = latitude; + m_longitude = longitude; + m_hasPosition = true; + m_symbolTable = table; + m_symbolCode = code; + m_hasSymbol = true; + return true; +} + +bool APRSPacket::parseDataExension(QString& info, int& idx) +{ + int heightMap[] = {10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120}; + QStringList directivityMap = {"Omni", "NE", "E", "SE", "S", "SW", "W", "NW", "N", ""}; + + int remainingLength = info.length() - idx; + if (remainingLength < 7) + return true; + QString s = info.right(remainingLength); + + // Course and speed + QRegExp courseSpeed("^([0-9]{3})\\/([0-9]{3})"); + if (courseSpeed.indexIn(s) >= 0) + { + m_course = courseSpeed.capturedTexts()[1].toInt(); + m_speed = courseSpeed.capturedTexts()[2].toInt(); + m_hasCourseAndSpeed = true; + idx += 7; + return true; + } + + // Station radio details + QRegExp phg("^PHG([0-9])([0-9])([0-9])([0-9])"); + if (phg.indexIn(s) >= 0) + { + // Transmitter power + int powerCode = phg.capturedTexts()[1].toInt(); + int powerMap[] = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}; + m_powerWatts = powerMap[powerCode]; + + // Antenna height + int heightCode = phg.capturedTexts()[2].toInt(); + m_antennaHeightFt = heightMap[heightCode]; + + // Antenna gain + m_antennaGainDB = phg.capturedTexts()[3].toInt(); + + // Antenna directivity + int directivityCode = phg.capturedTexts()[4].toInt(); + m_antennaDirectivity = directivityMap[directivityCode]; + + m_hasStationDetails = true; + + idx += 7; + return true; + } + + // Radio range + QRegExp rng("^RNG([0-9]{4})"); + if (rng.indexIn(s) >= 0) + { + m_radioRangeMiles = rng.capturedTexts()[1].toInt(); + m_hasRadioRange = true; + idx += 7; + return true; + } + + // Omni-DF strength + QRegExp dfs("^DFS([0-9])([0-9])([0-9])([0-9])"); + if (dfs.indexIn(s) >= 0) + { + // Strength S-points + m_dfStrength = dfs.capturedTexts()[1].toInt(); + + // Antenna height + int heightCode = dfs.capturedTexts()[2].toInt(); + m_dfHeightFt = heightMap[heightCode]; + + // Antenna gain + m_dfGainDB = dfs.capturedTexts()[3].toInt(); + + // Antenna directivity + int directivityCode = dfs.capturedTexts()[4].toInt(); + m_dfAntennaDirectivity = directivityMap[directivityCode]; + + m_hasDf = true; + idx += 7; + return true; + } + + return true; +} + +bool APRSPacket::parseComment(QString& info, int& idx) +{ + int commentLength = info.length() - idx; + if (commentLength > 0) + { + m_comment = info.right(commentLength); + + // Comment can contain altitude anywhere in it. Of the form /A=001234 in feet + QRegExp re("\\/A=([0-9]{6})"); + int pos = re.indexIn(m_comment); + if (pos >= 0) + { + m_altitudeFt = re.capturedTexts()[1].toInt(); + m_hasAltitude = true; + // Strip it out of comment if at start of string + if (pos == 0) + m_comment = m_comment.mid(9); + } + } + + return true; +} + +bool APRSPacket::parseInt(QString& info, int& idx, int chars, int& value, bool& hasValue) +{ + int total = 0; + bool negative = false; + bool noValue = false; + + for (int i = 0; i < chars; i++) + { + if (info[idx].isDigit()) + { + total = total * 10; + total += info[idx].toLatin1() - '0'; + } + else if ((i == 0) && (info[idx] == '-')) + negative = true; + else if ((info[idx] == '.') || (info[idx] == ' ')) + noValue = true; + else + return false; + idx++; + } + if (!noValue) + { + if (negative) + value = -total; + else + value = total; + hasValue = true; + } + else + hasValue = false; + return true; +} + +bool APRSPacket::parseWeather(QString& info, int& idx, bool positionLess) +{ + if (!positionLess) + { + if (!parseInt(info, idx, 3, m_windDirection, m_hasWindDirection)) + return false; + if (info[idx++] != '/') + return false; + if (!parseInt(info, idx, 3, m_windSpeed, m_hasWindSpeed)) + return false; + } + + // Weather data + bool done = false; + while (!done && (idx < info.length())) + { + switch (info[idx++].toLatin1()) + { + case 'c': // Wind direction + if (!parseInt(info, idx, 3, m_windDirection, m_hasWindDirection)) + return false; + break; + case 's': // Wind speed + if (!parseInt(info, idx, 3, m_windSpeed, m_hasWindSpeed)) + return false; + break; + case 'g': // Gust + if (!parseInt(info, idx, 3, m_gust, m_hasGust)) + return false; + break; + case 't': // Temp + if (!parseInt(info, idx, 3, m_temp, m_hasTemp)) + { + qDebug() << "Failed parseing temp: idx" << idx; + return false; + } + break; + case 'r': // Rain last hour + if (!parseInt(info, idx, 3, m_rainLastHr, m_hasRainLastHr)) + return false; + break; + case 'p': // Rain last 24 hours + if (!parseInt(info, idx, 3, m_rainLast24Hrs, m_hasRainLast24Hrs)) + return false; + break; + case 'P': // Rain since midnight + if (!parseInt(info, idx, 3, m_rainSinceMidnight, m_hasRainSinceMidnight)) + return false; + break; + case 'h': // Humidity + if (!parseInt(info, idx, 2, m_humidity, m_hasHumidity)) + return false; + break; + case 'b': // Barometric pressure + if (!parseInt(info, idx, 5, m_barometricPressure, m_hasBarometricPressure)) + return false; + break; + case 'L': // Luminosity <999 + if (!parseInt(info, idx, 3, m_luminosity, m_hasLuminsoity)) + return false; + break; + case 'l': // Luminosity >= 1000 + if (!parseInt(info, idx, 3, m_luminosity, m_hasLuminsoity)) + return false; + m_luminosity += 1000; + break; + case 'S': // Snowfall + if (!parseInt(info, idx, 3, m_snowfallLast24Hrs, m_hasSnowfallLast24Hrs)) + return false; + break; + case '#': // Raw rain counter + if (!parseInt(info, idx, 3, m_rawRainCounter, m_hasRawRainCounter)) + return false; + break; + case 'X': // Radiation level + if (!parseInt(info, idx, 3, m_radiationLevel, m_hasRadiationLevel)) + return false; + break; + case 'F': // Floor water level + if (!parseInt(info, idx, 4, m_floodLevel, m_hasFloodLevel)) + return false; + break; + case 'V': // Battery volts + if (!parseInt(info, idx, 3, m_batteryVolts, m_hasBatteryVolts)) + return false; + break; + default: + done = true; + break; + } + } + if (done) + { + // APRS 1.1 spec says remaining fields are s/w and weather unit type + // But few real-world packets actually seem to conform to the spec + idx--; + int remaining = info.length() - idx; + m_weatherUnitType = info.right(remaining); + idx += remaining; + } + + m_hasWeather = true; + return true; +} + + +bool APRSPacket::parseStorm(QString& info, int& idx) +{ + bool unused; + + if (!parseInt(info, idx, 3, m_stormDirection, unused)) + return false; + if (info[idx++] != '/') + return false; + if (!parseInt(info, idx, 3, m_stormSpeed, unused)) + return false; + if (info[idx++] != '/') + return false; + QString type = info.mid(idx, 2); + idx += 2; + if (type == "TS") + m_stormType = "Tropical storm"; + else if (type == "HC") + m_stormType = "Hurrican"; + else if (type == "TD") + m_stormType = "Tropical depression"; + else + m_stormType = type; + + if (info[idx++] != '/') // Sustained wind speed + return false; + if (!parseInt(info, idx, 3, m_stormSustainedWindSpeed, unused)) + return false; + if (info[idx++] != '^') // Peak wind gusts + return false; + if (!parseInt(info, idx, 3, m_stormPeakWindGusts, unused)) + return false; + if (info[idx++] != '/') // Central pressure + return false; + if (!parseInt(info, idx, 4, m_stormCentralPresure, unused)) + return false; + if (info[idx++] != '>') // Radius hurrican winds + return false; + if (!parseInt(info, idx, 3, m_stormRadiusHurricanWinds, unused)) + return false; + if (info[idx++] != '&') // Radius tropical storm winds + return false; + if (!parseInt(info, idx, 3, m_stormRadiusTropicalStormWinds, unused)) + return false; + m_hasStormData = true; + // Optional field + if (info.length() >= idx + 4) + { + if (info[idx] != '%') // Radius whole gail + return true; + idx++; + if (!parseInt(info, idx, 3, m_stormRadiusWholeGail, m_hasStormRadiusWholeGail)) + return false; + } + return true; +} + +bool APRSPacket::parseObject(QString& info, int& idx) +{ + if (info.length() < idx+10) + return false; + + // Object names are 9 chars + m_objectName = info.mid(idx, 9).trimmed(); + idx += 9; + + if (info[idx] == '*') + m_objectLive = true; + else if (info[idx] == '_') + m_objectKilled = true; + else + return false; + idx++; + + return true; +} + +bool APRSPacket::parseItem(QString& info, int& idx) +{ + if (info.length() < idx+3) + return false; + + // Item names are 3-9 chars long, excluding ! or _ + m_objectName = ""; + int i; + for (i = 0; i < 10; i++) + { + if (info.length() >= idx) + { + QChar c = info[idx]; + if (c == '!' || c == '_') + break; + else + { + m_objectName.append(c); + idx++; + } + } + } + if (i == 11) + return false; + if (info[idx] == '!') + m_objectLive = true; + else if (info[idx] == '_') + m_objectKilled = true; + idx++; + + return true; +} + +bool APRSPacket::parseStatus(QString& info, int& idx) +{ + QString remaining = info.mid(idx); + + QRegExp timestampRE("^([0-9]{6})z"); // DHM timestamp + QRegExp maidenheadRE("^([A-Z]{2}[0-9]{2}[A-Z]{0,2})[/\\\\]."); // Maidenhead grid locator and symbol + + if (timestampRE.indexIn(remaining) >= 0) + { + parseTime(info, idx); + m_status = info.mid(idx); + idx += m_status.length(); + } + else if (maidenheadRE.indexIn(remaining) >= 0) + { + m_maidenhead = maidenheadRE.capturedTexts()[1]; + idx += m_maidenhead.length(); + m_symbolTable = info[idx++].toLatin1(); + m_symbolCode = info[idx++].toLatin1(); + m_hasSymbol = true; + if (info[idx] == ' ') + { + idx++; + m_status = info.mid(idx); + idx += m_status.length(); + } + } + else + { + m_status = remaining; + idx += m_status.length(); + } + m_hasStatus = true; + + // Check for beam heading and power in meteor scatter status reports + int len = m_status.length(); + if (len >= 3) + { + if (m_status[len-3] == '^') + { + bool error = false; + char h = m_status[len-2].toLatin1(); + char p = m_status[len-1].toLatin1(); + + if (isdigit(h)) + m_beamHeading = (h - '0') * 10; + else if (isupper(h)) + m_beamHeading = (h - 'A') * 10 + 100; + else + error = true; + + switch (p) + { + case '1': m_beamPower = 10; break; + case '2': m_beamPower = 40; break; + case '3': m_beamPower = 90; break; + case '4': m_beamPower = 160; break; + case '5': m_beamPower = 250; break; + case '6': m_beamPower = 360; break; + case '7': m_beamPower = 490; break; + case '8': m_beamPower = 640; break; + case '9': m_beamPower = 810; break; + case ':': m_beamPower = 1000; break; + case ';': m_beamPower = 1210; break; + case '<': m_beamPower = 1440; break; + case '=': m_beamPower = 1690; break; + case '>': m_beamPower = 1960; break; + case '?': m_beamPower = 2250; break; + case '@': m_beamPower = 2560; break; + case 'A': m_beamPower = 2890; break; + case 'B': m_beamPower = 3240; break; + case 'C': m_beamPower = 3610; break; + case 'D': m_beamPower = 4000; break; + case 'E': m_beamPower = 4410; break; + case 'F': m_beamPower = 4840; break; + case 'G': m_beamPower = 5290; break; + case 'H': m_beamPower = 5760; break; + case 'I': m_beamPower = 6250; break; + case 'J': m_beamPower = 6760; break; + case 'K': m_beamPower = 7290; break; + default: error = true; break; + } + if (!error) + { + m_hasBeam = true; + m_status = m_status.left(len - 3); + } + } + } + return true; +} + +bool APRSPacket::parseMessage(QString& info, int& idx) +{ + if (info.length() < idx+10) + return false; + + // Addressee is fixed width + if (info[idx+9] != ':') + return false; + m_addressee = info.mid(idx, 9).trimmed(); + idx += 10; + + // Message + m_message = info.mid(idx); + idx += m_message.length(); + + // Check if telemetry parameter/unit names + if (m_message.startsWith("PARM.")) + { + bool done = false; + QString s(""); + int i = 5; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + m_telemetryNames.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + m_telemetryNames.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + } + else if (m_message.startsWith("UNIT.")) + { + bool done = false; + QString s(""); + int i = 5; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + m_telemetryLabels.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + m_telemetryLabels.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + } + else if (m_message.startsWith("EQNS.")) + { + bool done = false; + QString s(""); + int i = 5; + QList telemetryCoefficients; + while (!done) + { + if (i >= m_message.length()) + { + if (!s.isEmpty()) + telemetryCoefficients.append(s); + done = true; + } + else if (m_message[i] == ',') + { + if (!s.isEmpty()) + telemetryCoefficients.append(s); + i++; + s = ""; + } + else + s.append(m_message[i++]); + } + m_hasTelemetryCoefficients = 0; + for (int j = 0; j < telemetryCoefficients.length() / 3; j++) + { + m_telemetryCoefficientsA[j] = telemetryCoefficients[j*3].toDouble(); + m_telemetryCoefficientsB[j] = telemetryCoefficients[j*3+1].toDouble(); + m_telemetryCoefficientsC[j] = telemetryCoefficients[j*3+2].toDouble(); + m_hasTelemetryCoefficients++; + } + } + else if (m_message.startsWith("BITS.")) + { + QString s(""); + int i = 5; + for (int j = 0; j < 8; j++) + { + if (i >= m_message.length()) + m_telemetryBitSense[j] = m_message[i] == '1'; + else + m_telemetryBitSense[j] = true; + i++; + } + m_hasTelemetryBitSense = true; + m_telemetryProjectName = m_message.mid(i); + i += m_telemetryProjectName.length(); + } + else + { + // Check for message number + QRegExp noRE("\\{([0-9]{1,5})$"); + if (noRE.indexIn(m_message) >= 0) + { + m_messageNo = noRE.capturedTexts()[1]; + m_message = m_message.left(m_message.length() - m_messageNo.length() - 1); + } + } + m_hasMessage = true; + + return true; +} + +bool APRSPacket::parseTelemetry(QString& info, int& idx) +{ + if (info[idx] == '#') + { + // Telemetry report + idx++; + if ((info[idx] == 'M') && (info[idx+1] == 'I') && (info[idx+2] == 'C')) + idx += 3; + else if (isdigit(info[idx].toLatin1()) && isdigit(info[idx+1].toLatin1()) && isdigit(info[idx+2].toLatin1())) + { + m_seqNo = info.mid(idx, 3).toInt(); + m_hasSeqNo = true; + idx += 3; + } + else + return false; + + if (info[idx] == ',') + idx++; + parseInt(info, idx, 3, m_a1, m_a1HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a2, m_a2HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a3, m_a3HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a4, m_a4HasValue); + if (info[idx++] != ',') + return false; + parseInt(info, idx, 3, m_a5, m_a5HasValue); + if (info[idx++] != ',') + return false; + for (int i = 0; i < 8; i++) + m_b[i] = info[idx++] == '1'; + m_bHasValue = true; + + m_telemetryComment = info.mid(idx); + idx += m_telemetryComment.length(); + m_hasTelemetry = true; + return true; + } + else + return false; +} diff --git a/sdrbase/util/aprs.h b/sdrbase/util/aprs.h new file mode 100644 index 000000000..9e2d6d3bb --- /dev/null +++ b/sdrbase/util/aprs.h @@ -0,0 +1,445 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_APRS_H +#define INCLUDE_APRS_H + +#include +#include +#include +#include + +#include "export.h" +#include "ax25.h" +#include "util/units.h" + +struct SDRBASE_API APRSPacket { + QString m_from; + QString m_to; + QString m_via; + QString m_data; // Original ASCII data + + QDateTime m_dateTime; // Date/time of reception / decoding + + // Timestamp (where fields are not transmitted, time of decoding is used) + QDateTime m_timestamp; + bool m_utc; // Whether UTC (true) or local time (false) + bool m_hasTimestamp; + + // Position + float m_latitude; + float m_longitude; + bool m_hasPosition; + + float m_altitudeFt; + bool m_hasAltitude; + + // Symbol + char m_symbolTable; + char m_symbolCode; + bool m_hasSymbol; + QString m_symbolImage; // Image filename for the symbol + + // Course and speed + int m_course; + int m_speed; + bool m_hasCourseAndSpeed; + + // Power, antenna height, gain, directivity + int m_powerWatts; + int m_antennaHeightFt; + int m_antennaGainDB; + QString m_antennaDirectivity; // Omni, or N, NE... + bool m_hasStationDetails; + + // Radio range + int m_radioRangeMiles; + bool m_hasRadioRange; + + // Omni-DF + int m_dfStrength; + int m_dfHeightFt; + int m_dfGainDB; + QString m_dfAntennaDirectivity; + bool m_hasDf; + + QString m_objectName; // Also used for items + bool m_objectLive; + bool m_objectKilled; + + QString m_comment; + + // Weather reports + int m_windDirection; // In degrees + bool m_hasWindDirection; + int m_windSpeed; // In mph + bool m_hasWindSpeed; + int m_gust; // Peak wind speed in last 5 minutes in mph + bool m_hasGust; + int m_temp; // Fahrenheit, can be negative down to -99 + bool m_hasTemp; + int m_rainLastHr; // Hundreths of an inch + bool m_hasRainLastHr; + int m_rainLast24Hrs; + bool m_hasRainLast24Hrs; + int m_rainSinceMidnight; + bool m_hasRainSinceMidnight; + int m_humidity; // % + bool m_hasHumidity; + int m_barometricPressure; // Tenths of millibars / tenths of hPascal + bool m_hasBarometricPressure; + int m_luminosity; // Watts per m^2 + bool m_hasLuminsoity; + int m_snowfallLast24Hrs; // In inches + bool m_hasSnowfallLast24Hrs; + int m_rawRainCounter; + bool m_hasRawRainCounter; + int m_radiationLevel; + bool m_hasRadiationLevel; + int m_floodLevel; // Tenths of a foot. Can be negative + bool m_hasFloodLevel; + int m_batteryVolts; // Tenths of a volt + bool m_hasBatteryVolts; + QString m_weatherUnitType; + bool m_hasWeather; + + int m_stormDirection; + int m_stormSpeed; + QString m_stormType; + int m_stormSustainedWindSpeed; // knots + int m_stormPeakWindGusts; // knots + int m_stormCentralPresure; // millibars/hPascal + int m_stormRadiusHurricanWinds; // nautical miles + int m_stormRadiusTropicalStormWinds;// nautical miles + int m_stormRadiusWholeGail; // nautical miles + bool m_hasStormRadiusWholeGail; + bool m_hasStormData; + + // Status messages + QString m_status; + QString m_maidenhead; + int m_beamHeading; + int m_beamPower; + bool m_hasBeam; + bool m_hasStatus; + + // Messages + QString m_addressee; + QString m_message; + QString m_messageNo; + bool m_hasMessage; + QList m_telemetryNames; + QList m_telemetryLabels; + double m_telemetryCoefficientsA[5]; + double m_telemetryCoefficientsB[5]; + double m_telemetryCoefficientsC[5]; + int m_hasTelemetryCoefficients; + int m_telemetryBitSense[8]; + bool m_hasTelemetryBitSense; + QString m_telemetryProjectName; + + // Telemetry + int m_seqNo; + bool m_hasSeqNo; + int m_a1; + bool m_a1HasValue; + int m_a2; + bool m_a2HasValue; + int m_a3; + bool m_a3HasValue; + int m_a4; + bool m_a4HasValue; + int m_a5; + bool m_a5HasValue; + bool m_b[8]; + bool m_bHasValue; + QString m_telemetryComment; + bool m_hasTelemetry; + + bool decode(AX25Packet packet); + + APRSPacket() : + m_hasTimestamp(false), + m_hasPosition(false), + m_hasAltitude(false), + m_hasSymbol(false), + m_hasCourseAndSpeed(false), + m_hasStationDetails(false), + m_hasRadioRange(false), + m_hasDf(false), + m_objectLive(false), + m_objectKilled(false), + m_hasWindDirection(false), + m_hasWindSpeed(false), + m_hasGust(false), + m_hasTemp(false), + m_hasRainLastHr(false), + m_hasRainLast24Hrs(false), + m_hasRainSinceMidnight(false), + m_hasHumidity(false), + m_hasBarometricPressure(false), + m_hasLuminsoity(false), + m_hasSnowfallLast24Hrs(false), + m_hasRawRainCounter(false), + m_hasRadiationLevel(false), + m_hasFloodLevel(false), + m_hasBatteryVolts(false), + m_hasWeather(false), + m_hasStormRadiusWholeGail(false), + m_hasStormData(false), + m_hasBeam(false), + m_hasStatus(false), + m_hasMessage(false), + m_hasTelemetryCoefficients(0), + m_hasTelemetryBitSense(false), + m_hasSeqNo(false), + m_a1HasValue(false), + m_a2HasValue(false), + m_a3HasValue(false), + m_a4HasValue(false), + m_a5HasValue(false), + m_bHasValue(false), + m_hasTelemetry(false) + { + } + + QString date() + { + if (m_hasTimestamp) + return m_timestamp.date().toString("yyyy/MM/dd"); + else + return QString(""); + } + + QString time() + { + if (m_hasTimestamp) + return m_timestamp.time().toString("hh:mm:ss"); + else + return QString(""); + } + + QString dateTime() + { + return QString("%1 %2").arg(date()).arg(time()); + } + + QString position() + { + return QString("%1,%2").arg(m_latitude).arg(m_longitude); + } + + QString toTNC2(QString igateCallsign) + { + return m_from + ">" + m_to + (m_via.isEmpty() ? "" : ("," + m_via)) + ",qAR," + igateCallsign + ":" + m_data + "\r\n"; + } + + // Convert a TNC2 formatted packet (as sent by APRS-IS Igates) to an AX25 byte array + static QByteArray toByteArray(QString tnc2) + { + QByteArray bytes; + QString tmp = ""; + QString from; + int state = 0; + + for (int i = 0; i < tnc2.length(); i++) + { + if (state == 0) + { + // From + if (tnc2[i] == '>') + { + from = tmp; + tmp = ""; + state = 1; + } + else + tmp.append(tnc2[i]); + } + else if (state == 1) + { + // To + if (tnc2[i] == ':') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + bytes.append(AX25Packet::encodeAddress(from, 1)); + state = 3; + } + else if (tnc2[i] == ',') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + bytes.append(AX25Packet::encodeAddress(from)); + tmp = ""; + state = 2; + } + else + tmp.append(tnc2[i]); + } + else if (state == 2) + { + // Via + if (tnc2[i] == ':') + { + bytes.append(AX25Packet::encodeAddress(tmp, 1)); + state = 3; + } + else if (tnc2[i] == ',') + { + bytes.append(AX25Packet::encodeAddress(tmp)); + tmp = ""; + } + else + tmp.append(tnc2[i]); + } + else if (state == 3) + { + // UI Type and PID + bytes.append(3); + bytes.append(-16); // 0xf0 + // APRS message + bytes.append(tnc2.mid(i).toLatin1().trimmed()); + // CRC + bytes.append((char)0); + bytes.append((char)0); + break; + } + } + + return bytes; + } + + QString toText(bool includeFrom=true, bool includePosition=false, char separator='\n') + { + QStringList text; + + if (!m_objectName.isEmpty()) + { + text.append(QString("%1 (%2)").arg(m_objectName).arg(m_from)); + } + else + { + if (includeFrom) + text.append(m_from); + } + + if (m_hasTimestamp) + { + QStringList time; + time.append(this->date()); + time.append(this->time()); + if (m_utc) + time.append("UTC"); + else + time.append("local"); + text.append(time.join(' ')); + } + + if (includePosition && m_hasPosition) + text.append(QString("Latitude: %1 Longitude: %2").arg(m_latitude).arg(m_longitude)); + if (m_hasAltitude) + text.append(QString("Altitude: %1 ft").arg(m_altitudeFt)); + if (m_hasCourseAndSpeed) + text.append(QString("Course: %1%3 Speed: %2 knts").arg(m_course).arg(m_speed).arg(QChar(0xb0))); + + if (m_hasStationDetails) + text.append(QString("TX Power: %1 Watts Antenna Height: %2 m Gain: %3 dB Direction: %4").arg(m_powerWatts).arg(std::round(Units::feetToMetres(m_antennaHeightFt))).arg(m_antennaGainDB).arg(m_antennaDirectivity)); + if (m_hasRadioRange) + text.append(QString("Range: %1 km").arg(Units::milesToKilometres(m_radioRangeMiles))); + if (m_hasDf) + text.append(QString("DF Strength: S %1 Height: %2 m Gain: %3 dB Direction: %4").arg(m_dfStrength).arg(std::round(Units::feetToMetres(m_dfHeightFt))).arg(m_dfGainDB).arg(m_dfAntennaDirectivity)); + + if (m_hasWeather) + { + QStringList weather, wind, air, rain; + + wind.append(QString("Wind")); + if (m_hasWindDirection) + wind.append(QString("%1%2").arg(m_windDirection).arg(QChar(0xb0))); + if (m_hasWindSpeed) + wind.append(QString("%1 mph").arg(m_windSpeed)); + if (m_hasGust) + wind.append(QString("Gusts %1 mph").arg(m_gust)); + weather.append(wind.join(' ')); + + if (m_hasTemp || m_hasHumidity || m_hasBarometricPressure) + { + air.append("Air"); + if (m_hasTemp) + air.append(QString("Temperature %1C").arg(Units::fahrenheitToCelsius(m_temp), 0, 'f', 1)); + if (m_hasHumidity) + air.append(QString("Humidity %1%").arg(m_humidity)); + if (m_hasBarometricPressure) + air.append(QString("Pressure %1 mbar").arg(m_barometricPressure/10.0)); + weather.append(air.join(' ')); + } + + if (m_hasRainLastHr || m_hasRainLast24Hrs || m_hasRainSinceMidnight) + { + rain.append("Rain"); + if (m_hasRainLastHr) + rain.append(QString("%1 mm last hour").arg(std::round(Units::inchesToMilimetres(m_rainLastHr/100.0)))); + if (m_hasRainLast24Hrs) + rain.append(QString("%1 mm last 24 hours").arg(std::round(Units::inchesToMilimetres(m_rainLast24Hrs/100.0)))); + if (m_hasRainSinceMidnight) + rain.append(QString("%1 mm since midnight").arg(std::round(Units::inchesToMilimetres(m_rainSinceMidnight/100.0)))); + weather.append(rain.join(' ')); + } + + if (!m_weatherUnitType.isEmpty()) + weather.append(m_weatherUnitType); + + text.append(weather.join(separator)); + } + + if (m_hasStormData) + { + QStringList storm; + + storm.append(m_stormType); + + storm.append(QString("Direction: %1%3 Speed: %2").arg(m_stormDirection).arg(m_stormSpeed).arg(QChar(0xb0))); + storm.append(QString("Sustained wind speed: %1 knots Peak wind gusts: %2 knots Central pressure: %3 mbar").arg(m_stormSustainedWindSpeed).arg(m_stormPeakWindGusts).arg(m_stormCentralPresure)); + storm.append(QString("Hurrican winds radius: %1 nm Tropical storm winds radius: %2 nm%3").arg(m_stormRadiusHurricanWinds).arg(m_stormRadiusTropicalStormWinds).arg(m_hasStormRadiusWholeGail ? QString("") : QString(" Whole gail radius: %3 nm").arg(m_stormRadiusWholeGail))); + + text.append(storm.join(separator)); + } + + if (!m_comment.isEmpty()) + text.append(m_comment); + + return text.join(separator); + } + +private: + int charToInt(QString &s, int idx); + bool parseTime(QString& info, int& idx); + bool parseTimeMDHM(QString& info, int& idx); + bool isLatLongChar(QCharRef c); + bool parsePosition(QString& info, int& idx); + bool parseDataExension(QString& info, int& idx); + bool parseComment(QString& info, int& idx); + bool parseInt(QString& info, int& idx, int chars, int& value, bool& hasValue); + bool parseWeather(QString& info, int& idx, bool positionLess); + bool parseStorm(QString& info, int& idx); + bool parseObject(QString& info, int& idx); + bool parseItem(QString& info, int& idx); + bool parseStatus(QString& info, int& idx); + bool parseMessage(QString& info, int& idx); + bool parseTelemetry(QString& info, int& idx); +}; + +#endif // INCLUDE_APRS_H diff --git a/sdrbase/util/astronomy.cpp b/sdrbase/util/astronomy.cpp new file mode 100644 index 000000000..c09b3103c --- /dev/null +++ b/sdrbase/util/astronomy.cpp @@ -0,0 +1,892 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// Copyright (C) 2004 Rutherford Appleton Laboratory // +// Copyright (C) 2012 Science and Technology Facilities Council. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include "util/units.h" +#include "astronomy.h" + +// Function prototypes +static void palRefz(double zu, double refa, double refb, double *zr); +static void palRefco (double hm, double tdk, double pmb, double rh, + double wl, double phi, double tlr, double eps, + double *refa, double *refb); +static void palRefro( double zobs, double hm, double tdk, double pmb, + double rh, double wl, double phi, double tlr, + double eps, double * ref); + + +// Calculate Julian date (days since January 1, 4713 BC) from Gregorian calendar date +double Astronomy::julianDate(int year, int month, int day, int hours, int minutes, int seconds) +{ + int julian_day; + double julian_date; + + // From: https://en.wikipedia.org/wiki/Julian_day + julian_day = (1461 * (year + 4800 + (month - 14)/12))/4 +(367 * (month - 2 - 12 * ((month - 14)/12)))/12 - (3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075; + julian_date = julian_day + (hours/24.0 - 0.5) + minutes/(24.0*60.0) + seconds/(24.0*60.0*60.0); + + return julian_date; +} + +// Calculate Julian date +double Astronomy::julianDate(QDateTime dt) +{ + QDateTime utc = dt.toUTC(); + QDate date = utc.date(); + QTime time = utc.time(); + return julianDate(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second()); +} + +// Get Julian date of J2000 Epoch +double Astronomy::jd_j2000(void) +{ + static double j2000 = 0.0; + + if (j2000 == 0.0) { + j2000 = julianDate(2000, 1, 1, 12, 0, 0); + } + return j2000; +} + +// Get Julian date of B1950 Epoch +double Astronomy::jd_b1950(void) +{ + static double b1950 = 0.0; + + if (b1950 == 0.0) { + b1950 = julianDate(1949, 12, 31, 22, 9, 0); + } + return b1950; +} + +// Get Julian date of current time Epoch +double Astronomy::jd_now(void) +{ + time_t system_time; + struct tm *utc_time; + + // Get current time in seconds since Unix Epoch (1970) + time(&system_time); + // Convert to UTC (GMT) + utc_time = gmtime(&system_time); + + // Convert to Julian date + return julianDate(utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday, + utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec); +} + +// Precess a RA/DEC between two given Epochs +RADec Astronomy::precess(RADec rd_in, double jd_from, double jd_to) +{ + RADec rd_out; + double x, y, z; + double xp, yp, zp; + double ra_rad, dec_rad; + double rot[3][3]; // [row][col] + double ra_deg; + + double days_per_century = 36524.219878; + double t0 = (jd_from - jd_b1950())/days_per_century; // Tropical centuries since B1950.0 + double t = (jd_to - jd_from)/days_per_century; // Tropical centuries from starting epoch to ending epoch + + // From: https://www.cloudynights.com/topic/561254-ra-dec-epoch-conversion/ + rot[0][0] = 1.0 - ((29696.0 + 26.0*t0)*t*t - 13.0*t*t*t)*.00000001; + rot[1][0] = ((2234941.0 + 1355.0*t0)*t - 676.0*t*t + 221.0*t*t*t)*.00000001; + rot[2][0] = ((971690.0 - 414.0*t0)*t + 207.0*t*t + 96.0*t*t*t)*.00000001; + rot[0][1] = -rot[1][0]; + rot[1][1] = 1.0 - ((24975.0 + 30.0*t0)*t*t - 15.0*t*t*t)*.00000001; + rot[2][1] = -((10858.0 + 2.0*t0)*t*t)*.00000001; + rot[0][2] = -rot[2][0]; + rot[1][2] = rot[2][1]; + rot[2][2] = 1.0 - ((4721.0 - 4.0*t0)*t*t)*.00000001; + + // Hours to degrees + ra_deg = rd_in.ra*(360.0/24.0); + + // Convert to rectangular coordinates + ra_rad = Units::degreesToRadians(ra_deg); + dec_rad = Units::degreesToRadians(rd_in.dec); + x = cos(ra_rad) * cos(dec_rad); + y = sin(ra_rad) * cos(dec_rad); + z = sin(dec_rad); + + // Rotate + xp = rot[0][0]*x + rot[0][1]*y + rot[0][2]*z; + yp = rot[1][0]*x + rot[1][1]*y + rot[1][2]*z; + zp = rot[2][0]*x + rot[2][1]*y + rot[2][2]*z; + + // Convert back to spherical coordinates + rd_out.ra = Units::radiansToDegrees(atan(yp/xp)); + if (xp < 0.0) { + rd_out.ra += 180.0; + } else if ((yp < 0) && (xp > 0)) { + rd_out.ra += 360.0; + } + rd_out.dec = Units::radiansToDegrees(asin(zp)); + + // Degrees to hours + rd_out.ra /= (360.0/24.0); + + return rd_out; +} + +// Calculate local mean sidereal time (LMST) in degrees +double Astronomy::localSiderealTime(QDateTime dateTime, double longitude) +{ + double jd = julianDate(dateTime); + + double d = (jd - jd_j2000()); // Days since J2000 epoch (including fraction) + double f = fmod(jd, 1.0); // Fractional part is decimal days + double ut = (f+0.5)*24.0; // Universal time in decimal hours + + // https://astronomy.stackexchange.com/questions/24859/local-sidereal-time + // 100.46 is offset for GMST at 0h UT on 1 Jan 2000 + // 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day + // Approx to 0.3 arcseconds + return fmod(100.46 + 0.985647 * d + longitude + (360/24) * ut, 360.0); +} + +// Convert from J2000 right ascension (decimal hours) and declination (decimal degrees) to altitude and azimuth, for location (decimal degrees) and time +AzAlt Astronomy::raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000) +{ + AzAlt aa; + double ra_deg; // Right ascension in degrees + double lst_deg; // Local sidereal time in degrees + double ha; // Hour angle + double a, az; + double dec_rad, lat_rad, ha_rad, alt_rad; // Corresponding variables as radians + + double jd = julianDate(dt); + + // Precess RA/DEC from J2000 Epoch to desired (typically current) Epoch + if (j2000) + rd = precess(rd, jd_j2000(), jd); + + // Calculate local mean sidereal time (LMST) in degrees + // https://astronomy.stackexchange.com/questions/24859/local-sidereal-time + // 100.46 is offset for GMST at 0h UT on 1 Jan 2000 + // 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day + // Approx to 0.3 arcseconds + lst_deg = Astronomy::localSiderealTime(dt, longitude); + + // Convert right ascension from hours to degrees + ra_deg = rd.ra * (360.0/24.0); + + // Calculate hour angle + ha = fmod(lst_deg - ra_deg, 360.0); + + // Convert degrees to radians + dec_rad = Units::degreesToRadians(rd.dec); + lat_rad = Units::degreesToRadians(latitude); + ha_rad = Units::degreesToRadians(ha); + + // Calculate altitude and azimuth - no correction for atmospheric refraction + // From: http://www.convertalot.com/celestial_horizon_co-ordinates_calculator.html + alt_rad = asin(sin(dec_rad)*sin(lat_rad) + cos(dec_rad)*cos(lat_rad)*cos(ha_rad)); + a = Units::radiansToDegrees(acos((sin(dec_rad)-sin(alt_rad)*sin(lat_rad)) / (cos(alt_rad)*cos(lat_rad)))); + if (sin(ha_rad) < 0.0) { + az = a; + } else { + az = 360.0 - a; + } + + aa.alt = Units::radiansToDegrees(alt_rad); + aa.az = az; + return aa; +} + +// Needs to work for negative a +double Astronomy::modulo(double a, double b) +{ + return a - b * floor(a/b); +} + +// Calculate azimuth and altitude angles to the sun from the given latitude and longitude at the given time +// Refer to: +// https://en.wikipedia.org/wiki/Position_of_the_Sun +// https://www.aa.quae.nl/en/reken/zonpositie.html +// Said to be accurate to .01 degree (36") for dates between 1950 and 2050 +// although we use slightly more accurate constants and an extra term in the equation +// of centre from the second link +// For an alternative, see: http://www.psa.es/sdg/sunpos.htm +// Most accurate algorithm is supposedly: https://midcdmz.nrel.gov/spa/ +void Astronomy::sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt) +{ + double jd = julianDate(dt); + double n = (jd - jd_j2000()); // Days since J2000 epoch (including fraction) + + double l = 280.461 + 0.9856474 * n; // Mean longitude of the Sun, corrected for the abberation of light + double g = 357.5291 + 0.98560028 * n; // Mean anomaly of the Sun - how far around orbit from perihlion, in degrees + + l = modulo(l, 360.0); + g = modulo(g, 360.0); + + double gr = Units::degreesToRadians(g); + + double la = l + 1.9148 * sin(gr) + 0.0200 * sin(2.0*gr) + 0.0003 * sin(3.0*gr); // Ecliptic longitude (Ecliptic latitude b set to 0) + + // Convert la, b=0, which give the position of the Sun in the ecliptic coordinate sytem, to + // equatorial coordinates + + double e = 23.4393 - 3.563E-7 * n; // Obliquity of the ecliptic - tilt of Earth's axis of rotation + + double er = Units::degreesToRadians(e); + double lr = Units::degreesToRadians(la); + + double a = atan2(cos(er) * sin(lr), cos(lr)); // Right ascension, radians + double d = asin(sin(er) * sin(lr)); // Declination, radians + + rd.ra = modulo(Units::radiansToDegrees(a), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(d); // Convert to degrees + + aa = raDecToAzAlt(rd, latitude, longitude, dt, false); +} + +double Astronomy::moonDays(QDateTime dt) +{ + QDateTime utc = dt.toUTC(); + QDate date = utc.date(); + QTime time = utc.time(); + + int y = date.year(); + int m = date.month(); + int D = date.day(); + int d = 367 * y - 7 * (y + (m+9)/12) / 4 - 3 * ((y + (m-9)/7) / 100 + 1) / 4 + 275*m/9 + D - 730515; + + return d + time.hour()/24.0 + time.minute()/(24.0*60.0) + time.second()/(24.0*60.0*60.0); +} + +// Calculate azimuth and altitude angles to the moon from the given latitude and longitude at the given time +// Refer to: https://stjarnhimlen.se/comp/ppcomp.html +// Accurate to 4 arcminute +void Astronomy::moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt) +{ + double d = moonDays(dt); + + double ecl = Units::degreesToRadians(23.4393 - 3.563E-7 * d); // Obliquity of the ecliptic - tilt of Earth's axis of rotation + + // Orbital elements for the Sun + double ws = Units::degreesToRadians(282.9404 + 4.70935E-5 * d); + double Ms = Units::degreesToRadians(356.0470 + 0.9856002585 * d); + + // Orbital elements for the Moon + double Nm = Units::degreesToRadians(125.1228 - 0.0529538083 * d); // longitude of the ascending node + double im = Units::degreesToRadians(5.1454); // inclination to the ecliptic (plane of the Earth's orbit) + double wm = Units::degreesToRadians(318.0634 + 0.1643573223 * d); // argument of perihelion + double am = 60.2666; // (Earth radii) semi-major axis, or mean distance from Sun + double em = 0.054900; // ecm // eccentricity (0=circle, 0-1=ellipse, 1=parabola) + double Mm = Units::degreesToRadians(115.3654 + 13.0649929509 * d); // mean anomaly (0 at perihelion; increases uniformly with time), degrees + + double Em = Mm + em * sin(Mm) * (1.0 + em * cos(Mm)); // Eccentric anomaly in radians + + double xv = am * (cos(Em) - em); + double yv = am * (sqrt(1.0 - em*em) * sin(Em)); + + double vm = atan2(yv, xv); // True anomaly + double rm = sqrt(xv*xv + yv*yv); // Distance + + // Compute position in space (for the Moon, this is geocentric) + double xh = rm * (cos(Nm) * cos(vm+wm) - sin(Nm) * sin(vm+wm) * cos(im)); + double yh = rm * (sin(Nm) * cos(vm+wm) + cos(Nm) * sin(vm+wm) * cos(im)); + double zh = rm * (sin(vm+wm) * sin(im)); + + // Convert to ecliptic longitude and latitude + double lonecl = atan2(yh, xh); + double latecl = atan2(zh, sqrt(xh*xh+yh*yh)); + + // Perturbations of the Moon + + double Ls = Ms + ws; // Mean Longitude of the Sun (Ns=0) + double Lm = Mm + wm + Nm; // Mean longitude of the Moon + double D = Lm - Ls; // Mean elongation of the Moon + double F = Lm - Nm; // Argument of latitude for the Moon + + double dlon; + dlon = -1.274 * sin(Mm - 2*D); // (the Evection) + dlon += +0.658 * sin(2*D); // (the Variation) + dlon += -0.186 * sin(Ms); // (the Yearly Equation) + dlon += -0.059 * sin(2*Mm - 2*D); + dlon += -0.057 * sin(Mm - 2*D + Ms); + dlon += +0.053 * sin(Mm + 2*D); + dlon += +0.046 * sin(2*D - Ms); + dlon += +0.041 * sin(Mm - Ms); + dlon += -0.035 * sin(D); // (the Parallactic Equation) + dlon += -0.031 * sin(Mm + Ms); + dlon += -0.015 * sin(2*F - 2*D); + dlon += +0.011 * sin(Mm - 4*D); + + double dlat; + dlat = -0.173 * sin(F - 2*D); + dlat += -0.055 * sin(Mm - F - 2*D); + dlat += -0.046 * sin(Mm + F - 2*D); + dlat += +0.033 * sin(F + 2*D); + dlat += +0.017 * sin(2*Mm + F); + + lonecl += Units::degreesToRadians(dlon); + latecl += Units::degreesToRadians(dlat); + + rm += -0.58 * cos(Mm - 2*D); + rm += -0.46 * cos(2*D); + + // Convert perturbed + xh = rm * cos(lonecl) * cos(latecl); + yh = rm * sin(lonecl) * cos(latecl); + zh = rm * sin(latecl); + + // Convert to geocentric coordinates (already the case for the Moon) + double xg = xh; + double yg = yh; + double zg = zh; + + // Convert to equatorial cordinates + double xe = xg; + double ye = yg * cos(ecl) - zg * sin(ecl); + double ze = yg * sin(ecl) + zg * cos(ecl); + + // Compute right ascension and declination + double ra = atan2(ye, xe); + double dec = atan2(ze, sqrt(xe*xe+ye*ye)); + + rd.ra = modulo(Units::radiansToDegrees(ra), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(dec); // Convert to degrees + + // Convert from geocentric to topocentric + double mpar = asin(1/rm); + + double lat = Units::degreesToRadians(latitude); + double gclat = Units::degreesToRadians(latitude - 0.1924 * sin(2.0 * lat)); + double rho = 0.99833 + 0.00167 * cos(2*lat); + + QTime time = dt.toUTC().time(); + double UT = time.hour() + time.minute()/60.0 + time.second()/(60.0*60.0); + + double GMST0 = Units::radiansToDegrees(Ls)/15.0 + 12; // In hours + double GMST = GMST0 + UT; + double LST = GMST + longitude/15.0; + + double HA = Units::degreesToRadians(LST*15.0 - Units::radiansToDegrees(ra)); // Hour angle in radians + double g = atan(tan(gclat) / cos(HA)); + + double topRA = ra - mpar * rho * cos(gclat) * sin(HA) / cos(dec); + double topDec; + if (g != 0.0) + topDec = dec - mpar * rho * sin(gclat) * sin(g - dec) / sin(g); + else + topDec = dec - mpar * rho * sin(-dec) * cos(HA); + + rd.ra = modulo(Units::radiansToDegrees(topRA), 360.0) / (360.0/24.0); // Convert to hours + rd.dec = Units::radiansToDegrees(topDec); // Convert to degrees + aa = raDecToAzAlt(rd, latitude, longitude, dt, false); +} + +// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using +// Saemundsson's formula (which is used by Stellarium and is primarily just for +// optical wavelengths) +// See: https://en.wikipedia.org/wiki/Atmospheric_refraction#Calculating_refraction +// Alt is in degrees. 90 = Zenith gives a factor of 0. +// Pressure in millibars +// Temperature in Celsuis +// We divide by 60.0 to get a value in degrees (as original formula is in arcminutes) +double Astronomy::refractionSaemundsson(double alt, double pressure, double temperature) +{ + double pt = (pressure/1010.0) * (283.0/(273.0+temperature)); + + return pt * (1.02 / tan(Units::degreesToRadians(alt+10.3/(alt+5.11))) + 0.0019279) / 60.0; +} + +// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using +// code from Starlink Positional Astronomy Library. This is more accurate for +// radio than Saemundsson's formula, but also more complex. +// See: https://github.com/Starlink/pal +// Alt is in degrees. 90 = Zenith gives a factor of 0. +// Pressure in millibars +// Temperature in Celsuis +// Humdity in % +// Frequency in Hertz +// Latitude in decimal degrees +// HeightAboveSeaLevel in metres +// Temperature lapse rate in K/km +double Astronomy::refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency, + double latitude, double heightAboveSeaLevel, double temperatureLapseRate) +{ + double tdk = Units::celsiusToKelvin(temperature); // Ambient temperature at the observer (K) + double wl = (299792458.0/frequency)*1000000.0; // Wavelength in micrometres + double rh = humidity/100.0; // Relative humidity in range 0-1 + double phi = Units::degreesToRadians(latitude); // Latitude of the observer (radian, astronomical) + double tlr = temperatureLapseRate/1000.0; // Temperature lapse rate in the troposphere (K/metre) + double refa, refb; + double z = 90.0-alt; + double zu = Units::degreesToRadians(z); + double zr; + + palRefco(heightAboveSeaLevel, tdk, pressure, rh, + wl, phi, tlr, 1E-10, + &refa, &refb); + palRefz(zu, refa, refb, &zr); + + return z-Units::radiansToDegrees(zr); +} + +double Astronomy::raToDecimal(const QString& value) +{ + QRegExp decimal("^([0-9]+(\\.[0-9]+)?)"); + QRegExp hms("^([0-9]+)[ h]([0-9]+)[ m]([0-9]+(\\.[0-9]+)?)s?"); + + if (decimal.exactMatch(value)) + return decimal.capturedTexts()[0].toDouble(); + else if (hms.exactMatch(value)) + { + return Units::hoursMinutesSecondsToDecimal( + hms.capturedTexts()[1].toDouble(), + hms.capturedTexts()[2].toDouble(), + hms.capturedTexts()[3].toDouble()); + } + return 0.0; +} + +double Astronomy::decToDecimal(const QString& value) +{ + QRegExp decimal("^(-?[0-9]+(\\.[0-9]+)?)"); + QRegExp dms(QString("^(-?[0-9]+)[ %1d]([0-9]+)[ 'm]([0-9]+(\\.[0-9]+)?)[\"s]?").arg(QChar(0xb0))); + + if (decimal.exactMatch(value)) + return decimal.capturedTexts()[0].toDouble(); + else if (dms.exactMatch(value)) + { + return Units::degreesMinutesSecondsToDecimal( + dms.capturedTexts()[1].toDouble(), + dms.capturedTexts()[2].toDouble(), + dms.capturedTexts()[3].toDouble()); + } + return 0.0; +} + +double Astronomy::lstAndRAToLongitude(double lst, double raHours) +{ + double longitude = lst - (raHours * 15.0); // Convert hours to degrees + if (longitude < -180.0) + longitude += 360.0; + else if (longitude > 180.0) + longitude -= 360.0; + return -longitude; // East positive +} + +// The following functions are from Starlink Positional Astronomy Library +// https://github.com/Starlink/pal + +/* Pi */ +static const double PAL__DPI = 3.1415926535897932384626433832795028841971693993751; + +/* 2Pi */ +static const double PAL__D2PI = 6.2831853071795864769252867665590057683943387987502; + +/* pi/180: degrees to radians */ +static const double PAL__DD2R = 0.017453292519943295769236907684886127134428718885417; + +/* Radians to degrees */ +static const double PAL__DR2D = 57.295779513082320876798154814105170332405472466564; + +/* DMAX(A,B) - return maximum value - evaluates arguments multiple times */ +#define DMAX(A,B) ((A) > (B) ? (A) : (B) ) + +/* DMIN(A,B) - return minimum value - evaluates arguments multiple times */ +#define DMIN(A,B) ((A) < (B) ? (A) : (B) ) + +// Normalize angle into range +/- pi +double palDrange( double angle ) { + double result = fmod( angle, PAL__D2PI ); + if( result > PAL__DPI ) { + result -= PAL__D2PI; + } else if( result < -PAL__DPI ) { + result += PAL__D2PI; + } + return result; +} + +// Calculate stratosphere parameters +static void pal1Atms ( double rt, double tt, double dnt, double gamal, + double r, double * dn, double * rdndr ) { + + double b; + double w; + + b = gamal / tt; + w = (dnt - 1.0) * exp( -b * (r-rt) ); + *dn = 1.0 + w; + *rdndr = -r * b * w; + +} + +// Calculate troposphere parameters +static void pal1Atmt ( double r0, double t0, double alpha, double gamm2, + double delm2, double c1, double c2, double c3, double c4, + double c5, double c6, double r, + double *t, double *dn, double *rdndr ) { + + double tt0; + double tt0gm2; + double tt0dm2; + + *t = DMAX( DMIN( t0 - alpha*(r-r0), 320.0), 100.0 ); + tt0 = *t / t0; + tt0gm2 = pow( tt0, gamm2 ); + tt0dm2 = pow( tt0, delm2 ); + *dn = 1.0 + ( c1 * tt0gm2 - ( c2 - c5 / *t ) * tt0dm2 ) * tt0; + *rdndr = r * ( -c3 * tt0gm2 + ( c4 - c6 / tt0 ) * tt0dm2 ); + +} + +// Adjust unrefracted zenith distance +static void palRefz ( double zu, double refa, double refb, double *zr ) { + + /* Constants */ + + /* Largest usable ZD (deg) */ + const double D93 = 93.0; + + /* ZD at which one model hands over to the other (radians) */ + const double Z83 = 83.0 * PAL__DD2R; + + /* coefficients for high ZD model (used beyond ZD 83 deg) */ + const double C1 = +0.55445; + const double C2 = -0.01133; + const double C3 = +0.00202; + const double C4 = +0.28385; + const double C5 = +0.02390; + + /* High-ZD-model prefiction (deg) for that point */ + const double REF83 = (C1+C2*7.0+C3*49.0)/(1.0+C4*7.0+C5*49.0); + + double zu1,zl,s,c,t,tsq,tcu,ref,e,e2; + + /* perform calculations for zu or 83 deg, whichever is smaller */ + zu1 = DMIN(zu,Z83); + + /* functions of ZD */ + zl = zu1; + s = sin(zl); + c = cos(zl); + t = s/c; + tsq = t*t; + tcu = t*tsq; + + /* refracted zd (mathematically to better than 1 mas at 70 deg) */ + zl = zl-(refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c)); + + /* further iteration */ + s = sin(zl); + c = cos(zl); + t = s/c; + tsq = t*t; + tcu = t*tsq; + ref = zu1-zl+ + (zl-zu1+refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c)); + + /* special handling for large zu */ + if (zu > zu1) { + e = 90.0-DMIN(D93,zu*PAL__DR2D); + e2 = e*e; + ref = (ref/REF83)*(C1+C2*e+C3*e2)/(1.0+C4*e+C5*e2); + } + + /* return refracted zd */ + *zr = zu-ref; + +} + +// Determine constants in atmospheric refraction model +static void palRefco ( double hm, double tdk, double pmb, double rh, + double wl, double phi, double tlr, double eps, + double *refa, double *refb ) { + + double r1, r2; + + /* Sample zenith distances: arctan(1) and arctan(4) */ + const double ATN1 = 0.7853981633974483; + const double ATN4 = 1.325817663668033; + + /* Determine refraction for the two sample zenith distances */ + palRefro(ATN1,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r1); + palRefro(ATN4,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r2); + + /* Solve for refraction constants */ + *refa = (64.0*r1-r2)/60.0; + *refb = (r2-4.0*r1)/60.0; + +} + +// Atmospheric refraction for radio and optical/IR wavelengths +static void palRefro( double zobs, double hm, double tdk, double pmb, + double rh, double wl, double phi, double tlr, + double eps, double * ref ) { + + /* + * Fixed parameters + */ + + /* 93 degrees in radians */ + const double D93 = 1.623156204; + /* Universal gas constant */ + const double GCR = 8314.32; + /* Molecular weight of dry air */ + const double DMD = 28.9644; + /* Molecular weight of water vapour */ + const double DMW = 18.0152; + /* Mean Earth radius (metre) */ + const double S = 6378120.; + /* Exponent of temperature dependence of water vapour pressure */ + const double DELTA = 18.36; + /* Height of tropopause (metre) */ + const double HT = 11000.; + /* Upper limit for refractive effects (metre) */ + const double HS = 80000.; + /* Numerical integration: maximum number of strips. */ + const int ISMAX=16384l; + + /* Local variables */ + int is, k, n, i, j; + + int optic, loop; /* booleans */ + + double zobs1,zobs2,hmok,tdkok,pmbok,rhok,wlok,alpha, + tol,wlsq,gb,a,gamal,gamma,gamm2,delm2, + tdc,psat,pwo,w, + c1,c2,c3,c4,c5,c6,r0,tempo,dn0,rdndr0,sk0,f0, + rt,tt,dnt,rdndrt,sine,zt,ft,dnts,rdndrp,zts,fts, + rs,dns,rdndrs,zs,fs,refold,z0,zrange,fb,ff,fo,fe, + h,r,sz,rg,dr,tg,dn,rdndr,t,f,refp,reft; + + /* The refraction integrand */ +#define refi(DN,RDNDR) RDNDR/(DN+RDNDR) + + /* Transform ZOBS into the normal range. */ + zobs1 = palDrange(zobs); + zobs2 = DMIN(fabs(zobs1),D93); + + /* keep other arguments within safe bounds. */ + hmok = DMIN(DMAX(hm,-1e3),HS); + tdkok = DMIN(DMAX(tdk,100.0),500.0); + pmbok = DMIN(DMAX(pmb,0.0),10000.0); + rhok = DMIN(DMAX(rh,0.0),1.0); + wlok = DMAX(wl,0.1); + alpha = DMIN(DMAX(fabs(tlr),0.001),0.01); + + /* tolerance for iteration. */ + tol = DMIN(DMAX(fabs(eps),1e-12),0.1)/2.0; + + /* decide whether optical/ir or radio case - switch at 100 microns. */ + optic = wlok < 100.0; + + /* set up model atmosphere parameters defined at the observer. */ + wlsq = wlok*wlok; + gb = 9.784*(1.0-0.0026*cos(phi+phi)-0.00000028*hmok); + if (optic) { + a = (287.6155+(1.62887+0.01360/wlsq)/wlsq) * 273.15e-6/1013.25; + } else { + a = 77.6890e-6; + } + gamal = (gb*DMD)/GCR; + gamma = gamal/alpha; + gamm2 = gamma-2.0; + delm2 = DELTA-2.0; + tdc = tdkok-273.15; + psat = pow(10.0,(0.7859+0.03477*tdc)/(1.0+0.00412*tdc)) * + (1.0+pmbok*(4.5e-6+6.0e-10*tdc*tdc)); + if (pmbok > 0.0) { + pwo = rhok*psat/(1.0-(1.0-rhok)*psat/pmbok); + } else { + pwo = 0.0; + } + w = pwo*(1.0-DMW/DMD)*gamma/(DELTA-gamma); + c1 = a*(pmbok+w)/tdkok; + if (optic) { + c2 = (a*w+11.2684e-6*pwo)/tdkok; + } else { + c2 = (a*w+6.3938e-6*pwo)/tdkok; + } + c3 = (gamma-1.0)*alpha*c1/tdkok; + c4 = (DELTA-1.0)*alpha*c2/tdkok; + if (optic) { + c5 = 0.0; + c6 = 0.0; + } else { + c5 = 375463e-6*pwo/tdkok; + c6 = c5*delm2*alpha/(tdkok*tdkok); + } + + /* conditions at the observer. */ + r0 = S+hmok; + pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6, + r0,&tempo,&dn0,&rdndr0); + sk0 = dn0*r0*sin(zobs2); + f0 = refi(dn0,rdndr0); + + /* conditions in the troposphere at the tropopause. */ + rt = S+DMAX(HT,hmok); + pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6, + rt,&tt,&dnt,&rdndrt); + sine = sk0/(rt*dnt); + zt = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + ft = refi(dnt,rdndrt); + + /* conditions in the stratosphere at the tropopause. */ + pal1Atms(rt,tt,dnt,gamal,rt,&dnts,&rdndrp); + sine = sk0/(rt*dnts); + zts = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + fts = refi(dnts,rdndrp); + + /* conditions at the stratosphere limit. */ + rs = S+HS; + pal1Atms(rt,tt,dnt,gamal,rs,&dns,&rdndrs); + sine = sk0/(rs*dns); + zs = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0))); + fs = refi(dns,rdndrs); + + /* variable initialization to avoid compiler warning. */ + reft = 0.0; + + /* integrate the refraction integral in two parts; first in the + * troposphere (k=1), then in the stratosphere (k=2). */ + + for (k=1; k<=2; k++) { + + /* initialize previous refraction to ensure at least two iterations. */ + refold = 1.0; + + /* start off with 8 strips. */ + is = 8; + + /* start z, z range, and start and end values. */ + if (k==1) { + z0 = zobs2; + zrange = zt-z0; + fb = f0; + ff = ft; + } else { + z0 = zts; + zrange = zs-z0; + fb = fts; + ff = fs; + } + + /* sums of odd and even values. */ + fo = 0.0; + fe = 0.0; + + /* first time through the loop we have to do every point. */ + n = 1; + + /* start of iteration loop (terminates at specified precision). */ + loop = 1; + while (loop) { + + /* strip width. */ + h = zrange/((double)is); + + /* initialize distance from earth centre for quadrature pass. */ + if (k == 1) { + r = r0; + } else { + r = rt; + } + + /* one pass (no need to compute evens after first time). */ + for (i=1; i 1e-20) { + w = sk0/sz; + rg = r; + dr = 1.0e6; + j = 0; + while ( fabs(dr) > 1.0 && j < 4 ) { + j++; + if (k==1) { + pal1Atmt(r0,tdkok,alpha,gamm2,delm2, + c1,c2,c3,c4,c5,c6,rg,&tg,&dn,&rdndr); + } else { + pal1Atms(rt,tt,dnt,gamal,rg,&dn,&rdndr); + } + dr = (rg*dn-w)/(dn+rdndr); + rg = rg-dr; + } + r = rg; + } + + /* find the refractive index and integrand at r. */ + if (k==1) { + pal1Atmt(r0,tdkok,alpha,gamm2,delm2, + c1,c2,c3,c4,c5,c6,r,&t,&dn,&rdndr); + } else { + pal1Atms(rt,tt,dnt,gamal,r,&dn,&rdndr); + } + f = refi(dn,rdndr); + + /* accumulate odd and (first time only) even values. */ + if (n==1 && i%2 == 0) { + fe += f; + } else { + fo += f; + } + } + + /* evaluate the integrand using simpson's rule. */ + refp = h*(fb+4.0*fo+2.0*fe+ff)/3.0; + + /* has the required precision been achieved (or can't be)? */ + if (fabs(refp-refold) > tol && is < ISMAX) { + + /* no: prepare for next iteration.*/ + + /* save current value for convergence test. */ + refold = refp; + + /* double the number of strips. */ + is += is; + + /* sum of all current values = sum of next pass's even values. */ + fe += fo; + + /* prepare for new odd values. */ + fo = 0.0; + + /* skip even values next time. */ + n = 2; + } else { + + /* yes: save troposphere component and terminate the loop. */ + if (k==1) reft = refp; + loop = 0; + } + } + } + + /* result. */ + *ref = reft+refp; + if (zobs1 < 0.0) *ref = -(*ref); + +} diff --git a/sdrbase/util/astronomy.h b/sdrbase/util/astronomy.h new file mode 100644 index 000000000..3784c8196 --- /dev/null +++ b/sdrbase/util/astronomy.h @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_ASTRONOMY_H +#define INCLUDE_ASTRONOMY_H + +#include "export.h" + +class QDateTime; + +// Right ascension and declination +struct SDRBASE_API RADec { + double ra; + double dec; +}; + +// Azimuth and Altitude +struct SDRBASE_API AzAlt { + double az; + double alt; +}; + +class SDRBASE_API Astronomy { + +public: + static double julianDate(int year, int month, int day, int hours, int minutes, int seconds); + static double julianDate(QDateTime dt); + + static double jd_j2000(void); + static double jd_b1950(void); + static double jd_now(void); + + static RADec precess(RADec rd_in, double jd_from, double jd_to); + static AzAlt raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000=true); + + static double localSiderealTime(QDateTime dateTime, double longitude); + + static void sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt); + static double moonDays(QDateTime dt); + static void moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt); + + static double refractionSaemundsson(double alt, double pressure, double temperature); + static double refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency, double latitude, double heightAboveSeaLevel, double temperatureLapseRate); + + static double raToDecimal(const QString& value); + static double decToDecimal(const QString& value); + + static double lstAndRAToLongitude(double lst, double raHours); + +protected: + static double modulo(double a, double b); +}; + +#endif // INCLUDE_ASTRONOMY_H diff --git a/sdrbase/util/ax25.cpp b/sdrbase/util/ax25.cpp new file mode 100644 index 000000000..9fdfbccb6 --- /dev/null +++ b/sdrbase/util/ax25.cpp @@ -0,0 +1,213 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "ax25.h" + +// CRC is assumed to be correct (checked in packetdemodsink) +bool AX25Packet::decode(QByteArray packet) +{ + int i, j; + char destAddress[7]; + unsigned char destSSID; + char sourceAddress[7]; + unsigned char sourceSSID; + char repeaterAddress[7]; + unsigned char repeaterSSID; + unsigned char ssid; + unsigned char control; + + // Check for minimum size packet. Addresses, control and CRC + if (packet.size() < 7+7+1+2) + return false; + + // Address - ASCII shifted right one bit + for (i = 0; i < 6; i++) + destAddress[i] = (packet[i] >> 1) & 0x7f; + destAddress[6] = '\0'; + destSSID = packet[6]; + for (i = 0; i < 6; i++) + sourceAddress[i] = (packet[7+i] >> 1) & 0x7f; + sourceAddress[6] = '\0'; + sourceSSID = packet[13]; + + // From = source address + m_from = QString(sourceAddress).trimmed(); + ssid = (sourceSSID >> 1) & 0xf; + if (ssid != 0) + m_from = QString("%1-%2").arg(m_from).arg(ssid); + + // To = destination address + m_to = QString(destAddress).trimmed(); + ssid = (destSSID >> 1) & 0xf; + if (ssid != 0) + m_to = QString("%1-%2").arg(m_to).arg(ssid); + + // List of repeater addresses for via field + m_via = QString(""); + i = 13; + while ((packet[i] & 1) == 0) + { + i++; + for (j = 0; j < 6; j++) + repeaterAddress[j] = (packet[i+j] >> 1) & 0x7f; + repeaterAddress[j] = '\0'; + i += 6; + repeaterSSID = packet[i]; + ssid = (repeaterSSID >> 1) & 0xf; + QString repeater = QString(repeaterAddress).trimmed(); + QString ssidString = (ssid != 0) ? QString("%2-%3").arg(repeater).arg(ssid) : QString(repeater); + if (m_via == "") + m_via = ssidString; + else + m_via = QString("%1,%2").arg(m_via).arg(ssidString); + } + i++; + // Control can be 1 or 2 bytes - how to know if 2? + // I, U and S frames + control = packet[i++]; + if ((control & 1) == 0) + m_type = QString("I"); + else if ((control & 3) == 3) + { + // See figure 4.4 of AX.25 spec + switch (control & 0xef) + { + case 0x6f: + m_type = QString("SABME"); + break; + case 0x2f: + m_type = QString("SABM"); + break; + case 0x43: + m_type = QString("DISC"); + break; + case 0x0f: + m_type = QString("DM"); + break; + case 0x63: + m_type = QString("UA"); + break; + case 0x87: + m_type = QString("FR"); + break; + case 0x03: + m_type = QString("UI"); + break; + case 0xaf: + m_type = QString("XID"); + break; + case 0xe3: + m_type = QString("TEST"); + break; + default: + m_type = QString("U"); + break; + } + } + else + m_type = QString("S"); + // APRS packets use UI frames, which are a subype of U frames + // Only I and UI frames have Layer 3 Protocol ID (PID). + if ((m_type == "I") || (m_type == "UI")) + m_pid = QString("%1").arg(((unsigned)packet[i++]) & 0xff, 2, 16, QLatin1Char('0')); + else + m_pid = QString(""); + int infoStart, infoEnd; + infoStart = i; + infoEnd = packet.size()-2-i; + QByteArray info(packet.mid(infoStart, infoEnd)); + m_dataASCII = QString::fromLatin1(info); + m_dataHex = QString(info.toHex()); + + return true; +} + +bool AX25Packet::ssid(QByteArray& b, int i, int len, uint8_t& ssid) +{ + if (b[i] == '-') + { + if (len > i + 1) + { + ssid = b[i+1] - '0'; + if ((len > i + 2) && isdigit(b[i+2])) { + ssid = (ssid*10) + (b[i+2] - '0'); + } + if (ssid >= 16) + { + // FIXME: APRS-IS seems to support 2 letter SSIDs + // These can't be sent over RF, as not enough bits in AX.25 packet + qDebug() << "AX25Packet::ssid: SSID greater than 15 not supported"; + ssid = 0; + return false; + } + else + { + return true; + } + } + else + { + qDebug() << "AX25Packet::ssid: SSID number missing"; + return false; + } + } + else + return false; +} + +QByteArray AX25Packet::encodeAddress(QString address, uint8_t crrl) +{ + int len; + int i; + QByteArray encodedAddress; + QByteArray b; + uint8_t ssid = 0; + bool hyphenSeen = false; + + len = address.length(); + b = address.toUtf8(); + ssid = 0; + for (i = 0; i < 6; i++) + { + if ((i < len) && !hyphenSeen) + { + if (b[i] == '-') + { + AX25Packet::ssid(b, i, len, ssid); + hyphenSeen = true; + encodedAddress.append(' ' << 1); + } + else + { + encodedAddress.append(b[i] << 1); + } + } + else + { + encodedAddress.append(' ' << 1); + } + } + if (b[i] == '-') + { + AX25Packet::ssid(b, i, len, ssid); + } + encodedAddress.append(crrl | (ssid << 1)); + + return encodedAddress; +} diff --git a/sdrbase/util/ax25.h b/sdrbase/util/ax25.h new file mode 100644 index 000000000..44a45ffc3 --- /dev/null +++ b/sdrbase/util/ax25.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_AX25_H +#define INCLUDE_AX25_H + +#include +#include + +#include "export.h" + +struct SDRBASE_API AX25Packet { + QString m_from; + QString m_to; + QString m_via; + QString m_type; + QString m_pid; + QString m_dataASCII; + QString m_dataHex; + + bool decode(QByteArray packet); + + static bool ssid(QByteArray& b, int i, int len, uint8_t& ssid); + static QByteArray encodeAddress(QString address, uint8_t crrl=0); + +}; + +#endif // INCLUDE_AX25_H diff --git a/sdrbase/util/maidenhead.cpp b/sdrbase/util/maidenhead.cpp new file mode 100644 index 000000000..7cdda6957 --- /dev/null +++ b/sdrbase/util/maidenhead.cpp @@ -0,0 +1,83 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Maidenhead locator utilities // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "maidenhead.h" + +// See: https://en.wikipedia.org/wiki/Maidenhead_Locator_System + +QString Maidenhead::toMaidenhead(float latitude, float longitude) +{ + longitude += 180.0; + latitude += 90.0; + int lon1 = floor(longitude/20); + longitude -= lon1*20; + int lat1 = floor(latitude/10); + latitude -= lat1*10; + int lon2 = floor(longitude/2); + longitude -= lon2*2; + int lat2 = floor(latitude/1); + latitude -= lat2; + int lon3 = round(longitude*12); + int lat3 = round(latitude*24); + return QString("%1%2%3%4%5%6").arg(QChar(lon1+'A')).arg(QChar(lat1+'A')) + .arg(QChar(lon2+'0')).arg(QChar(lat2+'0')) + .arg(QChar(lon3+'A')).arg(QChar(lat3+'A')); +} + +bool Maidenhead::fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude) +{ + if (Maidenhead::isMaidenhead(maidenhead)) + { + int lon1 = maidenhead[0].toUpper().toLatin1() - 'A'; + int lat1 = maidenhead[1].toUpper().toLatin1() - 'A'; + int lon2 = maidenhead[2].toLatin1() - '0'; + int lat2 = maidenhead[3].toLatin1() - '0'; + int lon3 = maidenhead[4].toUpper().toLatin1() - 'A'; + int lat3 = maidenhead[5].toUpper().toLatin1() - 'A'; + int lon4 = 0; + int lat4 = 0; + if (maidenhead.length() == 8) + { + lon4 = maidenhead[6].toLatin1() - '0'; + lat4 = maidenhead[7].toLatin1() - '0'; + } + longitude = lon1 * 20 + lon2 * 2 + lon3 * 2.0/24.0 + lon4 * 2.0/24.0/10; // 20=360/18 + latitude = lat1 * 10 + lat2 * 1 + lat3 * 1.0/24.0 + lat4 * 1.0/24.0/10; // 10=180/18 + + longitude -= 180.0; + latitude -= 90.0; + + return true; + } + else + return false; +} + +bool Maidenhead::isMaidenhead(const QString& maidenhead) +{ + int length = maidenhead.length(); + if ((length != 6) && (length != 8)) + return false; + QRegExp re("[A-Ra-r][A-Ra-r][0-9][0-9][A-Xa-x][A-Xa-x]([0-9][0-9])?"); + return re.exactMatch(maidenhead); +} diff --git a/sdrbase/util/maidenhead.h b/sdrbase/util/maidenhead.h new file mode 100644 index 000000000..0147e0f7b --- /dev/null +++ b/sdrbase/util/maidenhead.h @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// Maidenhead locator utilities // +// // +// 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_MAIDENHEAD_H +#define INCLUDE_MAIDENHEAD_H + +#include + +#include "export.h" + +class SDRBASE_API Maidenhead { + +public: + + static QString toMaidenhead(float latitude, float longitude); + static bool fromMaidenhead(const QString& maidenhead, float& latitude, float& longitude); + static bool isMaidenhead(const QString& maidenhead); + +}; + +#endif // INCLUDE_MAIDENHEAD_H diff --git a/sdrbase/util/units.h b/sdrbase/util/units.h index 4285f26e0..8faecc1fa 100644 --- a/sdrbase/util/units.h +++ b/sdrbase/util/units.h @@ -19,6 +19,10 @@ #define INCLUDE_UNITS_H #include +#include +#include +#include +#include #include "export.h" @@ -72,11 +76,143 @@ public: return degrees * ((float)M_PI) / 180.0f; } - static float radiansToDegress(float radians) + static float radiansToDegrees(float radians) { return radians * 180.0f / ((float)M_PI); } + static float fahrenheitToCelsius(float fahrenheit) + { + return (fahrenheit - 32.0f) * (5.0f/9.0f); + } + + static float celsiusToKelvin(float celsius) + { + return celsius + 273.15f; + } + + static float inchesToMilimetres(float inches) + { + return inches * 25.4f; + } + + static float milesToKilometres(float miles) + { + return miles * 1.60934f; + } + + static float degreesMinutesSecondsToDecimal(int degrees, int minutes, float seconds) + { + float dec = std::abs(degrees) + minutes * 1.0f/60.0f + seconds * 1.0f/(60.0f*60.0f); + if (degrees < 0) + return -dec; + else + return dec; + } + + static float hoursMinutesSecondsToDecimal(int hours, int minutes, float seconds) + { + return hours + minutes * 1.0f/60.0f + seconds * 1.0f/(60.0f*60.0f); + } + + static QString decimalDegreesToDegreeMinutesAndSeconds(float decimal, int secondsFieldWidth=5) + { + float v, d, m, s; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = floor(v); + v -= d; + v *= 60.0; + m = floor(v); + v -= m; + v *= 60.0; + s = v; + return QString("%1%2%3%4'%5\"").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)).arg((int)m, 2, 10, QChar('0')).arg(s, secondsFieldWidth, 'f', 2, QChar('0')); + } + + static QString decimalDegreesToDegreesAndMinutes(float decimal) + { + float v, d, m; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = floor(v); + v -= d; + v *= 60.0; + m = round(v); + return QString("%1%2%3%4'").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)).arg((int)m, 2, 10, QChar('0')); + } + + static QString decimalDegreesToDegrees(float decimal) + { + float v, d; + int neg; + + v = decimal; + neg = v < 0.0f; + v = fabs(v); + d = round(v); + return QString("%1%2%3").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)); + } + + static QString decimalHoursToHoursMinutesAndSeconds(float decimal, int precision=2) + { + float v, h, m, s; + + v = decimal; + v = fabs(v); + h = floor(v); + v -= h; + v *= 60.0; + m = floor(v); + v -= m; + v *= 60.0; + s = v; + return QString("%1h%2m%3s").arg((int)h).arg((int)m, 2, 10, QChar('0')).arg(s, 2, 'f', precision, QChar('0')); + } + + // Try to convert a string to latitude and longitude. Returns false if not recognised format. + // https://en.wikipedia.org/wiki/ISO_6709 specifies a standard syntax + // We support both decimal and DMS formats + static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude) + { + QRegExp decimal("(-?[0-9]+\\.[0-9]+) *,? *(-?[0-9]+\\.[0-9]+)"); + if (decimal.exactMatch(string)) + { + latitude = decimal.capturedTexts()[1].toFloat(); + longitude = decimal.capturedTexts()[2].toFloat(); + return true; + } + QRegExp dms(QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0))); + if (dms.exactMatch(string)) + { + qDebug() << "Captured: " << dms.capturedTexts(); + float latD = dms.capturedTexts()[1].toFloat(); + float latM = dms.capturedTexts()[2].toFloat(); + float latS = dms.capturedTexts()[3].toFloat(); + bool north = dms.capturedTexts()[5] == "N"; + float lonD = dms.capturedTexts()[6].toFloat(); + float lonM = dms.capturedTexts()[7].toFloat(); + float lonS = dms.capturedTexts()[8].toFloat(); + bool east = dms.capturedTexts()[10] == "E"; + latitude = latD + latM/60.0 + latS/(60.0*60.0); + if (!north) + latitude = -latitude; + longitude = lonD + lonM/60.0 + lonS/(60.0*60.0); + if (!east) + longitude = -longitude; + qDebug() << "Lat " << latitude; + qDebug() << "Long " << longitude; + return true; + } + return false; + } + }; #endif // INCLUDE_UNITS_H diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index c174184a4..3bc369076 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -3916,6 +3916,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setLocalSourceSettings(new SWGSDRangel::SWGLocalSourceSettings()); channelSettings->getLocalSourceSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "PacketDemodSettings") + { + channelSettings->setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings()); + channelSettings->getPacketDemodSettings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "PacketModSettings") { channelSettings->setPacketModSettings(new SWGSDRangel::SWGPacketModSettings()); @@ -4370,16 +4375,31 @@ bool WebAPIRequestMapper::getFeatureSettings( QJsonObject settingsJsonObject = featureSettingsJson[featureSettingsKey].toObject(); featureSettingsKeys = settingsJsonObject.keys(); - if (featureSettingsKey == "GS232ControllerSettings") + if (featureSettingsKey == "APRSSettings") + { + featureSettings->setAprsSettings(new SWGSDRangel::SWGAPRSSettings()); + featureSettings->getAprsSettings()->fromJsonObject(settingsJsonObject); + } + else if (featureSettingsKey == "GS232ControllerSettings") { featureSettings->setGs232ControllerSettings(new SWGSDRangel::SWGGS232ControllerSettings()); featureSettings->getGs232ControllerSettings()->fromJsonObject(settingsJsonObject); } + else if (featureSettingsKey == "MapSettings") + { + featureSettings->setMapSettings(new SWGSDRangel::SWGMapSettings()); + featureSettings->getMapSettings()->fromJsonObject(settingsJsonObject); + } else if (featureSettingsKey == "SimplePTTSettings") { featureSettings->setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings()); featureSettings->getSimplePttSettings()->fromJsonObject(settingsJsonObject); } + else if (featureSettingsKey == "StarTrackerSettings") + { + featureSettings->setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings()); + featureSettings->getStarTrackerSettings()->fromJsonObject(settingsJsonObject); + } else if (featureSettingsKey == "RigCtlServerSettings") { featureSettings->setRigCtlServerSettings(new SWGSDRangel::SWGRigCtlServerSettings()); @@ -4412,7 +4432,12 @@ bool WebAPIRequestMapper::getFeatureActions( QJsonObject actionsJsonObject = featureActionsJson[featureActionsKey].toObject(); featureActionsKeys = actionsJsonObject.keys(); - if (featureActionsKey == "SimplePTTActions") + if (featureActionsKey == "MapActions") + { + featureActions->setMapActions(new SWGSDRangel::SWGMapActions()); + featureActions->getMapActions()->fromJsonObject(actionsJsonObject); + } + else if (featureActionsKey == "SimplePTTActions") { featureActions->setSimplePttActions(new SWGSDRangel::SWGSimplePTTActions()); featureActions->getSimplePttActions()->fromJsonObject(actionsJsonObject); @@ -4540,6 +4565,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setIeee802154ModSettings(nullptr); channelSettings.setNfmDemodSettings(nullptr); channelSettings.setNfmModSettings(nullptr); + channelSettings.setPacketDemodSettings(nullptr); channelSettings.setPacketModSettings(nullptr); channelSettings.setRemoteSinkSettings(nullptr); channelSettings.setRemoteSourceSettings(nullptr); @@ -4602,7 +4628,12 @@ void WebAPIRequestMapper::resetFeatureSettings(SWGSDRangel::SWGFeatureSettings& { featureSettings.cleanup(); featureSettings.setFeatureType(nullptr); + featureSettings.setAprsSettings(nullptr); + featureSettings.setGs232ControllerSettings(nullptr); + featureSettings.setMapSettings(nullptr); featureSettings.setSimplePttSettings(nullptr); + featureSettings.setStarTrackerSettings(nullptr); + featureSettings.setRigCtlServerSettings(nullptr); } void WebAPIRequestMapper::resetFeatureReport(SWGSDRangel::SWGFeatureReport& featureReport) @@ -4616,6 +4647,7 @@ void WebAPIRequestMapper::resetFeatureActions(SWGSDRangel::SWGFeatureActions& fe { featureActions.cleanup(); featureActions.setFeatureType(nullptr); + featureActions.setMapActions(nullptr); featureActions.setSimplePttActions(nullptr); } diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index a2bd59bf8..09ce7ba96 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -47,6 +47,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.demod.localsink", "LocalSinkSettings"}, {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap {"sdrangel.channel.localsource", "LocalSourceSettings"}, + {"sdrangel.channelrx.demodpacket", "PacketDemodSettings"}, {"sdrangel.channeltx.modpacket", "PacketModSettings"}, {"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"}, {"sdrangel.demod.remotesink", "RemoteSinkSettings"}, @@ -135,6 +136,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"}, {"NFMDemod", "NFMDemodSettings"}, {"NFMMod", "NFMModSettings"}, + {"PacketDemod", "PacketDemodSettings"}, {"PacketMod", "PacketModSettings"}, {"LocalSink", "LocalSinkSettings"}, {"LocalSource", "LocalSourceSettings"}, @@ -237,16 +239,25 @@ const QMap WebAPIUtils::m_mimoDeviceHwIdToActionsKey = { }; const QMap WebAPIUtils::m_featureTypeToSettingsKey = { + {"APRS", "APRSSettings"}, + {"GS232Controller", "GS232ControllerSettings"}, + {"Map", "MapSettings"}, {"SimplePTT", "SimplePTTSettings"}, + {"StarTracker", "StarTrackerSettings"}, {"RigCtlServer", "RigCtlServerSettings"} }; const QMap WebAPIUtils::m_featureTypeToActionsKey = { + {"Map", "MapActions"}, {"SimplePTT", "SimplePTTActions"} }; const QMap WebAPIUtils::m_featureURIToSettingsKey = { + {"sdrangel.feature.aprs", "APRSSettings"}, + {"sdrangel.feature.gs232controller", "GS232ControllerSettings"}, + {"sdrangel.feature.map", "MapSettings"}, {"sdrangel.feature.simpleptt", "SimplePTTSettings"}, + {"sdrangel.feature.startracker", "StarTrackerSettings"}, {"sdrangel.feature.rigctlserver", "RigCtlServerSettings"} }; diff --git a/swagger/sdrangel/api/swagger/include/APRS.yaml b/swagger/sdrangel/api/swagger/include/APRS.yaml new file mode 100644 index 000000000..f61120a68 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/APRS.yaml @@ -0,0 +1,30 @@ +APRSSettings: + description: "APRS settings" + properties: + igateServer: + type: string + igatePort: + type: integer + igateCallsign: + type: string + igatePasscode: + type: string + igateFilter: + type: string + igateEnabled: + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index 2a76c9654..f72c19146 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -63,6 +63,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/LocalSink.yaml#/LocalSinkSettings" LocalSourceSettings: $ref: "http://swgserver:8081/api/swagger/include/LocalSource.yaml#/LocalSourceSettings" + PacketDemodSettings: + $ref: "http://swgserver:8081/api/swagger/include/PacketDemod.yaml#/PacketDemodSettings" PacketModSettings: $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModSettings" RemoteSinkSettings: diff --git a/swagger/sdrangel/api/swagger/include/FeatureActions.yaml b/swagger/sdrangel/api/swagger/include/FeatureActions.yaml index 5739ae33e..0d9b38e3a 100644 --- a/swagger/sdrangel/api/swagger/include/FeatureActions.yaml +++ b/swagger/sdrangel/api/swagger/include/FeatureActions.yaml @@ -15,5 +15,7 @@ FeatureActions: type: integer AFCActions: $ref: "http://swgserver:8081/api/swagger/include/AFC.yaml#/AFCActions" + MapActions: + $ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapActions" SimplePTTActions: $ref: "http://swgserver:8081/api/swagger/include/SimplePTT.yaml#/SimplePTTActions" diff --git a/swagger/sdrangel/api/swagger/include/FeatureSettings.yaml b/swagger/sdrangel/api/swagger/include/FeatureSettings.yaml index 791577406..3f4d7805a 100644 --- a/swagger/sdrangel/api/swagger/include/FeatureSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/FeatureSettings.yaml @@ -15,12 +15,18 @@ FeatureSettings: type: integer AFCSettings: $ref: "http://swgserver:8081/api/swagger/include/AFC.yaml#/AFCSettings" + APRSSettings: + $ref: "http://swgserver:8081/api/swagger/include/APRS.yaml#/APRSSettings" DemodAnalyzerSettings: $ref: "http://swgserver:8081/api/swagger/include/DemodAnalyzer.yaml#/DemodAnalyzerSettings" GS232ControllerSettings: $ref: "http://swgserver:8081/api/swagger/include/GS232Controller.yaml#/GS232ControllerSettings" + MapSettings: + $ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapSettings" RigCtlServerSettings: $ref: "http://swgserver:8081/api/swagger/include/RigCtlServer.yaml#/RigCtlServerSettings" + StarTrackerSettings: + $ref: "http://swgserver:8081/api/swagger/include/StarTracker.yaml#/StarTrackerSettings" SimplePTTSettings: $ref: "http://swgserver:8081/api/swagger/include/SimplePTT.yaml#/SimplePTTSettings" VORLocalizerSettings: diff --git a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml index 7c8b2ed12..3ffbbad48 100644 --- a/swagger/sdrangel/api/swagger/include/GS232Controller.yaml +++ b/swagger/sdrangel/api/swagger/include/GS232Controller.yaml @@ -14,15 +14,21 @@ GS232ControllerSettings: description: The baud rate to use for the serial connection to the GS-232 controller type: integer track: + description: Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no) + type: integer + target: + description: "Identifier of the channel or feature plugin providing target azimuth and elevation (E.g. R0:0 ADSBDemod)" + type: string + azimuthOffset: + description: Azimuth offset in degrees + type: integer + elevationOffset: + description: Elevation offset in degrees type: integer title: type: string rgbColor: type: integer - deviceIndex: - type: integer - channelIndex: - type: integer useReverseAPI: description: Synchronize with reverse API (1 for yes, 0 for no) type: integer diff --git a/swagger/sdrangel/api/swagger/include/Map.yaml b/swagger/sdrangel/api/swagger/include/Map.yaml new file mode 100644 index 000000000..ce71eaa72 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/Map.yaml @@ -0,0 +1,57 @@ +MapSettings: + description: Map + properties: + displayNames: + description: Display object names on the map (1 for yes, 0 for no) + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer + +MapActions: + description: Map + properties: + find: + description: "The name of the item or the location to centre the map on" + type: string + +MapItem: + description: "An item to draw on the map. Set image to an empty string to remove item from the map." + required: + - name + properties: + name: + description: "A name for the item" + type: string + image: + description: "Filename or URL of image to draw on the map" + type: string + imageRotation: + description: "Angle to rotate the image by" + type: integer + imageFixedSize: + description: "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + type: integer + text: + descrption: "Text to draw on the map when item is selected" + type: string + latitude: + description: "Latitude in decimal degrees, positive to the north" + type: number + format: float + longitude: + description: "Longitude in decimal degrees, positive to the east" + type: number + format: float diff --git a/swagger/sdrangel/api/swagger/include/PacketDemod.yaml b/swagger/sdrangel/api/swagger/include/PacketDemod.yaml new file mode 100644 index 000000000..fe919fdfd --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/PacketDemod.yaml @@ -0,0 +1,35 @@ +PacketDemodSettings: + description: PacketDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + mode: + type: string + description: > + Transmission mode + * "1200 AFSK" + rfBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + rgbColor: + type: integer + title: + type: string + streamIndex: + description: MIMO channel. Not relevant when connected to SI (single Rx). + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/StarTracker.yaml b/swagger/sdrangel/api/swagger/include/StarTracker.yaml new file mode 100644 index 000000000..3a1128447 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/StarTracker.yaml @@ -0,0 +1,87 @@ +StarTrackerSettings: + description: "Star Tracker settings" + properties: + target: + description: "Target object (Sun, Moon or Custom)" + type: string + ra: + description: "Right ascension of custom target" + type: string + dec: + description: "Declination of custom target" + type: string + latitude: + description: "Latitude in decimal degrees (North positive) of observation/antenna location" + type: number + format: float + longitude: + description: "Longitude in decimal degrees (East positive) of observation/antenna location" + type: number + format: float + dateTime: + description: "Date and time of observation. ISO 8601 extended format: yyyy-MM-ddTHH:mm:ss with Z suffix for UTC. Empty string for current time." + type: string + refraction: + description: "Atmospheric refraction correction (None or Saemundsson)" + type: string + pressure: + description: "Air pressure in millibars, for refraction" + type: number + format: float + temperature: + description: "Air temperature in Celsuis, for refraction" + type: number + format: float + humidity: + description: "Relative humidity in %, for refraction" + type: number + format: float + heightAboveSeaLevel: + description: "Height above sea level in metres of observation/antenna location" + type: number + format: float + temperatureLapseRate: + description: "Temperature lapse rate in K/km" + type: number + format: float + frequency: + description: "Frequency of radio waves being observed in MHz" + type: number + format: float + stellariumServerEnabled: + descrption: "Enable Stellarium server (1 for yes, 0 for no)" + type: integer + stellariumPort: + description: "IP port number for Stellarium server to listen on (Default is 10001)." + type: integer + updatePeriod: + description: "Time in seconds between each calculation of the target's position" + type: number + format: float + epoch: + description: "Epoch for RA and Dec (J2000 or JNOW)" + type: string + drawSunOnMap: + description: "Draw the overhead position of the Sun on the Map (1 for yes, 0 for no)" + type: integer + drawMoonOnMap: + description: "Draw the overhead position of the Moon on the Map (1 for yes, 0 for no)" + type: integer + drawStarOnMap: + description: "Draw the overhead position of the target Star on the Map (1 for yes, 0 for no)" + type: integer + title: + type: string + rgbColor: + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index 16119af94..8bf3c64e6 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -2744,7 +2744,7 @@ definitions: - longitude properties: latitude: - description: "Lautitude in decimal degrees positive to the north" + description: "Latitude in decimal degrees positive to the north" type: number format: float longitude: @@ -2818,6 +2818,25 @@ definitions: description: "Serial device name or server address" type: string + MapItem: + $ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapItem" + + # This isn't in GS232Controller, as it may eventually be used by other controllers or features + TargetAzimuthElevation: + description: "A target azimuth and elevation" + properties: + name: + descrption: "The name of the target" + type: string + azimuth: + descrption: "The azimuth angle in degrees to the target" + type: number + format: float + elevation: + descrption: "The elevation angle in degrees to the target" + type: number + format: float + Presets: description: "Settings presets" required: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 6887654e4..9fff6eac4 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1062,6 +1062,51 @@ margin-bottom: 20px; } }, "description" : "AMMod" +}; + defs.APRSSettings = { + "properties" : { + "igateServer" : { + "type" : "string" + }, + "igatePort" : { + "type" : "integer" + }, + "igateCallsign" : { + "type" : "string" + }, + "igatePasscode" : { + "type" : "string" + }, + "igateFilter" : { + "type" : "string" + }, + "igateEnabled" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "APRS settings" }; defs.ATVDemodSettings = { "properties" : { @@ -2674,6 +2719,9 @@ margin-bottom: 20px; "LocalSourceSettings" : { "$ref" : "#/definitions/LocalSourceSettings" }, + "PacketDemodSettings" : { + "$ref" : "#/definitions/PacketDemodSettings" + }, "PacketModSettings" : { "$ref" : "#/definitions/PacketModSettings" }, @@ -4018,6 +4066,9 @@ margin-bottom: 20px; "AFCActions" : { "$ref" : "#/definitions/AFCActions" }, + "MapActions" : { + "$ref" : "#/definitions/MapActions" + }, "SimplePTTActions" : { "$ref" : "#/definitions/SimplePTTActions" } @@ -4122,15 +4173,24 @@ margin-bottom: 20px; "AFCSettings" : { "$ref" : "#/definitions/AFCSettings" }, + "APRSSettings" : { + "$ref" : "#/definitions/APRSSettings" + }, "DemodAnalyzerSettings" : { "$ref" : "#/definitions/DemodAnalyzerSettings" }, "GS232ControllerSettings" : { "$ref" : "#/definitions/GS232ControllerSettings" }, + "MapSettings" : { + "$ref" : "#/definitions/MapSettings" + }, "RigCtlServerSettings" : { "$ref" : "#/definitions/RigCtlServerSettings" }, + "StarTrackerSettings" : { + "$ref" : "#/definitions/StarTrackerSettings" + }, "SimplePTTSettings" : { "$ref" : "#/definitions/SimplePTTSettings" }, @@ -4867,7 +4927,20 @@ margin-bottom: 20px; "description" : "The baud rate to use for the serial connection to the GS-232 controller" }, "track" : { - "type" : "integer" + "type" : "integer", + "description" : "Track a target where azimuth and elevation are determined by another plugin (1 for yes, 0 for no)" + }, + "target" : { + "type" : "string", + "description" : "Identifier of the channel or feature plugin providing target azimuth and elevation (E.g. R0:0 ADSBDemod)" + }, + "azimuthOffset" : { + "type" : "integer", + "description" : "Azimuth offset in degrees" + }, + "elevationOffset" : { + "type" : "integer", + "description" : "Elevation offset in degrees" }, "title" : { "type" : "string" @@ -4875,12 +4948,6 @@ margin-bottom: 20px; "rgbColor" : { "type" : "integer" }, - "deviceIndex" : { - "type" : "integer" - }, - "channelIndex" : { - "type" : "integer" - }, "useReverseAPI" : { "type" : "integer", "description" : "Synchronize with reverse API (1 for yes, 0 for no)" @@ -6156,7 +6223,7 @@ margin-bottom: 20px; "latitude" : { "type" : "number", "format" : "float", - "description" : "Lautitude in decimal degrees positive to the north" + "description" : "Latitude in decimal degrees positive to the north" }, "longitude" : { "type" : "number", @@ -6186,6 +6253,116 @@ margin-bottom: 20px; } }, "description" : "Logging parameters setting" +}; + defs.MapActions = { + "properties" : { + "find" : { + "type" : "string", + "description" : "The name of the item or the location to centre the map on" + } + }, + "description" : "Map" +}; + defs.MapItem = { + "required" : [ "name" ], + "properties" : { + "name" : { + "type" : "string", + "description" : "A name for the item" + }, + "image" : { + "type" : "string", + "description" : "Filename or URL of image to draw on the map" + }, + "imageRotation" : { + "type" : "integer", + "description" : "Angle to rotate the image by" + }, + "imageFixedSize" : { + "type" : "integer", + "description" : "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + }, + "text" : { + "type" : "string" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees, positive to the north" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees, positive to the east" + } + }, + "description" : "An item to draw on the map. Set image to an empty string to remove item from the map." +}; + defs.MapItem_2 = { + "required" : [ "name" ], + "properties" : { + "name" : { + "type" : "string", + "description" : "A name for the item" + }, + "image" : { + "type" : "string", + "description" : "Filename or URL of image to draw on the map" + }, + "imageRotation" : { + "type" : "integer", + "description" : "Angle to rotate the image by" + }, + "imageFixedSize" : { + "type" : "integer", + "description" : "Keep the image the same size, regardless of map zoom level (1 for yes, 0 for no)" + }, + "text" : { + "type" : "string" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees, positive to the north" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees, positive to the east" + } + }, + "description" : "An item to draw on the map. Set image to an empty string to remove item from the map." +}; + defs.MapSettings = { + "properties" : { + "displayNames" : { + "type" : "integer", + "description" : "Display object names on the map (1 for yes, 0 for no)" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "Map" }; defs.MetisMISOSettings = { "properties" : { @@ -6554,6 +6731,53 @@ margin-bottom: 20px; } }, "description" : "Enumeration with name for values" +}; + defs.PacketDemodSettings = { + "properties" : { + "inputFrequencyOffset" : { + "type" : "integer", + "format" : "int64" + }, + "mode" : { + "type" : "string", + "description" : "Transmission mode\n * \"1200 AFSK\"\n" + }, + "rfBandwidth" : { + "type" : "number", + "format" : "float" + }, + "fmDeviation" : { + "type" : "number", + "format" : "float" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "streamIndex" : { + "type" : "integer", + "description" : "MIMO channel. Not relevant when connected to SI (single Rx)." + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "PacketDemod" }; defs.PacketModActions = { "properties" : { @@ -8751,6 +8975,121 @@ margin-bottom: 20px; "type" : "integer" } } +}; + defs.StarTrackerSettings = { + "properties" : { + "target" : { + "type" : "string", + "description" : "Target object (Sun, Moon or Custom)" + }, + "ra" : { + "type" : "string", + "description" : "Right ascension of custom target" + }, + "dec" : { + "type" : "string", + "description" : "Declination of custom target" + }, + "latitude" : { + "type" : "number", + "format" : "float", + "description" : "Latitude in decimal degrees (North positive) of observation/antenna location" + }, + "longitude" : { + "type" : "number", + "format" : "float", + "description" : "Longitude in decimal degrees (East positive) of observation/antenna location" + }, + "dateTime" : { + "type" : "string", + "description" : "Date and time of observation. ISO 8601 extended format: yyyy-MM-ddTHH:mm:ss with Z suffix for UTC. Empty string for current time." + }, + "refraction" : { + "type" : "string", + "description" : "Atmospheric refraction correction (None or Saemundsson)" + }, + "pressure" : { + "type" : "number", + "format" : "float", + "description" : "Air pressure in millibars, for refraction" + }, + "temperature" : { + "type" : "number", + "format" : "float", + "description" : "Air temperature in Celsuis, for refraction" + }, + "humidity" : { + "type" : "number", + "format" : "float", + "description" : "Relative humidity in %, for refraction" + }, + "heightAboveSeaLevel" : { + "type" : "number", + "format" : "float", + "description" : "Height above sea level in metres of observation/antenna location" + }, + "temperatureLapseRate" : { + "type" : "number", + "format" : "float", + "description" : "Temperature lapse rate in K/km" + }, + "frequency" : { + "type" : "number", + "format" : "float", + "description" : "Frequency of radio waves being observed in MHz" + }, + "stellariumServerEnabled" : { + "type" : "integer" + }, + "stellariumPort" : { + "type" : "integer", + "description" : "IP port number for Stellarium server to listen on (Default is 10001)." + }, + "updatePeriod" : { + "type" : "number", + "format" : "float", + "description" : "Time in seconds between each calculation of the target's position" + }, + "epoch" : { + "type" : "string", + "description" : "Epoch for RA and Dec (J2000 or JNOW)" + }, + "drawSunOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the Sun on the Map (1 for yes, 0 for no)" + }, + "drawMoonOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the Moon on the Map (1 for yes, 0 for no)" + }, + "drawStarOnMap" : { + "type" : "integer", + "description" : "Draw the overhead position of the target Star on the Map (1 for yes, 0 for no)" + }, + "title" : { + "type" : "string" + }, + "rgbColor" : { + "type" : "integer" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + } + }, + "description" : "Star Tracker settings" }; defs.SuccessResponse = { "required" : [ "message" ], @@ -8759,6 +9098,22 @@ margin-bottom: 20px; "type" : "string" } } +}; + defs.TargetAzimuthElevation = { + "properties" : { + "name" : { + "type" : "string" + }, + "azimuth" : { + "type" : "number", + "format" : "float" + }, + "elevation" : { + "type" : "number", + "format" : "float" + } + }, + "description" : "A target azimuth and elevation" }; defs.TestMISettings = { "properties" : { @@ -44917,7 +45272,7 @@ except ApiException as e:
- Generated 2020-12-20T18:34:47.837+01:00 + Generated 2021-01-13T17:40:49.583+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.cpp new file mode 100644 index 000000000..30ca1b992 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.cpp @@ -0,0 +1,396 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAPRSSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAPRSSettings::SWGAPRSSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAPRSSettings::SWGAPRSSettings() { + igate_server = nullptr; + m_igate_server_isSet = false; + igate_port = 0; + m_igate_port_isSet = false; + igate_callsign = nullptr; + m_igate_callsign_isSet = false; + igate_passcode = nullptr; + m_igate_passcode_isSet = false; + igate_filter = nullptr; + m_igate_filter_isSet = false; + igate_enabled = 0; + m_igate_enabled_isSet = false; + title = nullptr; + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGAPRSSettings::~SWGAPRSSettings() { + this->cleanup(); +} + +void +SWGAPRSSettings::init() { + igate_server = new QString(""); + m_igate_server_isSet = false; + igate_port = 0; + m_igate_port_isSet = false; + igate_callsign = new QString(""); + m_igate_callsign_isSet = false; + igate_passcode = new QString(""); + m_igate_passcode_isSet = false; + igate_filter = new QString(""); + m_igate_filter_isSet = false; + igate_enabled = 0; + m_igate_enabled_isSet = false; + title = new QString(""); + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGAPRSSettings::cleanup() { + if(igate_server != nullptr) { + delete igate_server; + } + + if(igate_callsign != nullptr) { + delete igate_callsign; + } + if(igate_passcode != nullptr) { + delete igate_passcode; + } + if(igate_filter != nullptr) { + delete igate_filter; + } + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGAPRSSettings* +SWGAPRSSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAPRSSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&igate_server, pJson["igateServer"], "QString", "QString"); + + ::SWGSDRangel::setValue(&igate_port, pJson["igatePort"], "qint32", ""); + + ::SWGSDRangel::setValue(&igate_callsign, pJson["igateCallsign"], "QString", "QString"); + + ::SWGSDRangel::setValue(&igate_passcode, pJson["igatePasscode"], "QString", "QString"); + + ::SWGSDRangel::setValue(&igate_filter, pJson["igateFilter"], "QString", "QString"); + + ::SWGSDRangel::setValue(&igate_enabled, pJson["igateEnabled"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGAPRSSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAPRSSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(igate_server != nullptr && *igate_server != QString("")){ + toJsonValue(QString("igateServer"), igate_server, obj, QString("QString")); + } + if(m_igate_port_isSet){ + obj->insert("igatePort", QJsonValue(igate_port)); + } + if(igate_callsign != nullptr && *igate_callsign != QString("")){ + toJsonValue(QString("igateCallsign"), igate_callsign, obj, QString("QString")); + } + if(igate_passcode != nullptr && *igate_passcode != QString("")){ + toJsonValue(QString("igatePasscode"), igate_passcode, obj, QString("QString")); + } + if(igate_filter != nullptr && *igate_filter != QString("")){ + toJsonValue(QString("igateFilter"), igate_filter, obj, QString("QString")); + } + if(m_igate_enabled_isSet){ + obj->insert("igateEnabled", QJsonValue(igate_enabled)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +QString* +SWGAPRSSettings::getIgateServer() { + return igate_server; +} +void +SWGAPRSSettings::setIgateServer(QString* igate_server) { + this->igate_server = igate_server; + this->m_igate_server_isSet = true; +} + +qint32 +SWGAPRSSettings::getIgatePort() { + return igate_port; +} +void +SWGAPRSSettings::setIgatePort(qint32 igate_port) { + this->igate_port = igate_port; + this->m_igate_port_isSet = true; +} + +QString* +SWGAPRSSettings::getIgateCallsign() { + return igate_callsign; +} +void +SWGAPRSSettings::setIgateCallsign(QString* igate_callsign) { + this->igate_callsign = igate_callsign; + this->m_igate_callsign_isSet = true; +} + +QString* +SWGAPRSSettings::getIgatePasscode() { + return igate_passcode; +} +void +SWGAPRSSettings::setIgatePasscode(QString* igate_passcode) { + this->igate_passcode = igate_passcode; + this->m_igate_passcode_isSet = true; +} + +QString* +SWGAPRSSettings::getIgateFilter() { + return igate_filter; +} +void +SWGAPRSSettings::setIgateFilter(QString* igate_filter) { + this->igate_filter = igate_filter; + this->m_igate_filter_isSet = true; +} + +qint32 +SWGAPRSSettings::getIgateEnabled() { + return igate_enabled; +} +void +SWGAPRSSettings::setIgateEnabled(qint32 igate_enabled) { + this->igate_enabled = igate_enabled; + this->m_igate_enabled_isSet = true; +} + +QString* +SWGAPRSSettings::getTitle() { + return title; +} +void +SWGAPRSSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGAPRSSettings::getRgbColor() { + return rgb_color; +} +void +SWGAPRSSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +qint32 +SWGAPRSSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGAPRSSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGAPRSSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGAPRSSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGAPRSSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGAPRSSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGAPRSSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGAPRSSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGAPRSSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGAPRSSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGAPRSSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(igate_server && *igate_server != QString("")){ + isObjectUpdated = true; break; + } + if(m_igate_port_isSet){ + isObjectUpdated = true; break; + } + if(igate_callsign && *igate_callsign != QString("")){ + isObjectUpdated = true; break; + } + if(igate_passcode && *igate_passcode != QString("")){ + isObjectUpdated = true; break; + } + if(igate_filter && *igate_filter != QString("")){ + isObjectUpdated = true; break; + } + if(m_igate_enabled_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.h b/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.h new file mode 100644 index 000000000..d29971bbe --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAPRSSettings.h @@ -0,0 +1,131 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAPRSSettings.h + * + * APRS settings + */ + +#ifndef SWGAPRSSettings_H_ +#define SWGAPRSSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAPRSSettings: public SWGObject { +public: + SWGAPRSSettings(); + SWGAPRSSettings(QString* json); + virtual ~SWGAPRSSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAPRSSettings* fromJson(QString &jsonString) override; + + QString* getIgateServer(); + void setIgateServer(QString* igate_server); + + qint32 getIgatePort(); + void setIgatePort(qint32 igate_port); + + QString* getIgateCallsign(); + void setIgateCallsign(QString* igate_callsign); + + QString* getIgatePasscode(); + void setIgatePasscode(QString* igate_passcode); + + QString* getIgateFilter(); + void setIgateFilter(QString* igate_filter); + + qint32 getIgateEnabled(); + void setIgateEnabled(qint32 igate_enabled); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + QString* igate_server; + bool m_igate_server_isSet; + + qint32 igate_port; + bool m_igate_port_isSet; + + QString* igate_callsign; + bool m_igate_callsign_isSet; + + QString* igate_passcode; + bool m_igate_passcode_isSet; + + QString* igate_filter; + bool m_igate_filter_isSet; + + qint32 igate_enabled; + bool m_igate_enabled_isSet; + + QString* title; + bool m_title_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGAPRSSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index e631ed57c..1a2b5ee4c 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -82,6 +82,8 @@ SWGChannelSettings::SWGChannelSettings() { m_local_sink_settings_isSet = false; local_source_settings = nullptr; m_local_source_settings_isSet = false; + packet_demod_settings = nullptr; + m_packet_demod_settings_isSet = false; packet_mod_settings = nullptr; m_packet_mod_settings_isSet = false; remote_sink_settings = nullptr; @@ -168,6 +170,8 @@ SWGChannelSettings::init() { m_local_sink_settings_isSet = false; local_source_settings = new SWGLocalSourceSettings(); m_local_source_settings_isSet = false; + packet_demod_settings = new SWGPacketDemodSettings(); + m_packet_demod_settings_isSet = false; packet_mod_settings = new SWGPacketModSettings(); m_packet_mod_settings_isSet = false; remote_sink_settings = new SWGRemoteSinkSettings(); @@ -271,6 +275,9 @@ SWGChannelSettings::cleanup() { if(local_source_settings != nullptr) { delete local_source_settings; } + if(packet_demod_settings != nullptr) { + delete packet_demod_settings; + } if(packet_mod_settings != nullptr) { delete packet_mod_settings; } @@ -374,6 +381,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&local_source_settings, pJson["LocalSourceSettings"], "SWGLocalSourceSettings", "SWGLocalSourceSettings"); + ::SWGSDRangel::setValue(&packet_demod_settings, pJson["PacketDemodSettings"], "SWGPacketDemodSettings", "SWGPacketDemodSettings"); + ::SWGSDRangel::setValue(&packet_mod_settings, pJson["PacketModSettings"], "SWGPacketModSettings", "SWGPacketModSettings"); ::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings"); @@ -495,6 +504,9 @@ SWGChannelSettings::asJsonObject() { if((local_source_settings != nullptr) && (local_source_settings->isSet())){ toJsonValue(QString("LocalSourceSettings"), local_source_settings, obj, QString("SWGLocalSourceSettings")); } + if((packet_demod_settings != nullptr) && (packet_demod_settings->isSet())){ + toJsonValue(QString("PacketDemodSettings"), packet_demod_settings, obj, QString("SWGPacketDemodSettings")); + } if((packet_mod_settings != nullptr) && (packet_mod_settings->isSet())){ toJsonValue(QString("PacketModSettings"), packet_mod_settings, obj, QString("SWGPacketModSettings")); } @@ -805,6 +817,16 @@ SWGChannelSettings::setLocalSourceSettings(SWGLocalSourceSettings* local_source_ this->m_local_source_settings_isSet = true; } +SWGPacketDemodSettings* +SWGChannelSettings::getPacketDemodSettings() { + return packet_demod_settings; +} +void +SWGChannelSettings::setPacketDemodSettings(SWGPacketDemodSettings* packet_demod_settings) { + this->packet_demod_settings = packet_demod_settings; + this->m_packet_demod_settings_isSet = true; +} + SWGPacketModSettings* SWGChannelSettings::getPacketModSettings() { return packet_mod_settings; @@ -1011,6 +1033,9 @@ SWGChannelSettings::isSet(){ if(local_source_settings && local_source_settings->isSet()){ isObjectUpdated = true; break; } + if(packet_demod_settings && packet_demod_settings->isSet()){ + isObjectUpdated = true; break; + } if(packet_mod_settings && packet_mod_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 58a49cc71..1dde09d7d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -45,6 +45,7 @@ #include "SWGLocalSourceSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGPacketDemodSettings.h" #include "SWGPacketModSettings.h" #include "SWGRemoteSinkSettings.h" #include "SWGRemoteSourceSettings.h" @@ -158,6 +159,9 @@ public: SWGLocalSourceSettings* getLocalSourceSettings(); void setLocalSourceSettings(SWGLocalSourceSettings* local_source_settings); + SWGPacketDemodSettings* getPacketDemodSettings(); + void setPacketDemodSettings(SWGPacketDemodSettings* packet_demod_settings); + SWGPacketModSettings* getPacketModSettings(); void setPacketModSettings(SWGPacketModSettings* packet_mod_settings); @@ -279,6 +283,9 @@ private: SWGLocalSourceSettings* local_source_settings; bool m_local_source_settings_isSet; + SWGPacketDemodSettings* packet_demod_settings; + bool m_packet_demod_settings_isSet; + SWGPacketModSettings* packet_mod_settings; bool m_packet_mod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGFeatureActions.cpp b/swagger/sdrangel/code/qt5/client/SWGFeatureActions.cpp index 14103f987..f4f0a2fe7 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFeatureActions.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFeatureActions.cpp @@ -36,6 +36,8 @@ SWGFeatureActions::SWGFeatureActions() { m_originator_feature_index_isSet = false; afc_actions = nullptr; m_afc_actions_isSet = false; + map_actions = nullptr; + m_map_actions_isSet = false; simple_ptt_actions = nullptr; m_simple_ptt_actions_isSet = false; } @@ -54,6 +56,8 @@ SWGFeatureActions::init() { m_originator_feature_index_isSet = false; afc_actions = new SWGAFCActions(); m_afc_actions_isSet = false; + map_actions = new SWGMapActions(); + m_map_actions_isSet = false; simple_ptt_actions = new SWGSimplePTTActions(); m_simple_ptt_actions_isSet = false; } @@ -68,6 +72,9 @@ SWGFeatureActions::cleanup() { if(afc_actions != nullptr) { delete afc_actions; } + if(map_actions != nullptr) { + delete map_actions; + } if(simple_ptt_actions != nullptr) { delete simple_ptt_actions; } @@ -92,6 +99,8 @@ SWGFeatureActions::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&afc_actions, pJson["AFCActions"], "SWGAFCActions", "SWGAFCActions"); + ::SWGSDRangel::setValue(&map_actions, pJson["MapActions"], "SWGMapActions", "SWGMapActions"); + ::SWGSDRangel::setValue(&simple_ptt_actions, pJson["SimplePTTActions"], "SWGSimplePTTActions", "SWGSimplePTTActions"); } @@ -122,6 +131,9 @@ SWGFeatureActions::asJsonObject() { if((afc_actions != nullptr) && (afc_actions->isSet())){ toJsonValue(QString("AFCActions"), afc_actions, obj, QString("SWGAFCActions")); } + if((map_actions != nullptr) && (map_actions->isSet())){ + toJsonValue(QString("MapActions"), map_actions, obj, QString("SWGMapActions")); + } if((simple_ptt_actions != nullptr) && (simple_ptt_actions->isSet())){ toJsonValue(QString("SimplePTTActions"), simple_ptt_actions, obj, QString("SWGSimplePTTActions")); } @@ -169,6 +181,16 @@ SWGFeatureActions::setAfcActions(SWGAFCActions* afc_actions) { this->m_afc_actions_isSet = true; } +SWGMapActions* +SWGFeatureActions::getMapActions() { + return map_actions; +} +void +SWGFeatureActions::setMapActions(SWGMapActions* map_actions) { + this->map_actions = map_actions; + this->m_map_actions_isSet = true; +} + SWGSimplePTTActions* SWGFeatureActions::getSimplePttActions() { return simple_ptt_actions; @@ -196,6 +218,9 @@ SWGFeatureActions::isSet(){ if(afc_actions && afc_actions->isSet()){ isObjectUpdated = true; break; } + if(map_actions && map_actions->isSet()){ + isObjectUpdated = true; break; + } if(simple_ptt_actions && simple_ptt_actions->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGFeatureActions.h b/swagger/sdrangel/code/qt5/client/SWGFeatureActions.h index 87194b32d..1d980a669 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFeatureActions.h +++ b/swagger/sdrangel/code/qt5/client/SWGFeatureActions.h @@ -23,6 +23,7 @@ #include "SWGAFCActions.h" +#include "SWGMapActions.h" #include "SWGSimplePTTActions.h" #include @@ -56,6 +57,9 @@ public: SWGAFCActions* getAfcActions(); void setAfcActions(SWGAFCActions* afc_actions); + SWGMapActions* getMapActions(); + void setMapActions(SWGMapActions* map_actions); + SWGSimplePTTActions* getSimplePttActions(); void setSimplePttActions(SWGSimplePTTActions* simple_ptt_actions); @@ -75,6 +79,9 @@ private: SWGAFCActions* afc_actions; bool m_afc_actions_isSet; + SWGMapActions* map_actions; + bool m_map_actions_isSet; + SWGSimplePTTActions* simple_ptt_actions; bool m_simple_ptt_actions_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.cpp index f2dae2151..fecafd5bd 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.cpp @@ -36,12 +36,18 @@ SWGFeatureSettings::SWGFeatureSettings() { m_originator_feature_index_isSet = false; afc_settings = nullptr; m_afc_settings_isSet = false; + aprs_settings = nullptr; + m_aprs_settings_isSet = false; demod_analyzer_settings = nullptr; m_demod_analyzer_settings_isSet = false; gs232_controller_settings = nullptr; m_gs232_controller_settings_isSet = false; + map_settings = nullptr; + m_map_settings_isSet = false; rig_ctl_server_settings = nullptr; m_rig_ctl_server_settings_isSet = false; + star_tracker_settings = nullptr; + m_star_tracker_settings_isSet = false; simple_ptt_settings = nullptr; m_simple_ptt_settings_isSet = false; vor_localizer_settings = nullptr; @@ -62,12 +68,18 @@ SWGFeatureSettings::init() { m_originator_feature_index_isSet = false; afc_settings = new SWGAFCSettings(); m_afc_settings_isSet = false; + aprs_settings = new SWGAPRSSettings(); + m_aprs_settings_isSet = false; demod_analyzer_settings = new SWGDemodAnalyzerSettings(); m_demod_analyzer_settings_isSet = false; gs232_controller_settings = new SWGGS232ControllerSettings(); m_gs232_controller_settings_isSet = false; + map_settings = new SWGMapSettings(); + m_map_settings_isSet = false; rig_ctl_server_settings = new SWGRigCtlServerSettings(); m_rig_ctl_server_settings_isSet = false; + star_tracker_settings = new SWGStarTrackerSettings(); + m_star_tracker_settings_isSet = false; simple_ptt_settings = new SWGSimplePTTSettings(); m_simple_ptt_settings_isSet = false; vor_localizer_settings = new SWGVORLocalizerSettings(); @@ -84,15 +96,24 @@ SWGFeatureSettings::cleanup() { if(afc_settings != nullptr) { delete afc_settings; } + if(aprs_settings != nullptr) { + delete aprs_settings; + } if(demod_analyzer_settings != nullptr) { delete demod_analyzer_settings; } if(gs232_controller_settings != nullptr) { delete gs232_controller_settings; } + if(map_settings != nullptr) { + delete map_settings; + } if(rig_ctl_server_settings != nullptr) { delete rig_ctl_server_settings; } + if(star_tracker_settings != nullptr) { + delete star_tracker_settings; + } if(simple_ptt_settings != nullptr) { delete simple_ptt_settings; } @@ -120,12 +141,18 @@ SWGFeatureSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&afc_settings, pJson["AFCSettings"], "SWGAFCSettings", "SWGAFCSettings"); + ::SWGSDRangel::setValue(&aprs_settings, pJson["APRSSettings"], "SWGAPRSSettings", "SWGAPRSSettings"); + ::SWGSDRangel::setValue(&demod_analyzer_settings, pJson["DemodAnalyzerSettings"], "SWGDemodAnalyzerSettings", "SWGDemodAnalyzerSettings"); ::SWGSDRangel::setValue(&gs232_controller_settings, pJson["GS232ControllerSettings"], "SWGGS232ControllerSettings", "SWGGS232ControllerSettings"); + ::SWGSDRangel::setValue(&map_settings, pJson["MapSettings"], "SWGMapSettings", "SWGMapSettings"); + ::SWGSDRangel::setValue(&rig_ctl_server_settings, pJson["RigCtlServerSettings"], "SWGRigCtlServerSettings", "SWGRigCtlServerSettings"); + ::SWGSDRangel::setValue(&star_tracker_settings, pJson["StarTrackerSettings"], "SWGStarTrackerSettings", "SWGStarTrackerSettings"); + ::SWGSDRangel::setValue(&simple_ptt_settings, pJson["SimplePTTSettings"], "SWGSimplePTTSettings", "SWGSimplePTTSettings"); ::SWGSDRangel::setValue(&vor_localizer_settings, pJson["VORLocalizerSettings"], "SWGVORLocalizerSettings", "SWGVORLocalizerSettings"); @@ -158,15 +185,24 @@ SWGFeatureSettings::asJsonObject() { if((afc_settings != nullptr) && (afc_settings->isSet())){ toJsonValue(QString("AFCSettings"), afc_settings, obj, QString("SWGAFCSettings")); } + if((aprs_settings != nullptr) && (aprs_settings->isSet())){ + toJsonValue(QString("APRSSettings"), aprs_settings, obj, QString("SWGAPRSSettings")); + } if((demod_analyzer_settings != nullptr) && (demod_analyzer_settings->isSet())){ toJsonValue(QString("DemodAnalyzerSettings"), demod_analyzer_settings, obj, QString("SWGDemodAnalyzerSettings")); } if((gs232_controller_settings != nullptr) && (gs232_controller_settings->isSet())){ toJsonValue(QString("GS232ControllerSettings"), gs232_controller_settings, obj, QString("SWGGS232ControllerSettings")); } + if((map_settings != nullptr) && (map_settings->isSet())){ + toJsonValue(QString("MapSettings"), map_settings, obj, QString("SWGMapSettings")); + } if((rig_ctl_server_settings != nullptr) && (rig_ctl_server_settings->isSet())){ toJsonValue(QString("RigCtlServerSettings"), rig_ctl_server_settings, obj, QString("SWGRigCtlServerSettings")); } + if((star_tracker_settings != nullptr) && (star_tracker_settings->isSet())){ + toJsonValue(QString("StarTrackerSettings"), star_tracker_settings, obj, QString("SWGStarTrackerSettings")); + } if((simple_ptt_settings != nullptr) && (simple_ptt_settings->isSet())){ toJsonValue(QString("SimplePTTSettings"), simple_ptt_settings, obj, QString("SWGSimplePTTSettings")); } @@ -217,6 +253,16 @@ SWGFeatureSettings::setAfcSettings(SWGAFCSettings* afc_settings) { this->m_afc_settings_isSet = true; } +SWGAPRSSettings* +SWGFeatureSettings::getAprsSettings() { + return aprs_settings; +} +void +SWGFeatureSettings::setAprsSettings(SWGAPRSSettings* aprs_settings) { + this->aprs_settings = aprs_settings; + this->m_aprs_settings_isSet = true; +} + SWGDemodAnalyzerSettings* SWGFeatureSettings::getDemodAnalyzerSettings() { return demod_analyzer_settings; @@ -237,6 +283,16 @@ SWGFeatureSettings::setGs232ControllerSettings(SWGGS232ControllerSettings* gs232 this->m_gs232_controller_settings_isSet = true; } +SWGMapSettings* +SWGFeatureSettings::getMapSettings() { + return map_settings; +} +void +SWGFeatureSettings::setMapSettings(SWGMapSettings* map_settings) { + this->map_settings = map_settings; + this->m_map_settings_isSet = true; +} + SWGRigCtlServerSettings* SWGFeatureSettings::getRigCtlServerSettings() { return rig_ctl_server_settings; @@ -247,6 +303,16 @@ SWGFeatureSettings::setRigCtlServerSettings(SWGRigCtlServerSettings* rig_ctl_ser this->m_rig_ctl_server_settings_isSet = true; } +SWGStarTrackerSettings* +SWGFeatureSettings::getStarTrackerSettings() { + return star_tracker_settings; +} +void +SWGFeatureSettings::setStarTrackerSettings(SWGStarTrackerSettings* star_tracker_settings) { + this->star_tracker_settings = star_tracker_settings; + this->m_star_tracker_settings_isSet = true; +} + SWGSimplePTTSettings* SWGFeatureSettings::getSimplePttSettings() { return simple_ptt_settings; @@ -284,15 +350,24 @@ SWGFeatureSettings::isSet(){ if(afc_settings && afc_settings->isSet()){ isObjectUpdated = true; break; } + if(aprs_settings && aprs_settings->isSet()){ + isObjectUpdated = true; break; + } if(demod_analyzer_settings && demod_analyzer_settings->isSet()){ isObjectUpdated = true; break; } if(gs232_controller_settings && gs232_controller_settings->isSet()){ isObjectUpdated = true; break; } + if(map_settings && map_settings->isSet()){ + isObjectUpdated = true; break; + } if(rig_ctl_server_settings && rig_ctl_server_settings->isSet()){ isObjectUpdated = true; break; } + if(star_tracker_settings && star_tracker_settings->isSet()){ + isObjectUpdated = true; break; + } if(simple_ptt_settings && simple_ptt_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.h b/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.h index 9ce7e16d5..36858b501 100644 --- a/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGFeatureSettings.h @@ -23,10 +23,13 @@ #include "SWGAFCSettings.h" +#include "SWGAPRSSettings.h" #include "SWGDemodAnalyzerSettings.h" #include "SWGGS232ControllerSettings.h" +#include "SWGMapSettings.h" #include "SWGRigCtlServerSettings.h" #include "SWGSimplePTTSettings.h" +#include "SWGStarTrackerSettings.h" #include "SWGVORLocalizerSettings.h" #include @@ -60,15 +63,24 @@ public: SWGAFCSettings* getAfcSettings(); void setAfcSettings(SWGAFCSettings* afc_settings); + SWGAPRSSettings* getAprsSettings(); + void setAprsSettings(SWGAPRSSettings* aprs_settings); + SWGDemodAnalyzerSettings* getDemodAnalyzerSettings(); void setDemodAnalyzerSettings(SWGDemodAnalyzerSettings* demod_analyzer_settings); SWGGS232ControllerSettings* getGs232ControllerSettings(); void setGs232ControllerSettings(SWGGS232ControllerSettings* gs232_controller_settings); + SWGMapSettings* getMapSettings(); + void setMapSettings(SWGMapSettings* map_settings); + SWGRigCtlServerSettings* getRigCtlServerSettings(); void setRigCtlServerSettings(SWGRigCtlServerSettings* rig_ctl_server_settings); + SWGStarTrackerSettings* getStarTrackerSettings(); + void setStarTrackerSettings(SWGStarTrackerSettings* star_tracker_settings); + SWGSimplePTTSettings* getSimplePttSettings(); void setSimplePttSettings(SWGSimplePTTSettings* simple_ptt_settings); @@ -91,15 +103,24 @@ private: SWGAFCSettings* afc_settings; bool m_afc_settings_isSet; + SWGAPRSSettings* aprs_settings; + bool m_aprs_settings_isSet; + SWGDemodAnalyzerSettings* demod_analyzer_settings; bool m_demod_analyzer_settings_isSet; SWGGS232ControllerSettings* gs232_controller_settings; bool m_gs232_controller_settings_isSet; + SWGMapSettings* map_settings; + bool m_map_settings_isSet; + SWGRigCtlServerSettings* rig_ctl_server_settings; bool m_rig_ctl_server_settings_isSet; + SWGStarTrackerSettings* star_tracker_settings; + bool m_star_tracker_settings_isSet; + SWGSimplePTTSettings* simple_ptt_settings; bool m_simple_ptt_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp index fa5adc558..15394e4d9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.cpp @@ -38,14 +38,16 @@ SWGGS232ControllerSettings::SWGGS232ControllerSettings() { m_baud_rate_isSet = false; track = 0; m_track_isSet = false; + target = nullptr; + m_target_isSet = false; + azimuth_offset = 0; + m_azimuth_offset_isSet = false; + elevation_offset = 0; + m_elevation_offset_isSet = false; title = nullptr; m_title_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; - device_index = 0; - m_device_index_isSet = false; - channel_index = 0; - m_channel_index_isSet = false; use_reverse_api = 0; m_use_reverse_api_isSet = false; reverse_api_address = nullptr; @@ -74,14 +76,16 @@ SWGGS232ControllerSettings::init() { m_baud_rate_isSet = false; track = 0; m_track_isSet = false; + target = new QString(""); + m_target_isSet = false; + azimuth_offset = 0; + m_azimuth_offset_isSet = false; + elevation_offset = 0; + m_elevation_offset_isSet = false; title = new QString(""); m_title_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; - device_index = 0; - m_device_index_isSet = false; - channel_index = 0; - m_channel_index_isSet = false; use_reverse_api = 0; m_use_reverse_api_isSet = false; reverse_api_address = new QString(""); @@ -103,11 +107,14 @@ SWGGS232ControllerSettings::cleanup() { } - if(title != nullptr) { - delete title; + if(target != nullptr) { + delete target; } + if(title != nullptr) { + delete title; + } if(reverse_api_address != nullptr) { @@ -139,14 +146,16 @@ SWGGS232ControllerSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&track, pJson["track"], "qint32", ""); + ::SWGSDRangel::setValue(&target, pJson["target"], "QString", "QString"); + + ::SWGSDRangel::setValue(&azimuth_offset, pJson["azimuthOffset"], "qint32", ""); + + ::SWGSDRangel::setValue(&elevation_offset, pJson["elevationOffset"], "qint32", ""); + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); - ::SWGSDRangel::setValue(&device_index, pJson["deviceIndex"], "qint32", ""); - - ::SWGSDRangel::setValue(&channel_index, pJson["channelIndex"], "qint32", ""); - ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); @@ -188,18 +197,21 @@ SWGGS232ControllerSettings::asJsonObject() { if(m_track_isSet){ obj->insert("track", QJsonValue(track)); } + if(target != nullptr && *target != QString("")){ + toJsonValue(QString("target"), target, obj, QString("QString")); + } + if(m_azimuth_offset_isSet){ + obj->insert("azimuthOffset", QJsonValue(azimuth_offset)); + } + if(m_elevation_offset_isSet){ + obj->insert("elevationOffset", QJsonValue(elevation_offset)); + } if(title != nullptr && *title != QString("")){ toJsonValue(QString("title"), title, obj, QString("QString")); } if(m_rgb_color_isSet){ obj->insert("rgbColor", QJsonValue(rgb_color)); } - if(m_device_index_isSet){ - obj->insert("deviceIndex", QJsonValue(device_index)); - } - if(m_channel_index_isSet){ - obj->insert("channelIndex", QJsonValue(channel_index)); - } if(m_use_reverse_api_isSet){ obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); } @@ -269,6 +281,36 @@ SWGGS232ControllerSettings::setTrack(qint32 track) { this->m_track_isSet = true; } +QString* +SWGGS232ControllerSettings::getTarget() { + return target; +} +void +SWGGS232ControllerSettings::setTarget(QString* target) { + this->target = target; + this->m_target_isSet = true; +} + +qint32 +SWGGS232ControllerSettings::getAzimuthOffset() { + return azimuth_offset; +} +void +SWGGS232ControllerSettings::setAzimuthOffset(qint32 azimuth_offset) { + this->azimuth_offset = azimuth_offset; + this->m_azimuth_offset_isSet = true; +} + +qint32 +SWGGS232ControllerSettings::getElevationOffset() { + return elevation_offset; +} +void +SWGGS232ControllerSettings::setElevationOffset(qint32 elevation_offset) { + this->elevation_offset = elevation_offset; + this->m_elevation_offset_isSet = true; +} + QString* SWGGS232ControllerSettings::getTitle() { return title; @@ -289,26 +331,6 @@ SWGGS232ControllerSettings::setRgbColor(qint32 rgb_color) { this->m_rgb_color_isSet = true; } -qint32 -SWGGS232ControllerSettings::getDeviceIndex() { - return device_index; -} -void -SWGGS232ControllerSettings::setDeviceIndex(qint32 device_index) { - this->device_index = device_index; - this->m_device_index_isSet = true; -} - -qint32 -SWGGS232ControllerSettings::getChannelIndex() { - return channel_index; -} -void -SWGGS232ControllerSettings::setChannelIndex(qint32 channel_index) { - this->channel_index = channel_index; - this->m_channel_index_isSet = true; -} - qint32 SWGGS232ControllerSettings::getUseReverseApi() { return use_reverse_api; @@ -379,18 +401,21 @@ SWGGS232ControllerSettings::isSet(){ if(m_track_isSet){ isObjectUpdated = true; break; } + if(target && *target != QString("")){ + isObjectUpdated = true; break; + } + if(m_azimuth_offset_isSet){ + isObjectUpdated = true; break; + } + if(m_elevation_offset_isSet){ + isObjectUpdated = true; break; + } if(title && *title != QString("")){ isObjectUpdated = true; break; } if(m_rgb_color_isSet){ isObjectUpdated = true; break; } - if(m_device_index_isSet){ - isObjectUpdated = true; break; - } - if(m_channel_index_isSet){ - isObjectUpdated = true; break; - } if(m_use_reverse_api_isSet){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h index e52a1936b..1a662bf9b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGGS232ControllerSettings.h @@ -57,18 +57,21 @@ public: qint32 getTrack(); void setTrack(qint32 track); + QString* getTarget(); + void setTarget(QString* target); + + qint32 getAzimuthOffset(); + void setAzimuthOffset(qint32 azimuth_offset); + + qint32 getElevationOffset(); + void setElevationOffset(qint32 elevation_offset); + QString* getTitle(); void setTitle(QString* title); qint32 getRgbColor(); void setRgbColor(qint32 rgb_color); - qint32 getDeviceIndex(); - void setDeviceIndex(qint32 device_index); - - qint32 getChannelIndex(); - void setChannelIndex(qint32 channel_index); - qint32 getUseReverseApi(); void setUseReverseApi(qint32 use_reverse_api); @@ -103,18 +106,21 @@ private: qint32 track; bool m_track_isSet; + QString* target; + bool m_target_isSet; + + qint32 azimuth_offset; + bool m_azimuth_offset_isSet; + + qint32 elevation_offset; + bool m_elevation_offset_isSet; + QString* title; bool m_title_isSet; qint32 rgb_color; bool m_rgb_color_isSet; - qint32 device_index; - bool m_device_index_isSet; - - qint32 channel_index; - bool m_channel_index_isSet; - qint32 use_reverse_api; bool m_use_reverse_api_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGMapActions.cpp b/swagger/sdrangel/code/qt5/client/SWGMapActions.cpp new file mode 100644 index 000000000..dbe5a9c0c --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapActions.cpp @@ -0,0 +1,110 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGMapActions.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGMapActions::SWGMapActions(QString* json) { + init(); + this->fromJson(*json); +} + +SWGMapActions::SWGMapActions() { + find = nullptr; + m_find_isSet = false; +} + +SWGMapActions::~SWGMapActions() { + this->cleanup(); +} + +void +SWGMapActions::init() { + find = new QString(""); + m_find_isSet = false; +} + +void +SWGMapActions::cleanup() { + if(find != nullptr) { + delete find; + } +} + +SWGMapActions* +SWGMapActions::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGMapActions::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&find, pJson["find"], "QString", "QString"); + +} + +QString +SWGMapActions::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGMapActions::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(find != nullptr && *find != QString("")){ + toJsonValue(QString("find"), find, obj, QString("QString")); + } + + return obj; +} + +QString* +SWGMapActions::getFind() { + return find; +} +void +SWGMapActions::setFind(QString* find) { + this->find = find; + this->m_find_isSet = true; +} + + +bool +SWGMapActions::isSet(){ + bool isObjectUpdated = false; + do{ + if(find && *find != QString("")){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGMapActions.h b/swagger/sdrangel/code/qt5/client/SWGMapActions.h new file mode 100644 index 000000000..67f4ed572 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapActions.h @@ -0,0 +1,59 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGMapActions.h + * + * Map + */ + +#ifndef SWGMapActions_H_ +#define SWGMapActions_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGMapActions: public SWGObject { +public: + SWGMapActions(); + SWGMapActions(QString* json); + virtual ~SWGMapActions(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGMapActions* fromJson(QString &jsonString) override; + + QString* getFind(); + void setFind(QString* find); + + + virtual bool isSet() override; + +private: + QString* find; + bool m_find_isSet; + +}; + +} + +#endif /* SWGMapActions_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGMapActions_find.cpp b/swagger/sdrangel/code/qt5/client/SWGMapActions_find.cpp new file mode 100644 index 000000000..0e9607141 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapActions_find.cpp @@ -0,0 +1,110 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGMapActions_find.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGMapActions_find::SWGMapActions_find(QString* json) { + init(); + this->fromJson(*json); +} + +SWGMapActions_find::SWGMapActions_find() { + id = nullptr; + m_id_isSet = false; +} + +SWGMapActions_find::~SWGMapActions_find() { + this->cleanup(); +} + +void +SWGMapActions_find::init() { + id = new QString(""); + m_id_isSet = false; +} + +void +SWGMapActions_find::cleanup() { + if(id != nullptr) { + delete id; + } +} + +SWGMapActions_find* +SWGMapActions_find::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGMapActions_find::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&id, pJson["id"], "QString", "QString"); + +} + +QString +SWGMapActions_find::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGMapActions_find::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(id != nullptr && *id != QString("")){ + toJsonValue(QString("id"), id, obj, QString("QString")); + } + + return obj; +} + +QString* +SWGMapActions_find::getId() { + return id; +} +void +SWGMapActions_find::setId(QString* id) { + this->id = id; + this->m_id_isSet = true; +} + + +bool +SWGMapActions_find::isSet(){ + bool isObjectUpdated = false; + do{ + if(id && *id != QString("")){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGMapActions_find.h b/swagger/sdrangel/code/qt5/client/SWGMapActions_find.h new file mode 100644 index 000000000..cce1988c8 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapActions_find.h @@ -0,0 +1,59 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGMapActions_find.h + * + * A request to centre the map on an item. + */ + +#ifndef SWGMapActions_find_H_ +#define SWGMapActions_find_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGMapActions_find: public SWGObject { +public: + SWGMapActions_find(); + SWGMapActions_find(QString* json); + virtual ~SWGMapActions_find(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGMapActions_find* fromJson(QString &jsonString) override; + + QString* getId(); + void setId(QString* id); + + + virtual bool isSet() override; + +private: + QString* id; + bool m_id_isSet; + +}; + +} + +#endif /* SWGMapActions_find_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp b/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp new file mode 100644 index 000000000..f390c6d01 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem.cpp @@ -0,0 +1,252 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGMapItem.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGMapItem::SWGMapItem(QString* json) { + init(); + this->fromJson(*json); +} + +SWGMapItem::SWGMapItem() { + name = nullptr; + m_name_isSet = false; + image = nullptr; + m_image_isSet = false; + image_rotation = 0; + m_image_rotation_isSet = false; + image_fixed_size = 0; + m_image_fixed_size_isSet = false; + text = nullptr; + m_text_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; +} + +SWGMapItem::~SWGMapItem() { + this->cleanup(); +} + +void +SWGMapItem::init() { + name = new QString(""); + m_name_isSet = false; + image = new QString(""); + m_image_isSet = false; + image_rotation = 0; + m_image_rotation_isSet = false; + image_fixed_size = 0; + m_image_fixed_size_isSet = false; + text = new QString(""); + m_text_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; +} + +void +SWGMapItem::cleanup() { + if(name != nullptr) { + delete name; + } + if(image != nullptr) { + delete image; + } + + + if(text != nullptr) { + delete text; + } + + +} + +SWGMapItem* +SWGMapItem::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGMapItem::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&image, pJson["image"], "QString", "QString"); + + ::SWGSDRangel::setValue(&image_rotation, pJson["imageRotation"], "qint32", ""); + + ::SWGSDRangel::setValue(&image_fixed_size, pJson["imageFixedSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&text, pJson["text"], "QString", "QString"); + + ::SWGSDRangel::setValue(&latitude, pJson["latitude"], "float", ""); + + ::SWGSDRangel::setValue(&longitude, pJson["longitude"], "float", ""); + +} + +QString +SWGMapItem::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGMapItem::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(image != nullptr && *image != QString("")){ + toJsonValue(QString("image"), image, obj, QString("QString")); + } + if(m_image_rotation_isSet){ + obj->insert("imageRotation", QJsonValue(image_rotation)); + } + if(m_image_fixed_size_isSet){ + obj->insert("imageFixedSize", QJsonValue(image_fixed_size)); + } + if(text != nullptr && *text != QString("")){ + toJsonValue(QString("text"), text, obj, QString("QString")); + } + if(m_latitude_isSet){ + obj->insert("latitude", QJsonValue(latitude)); + } + if(m_longitude_isSet){ + obj->insert("longitude", QJsonValue(longitude)); + } + + return obj; +} + +QString* +SWGMapItem::getName() { + return name; +} +void +SWGMapItem::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +QString* +SWGMapItem::getImage() { + return image; +} +void +SWGMapItem::setImage(QString* image) { + this->image = image; + this->m_image_isSet = true; +} + +qint32 +SWGMapItem::getImageRotation() { + return image_rotation; +} +void +SWGMapItem::setImageRotation(qint32 image_rotation) { + this->image_rotation = image_rotation; + this->m_image_rotation_isSet = true; +} + +qint32 +SWGMapItem::getImageFixedSize() { + return image_fixed_size; +} +void +SWGMapItem::setImageFixedSize(qint32 image_fixed_size) { + this->image_fixed_size = image_fixed_size; + this->m_image_fixed_size_isSet = true; +} + +QString* +SWGMapItem::getText() { + return text; +} +void +SWGMapItem::setText(QString* text) { + this->text = text; + this->m_text_isSet = true; +} + +float +SWGMapItem::getLatitude() { + return latitude; +} +void +SWGMapItem::setLatitude(float latitude) { + this->latitude = latitude; + this->m_latitude_isSet = true; +} + +float +SWGMapItem::getLongitude() { + return longitude; +} +void +SWGMapItem::setLongitude(float longitude) { + this->longitude = longitude; + this->m_longitude_isSet = true; +} + + +bool +SWGMapItem::isSet(){ + bool isObjectUpdated = false; + do{ + if(name && *name != QString("")){ + isObjectUpdated = true; break; + } + if(image && *image != QString("")){ + isObjectUpdated = true; break; + } + if(m_image_rotation_isSet){ + isObjectUpdated = true; break; + } + if(m_image_fixed_size_isSet){ + isObjectUpdated = true; break; + } + if(text && *text != QString("")){ + isObjectUpdated = true; break; + } + if(m_latitude_isSet){ + isObjectUpdated = true; break; + } + if(m_longitude_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem.h b/swagger/sdrangel/code/qt5/client/SWGMapItem.h new file mode 100644 index 000000000..f9c16eec3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem.h @@ -0,0 +1,95 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGMapItem.h + * + * An item to draw on the map. Set image to an empty string to remove item from the map. + */ + +#ifndef SWGMapItem_H_ +#define SWGMapItem_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGMapItem: public SWGObject { +public: + SWGMapItem(); + SWGMapItem(QString* json); + virtual ~SWGMapItem(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGMapItem* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + QString* getImage(); + void setImage(QString* image); + + qint32 getImageRotation(); + void setImageRotation(qint32 image_rotation); + + qint32 getImageFixedSize(); + void setImageFixedSize(qint32 image_fixed_size); + + QString* getText(); + void setText(QString* text); + + float getLatitude(); + void setLatitude(float latitude); + + float getLongitude(); + void setLongitude(float longitude); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + QString* image; + bool m_image_isSet; + + qint32 image_rotation; + bool m_image_rotation_isSet; + + qint32 image_fixed_size; + bool m_image_fixed_size_isSet; + + QString* text; + bool m_text_isSet; + + float latitude; + bool m_latitude_isSet; + + float longitude; + bool m_longitude_isSet; + +}; + +} + +#endif /* SWGMapItem_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp new file mode 100644 index 000000000..d788fee5a --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.cpp @@ -0,0 +1,252 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGMapItem_2.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGMapItem_2::SWGMapItem_2(QString* json) { + init(); + this->fromJson(*json); +} + +SWGMapItem_2::SWGMapItem_2() { + name = nullptr; + m_name_isSet = false; + image = nullptr; + m_image_isSet = false; + image_rotation = 0; + m_image_rotation_isSet = false; + image_fixed_size = 0; + m_image_fixed_size_isSet = false; + text = nullptr; + m_text_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; +} + +SWGMapItem_2::~SWGMapItem_2() { + this->cleanup(); +} + +void +SWGMapItem_2::init() { + name = new QString(""); + m_name_isSet = false; + image = new QString(""); + m_image_isSet = false; + image_rotation = 0; + m_image_rotation_isSet = false; + image_fixed_size = 0; + m_image_fixed_size_isSet = false; + text = new QString(""); + m_text_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; +} + +void +SWGMapItem_2::cleanup() { + if(name != nullptr) { + delete name; + } + if(image != nullptr) { + delete image; + } + + + if(text != nullptr) { + delete text; + } + + +} + +SWGMapItem_2* +SWGMapItem_2::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGMapItem_2::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&image, pJson["image"], "QString", "QString"); + + ::SWGSDRangel::setValue(&image_rotation, pJson["imageRotation"], "qint32", ""); + + ::SWGSDRangel::setValue(&image_fixed_size, pJson["imageFixedSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&text, pJson["text"], "QString", "QString"); + + ::SWGSDRangel::setValue(&latitude, pJson["latitude"], "float", ""); + + ::SWGSDRangel::setValue(&longitude, pJson["longitude"], "float", ""); + +} + +QString +SWGMapItem_2::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGMapItem_2::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(image != nullptr && *image != QString("")){ + toJsonValue(QString("image"), image, obj, QString("QString")); + } + if(m_image_rotation_isSet){ + obj->insert("imageRotation", QJsonValue(image_rotation)); + } + if(m_image_fixed_size_isSet){ + obj->insert("imageFixedSize", QJsonValue(image_fixed_size)); + } + if(text != nullptr && *text != QString("")){ + toJsonValue(QString("text"), text, obj, QString("QString")); + } + if(m_latitude_isSet){ + obj->insert("latitude", QJsonValue(latitude)); + } + if(m_longitude_isSet){ + obj->insert("longitude", QJsonValue(longitude)); + } + + return obj; +} + +QString* +SWGMapItem_2::getName() { + return name; +} +void +SWGMapItem_2::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +QString* +SWGMapItem_2::getImage() { + return image; +} +void +SWGMapItem_2::setImage(QString* image) { + this->image = image; + this->m_image_isSet = true; +} + +qint32 +SWGMapItem_2::getImageRotation() { + return image_rotation; +} +void +SWGMapItem_2::setImageRotation(qint32 image_rotation) { + this->image_rotation = image_rotation; + this->m_image_rotation_isSet = true; +} + +qint32 +SWGMapItem_2::getImageFixedSize() { + return image_fixed_size; +} +void +SWGMapItem_2::setImageFixedSize(qint32 image_fixed_size) { + this->image_fixed_size = image_fixed_size; + this->m_image_fixed_size_isSet = true; +} + +QString* +SWGMapItem_2::getText() { + return text; +} +void +SWGMapItem_2::setText(QString* text) { + this->text = text; + this->m_text_isSet = true; +} + +float +SWGMapItem_2::getLatitude() { + return latitude; +} +void +SWGMapItem_2::setLatitude(float latitude) { + this->latitude = latitude; + this->m_latitude_isSet = true; +} + +float +SWGMapItem_2::getLongitude() { + return longitude; +} +void +SWGMapItem_2::setLongitude(float longitude) { + this->longitude = longitude; + this->m_longitude_isSet = true; +} + + +bool +SWGMapItem_2::isSet(){ + bool isObjectUpdated = false; + do{ + if(name && *name != QString("")){ + isObjectUpdated = true; break; + } + if(image && *image != QString("")){ + isObjectUpdated = true; break; + } + if(m_image_rotation_isSet){ + isObjectUpdated = true; break; + } + if(m_image_fixed_size_isSet){ + isObjectUpdated = true; break; + } + if(text && *text != QString("")){ + isObjectUpdated = true; break; + } + if(m_latitude_isSet){ + isObjectUpdated = true; break; + } + if(m_longitude_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h new file mode 100644 index 000000000..67414fe28 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapItem_2.h @@ -0,0 +1,95 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGMapItem_2.h + * + * An item to draw on the map. Set image to an empty string to remove item from the map. + */ + +#ifndef SWGMapItem_2_H_ +#define SWGMapItem_2_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGMapItem_2: public SWGObject { +public: + SWGMapItem_2(); + SWGMapItem_2(QString* json); + virtual ~SWGMapItem_2(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGMapItem_2* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + QString* getImage(); + void setImage(QString* image); + + qint32 getImageRotation(); + void setImageRotation(qint32 image_rotation); + + qint32 getImageFixedSize(); + void setImageFixedSize(qint32 image_fixed_size); + + QString* getText(); + void setText(QString* text); + + float getLatitude(); + void setLatitude(float latitude); + + float getLongitude(); + void setLongitude(float longitude); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + QString* image; + bool m_image_isSet; + + qint32 image_rotation; + bool m_image_rotation_isSet; + + qint32 image_fixed_size; + bool m_image_fixed_size_isSet; + + QString* text; + bool m_text_isSet; + + float latitude; + bool m_latitude_isSet; + + float longitude; + bool m_longitude_isSet; + +}; + +} + +#endif /* SWGMapItem_2_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGMapSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGMapSettings.cpp new file mode 100644 index 000000000..e4c686e73 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapSettings.cpp @@ -0,0 +1,273 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGMapSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGMapSettings::SWGMapSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGMapSettings::SWGMapSettings() { + display_names = 0; + m_display_names_isSet = false; + title = nullptr; + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGMapSettings::~SWGMapSettings() { + this->cleanup(); +} + +void +SWGMapSettings::init() { + display_names = 0; + m_display_names_isSet = false; + title = new QString(""); + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGMapSettings::cleanup() { + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGMapSettings* +SWGMapSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGMapSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&display_names, pJson["displayNames"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGMapSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGMapSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_display_names_isSet){ + obj->insert("displayNames", QJsonValue(display_names)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +qint32 +SWGMapSettings::getDisplayNames() { + return display_names; +} +void +SWGMapSettings::setDisplayNames(qint32 display_names) { + this->display_names = display_names; + this->m_display_names_isSet = true; +} + +QString* +SWGMapSettings::getTitle() { + return title; +} +void +SWGMapSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGMapSettings::getRgbColor() { + return rgb_color; +} +void +SWGMapSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +qint32 +SWGMapSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGMapSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGMapSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGMapSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGMapSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGMapSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGMapSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGMapSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGMapSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGMapSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGMapSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_display_names_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGMapSettings.h b/swagger/sdrangel/code/qt5/client/SWGMapSettings.h new file mode 100644 index 000000000..4a0b77869 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGMapSettings.h @@ -0,0 +1,101 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGMapSettings.h + * + * Map + */ + +#ifndef SWGMapSettings_H_ +#define SWGMapSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGMapSettings: public SWGObject { +public: + SWGMapSettings(); + SWGMapSettings(QString* json); + virtual ~SWGMapSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGMapSettings* fromJson(QString &jsonString) override; + + qint32 getDisplayNames(); + void setDisplayNames(qint32 display_names); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + qint32 display_names; + bool m_display_names_isSet; + + QString* title; + bool m_title_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGMapSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index a14ea8262..301a8ddd5 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -25,6 +25,7 @@ #include "SWGAMDemodSettings.h" #include "SWGAMModReport.h" #include "SWGAMModSettings.h" +#include "SWGAPRSSettings.h" #include "SWGATVDemodSettings.h" #include "SWGATVModReport.h" #include "SWGATVModSettings.h" @@ -144,12 +145,17 @@ #include "SWGLocalSourceSettings.h" #include "SWGLocationInformation.h" #include "SWGLoggingInfo.h" +#include "SWGMapActions.h" +#include "SWGMapItem.h" +#include "SWGMapItem_2.h" +#include "SWGMapSettings.h" #include "SWGMetisMISOSettings.h" #include "SWGNFMDemodReport.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModReport.h" #include "SWGNFMModSettings.h" #include "SWGNamedEnum.h" +#include "SWGPacketDemodSettings.h" #include "SWGPacketModActions.h" #include "SWGPacketModActions_tx.h" #include "SWGPacketModReport.h" @@ -207,7 +213,9 @@ #include "SWGSoapySDRReport.h" #include "SWGSpectrumServer.h" #include "SWGSpectrumServer_clients.h" +#include "SWGStarTrackerSettings.h" #include "SWGSuccessResponse.h" +#include "SWGTargetAzimuthElevation.h" #include "SWGTestMISettings.h" #include "SWGTestMOSyncSettings.h" #include "SWGTestMiStreamSettings.h" @@ -274,6 +282,9 @@ namespace SWGSDRangel { if(QString("SWGAMModSettings").compare(type) == 0) { return new SWGAMModSettings(); } + if(QString("SWGAPRSSettings").compare(type) == 0) { + return new SWGAPRSSettings(); + } if(QString("SWGATVDemodSettings").compare(type) == 0) { return new SWGATVDemodSettings(); } @@ -631,6 +642,18 @@ namespace SWGSDRangel { if(QString("SWGLoggingInfo").compare(type) == 0) { return new SWGLoggingInfo(); } + if(QString("SWGMapActions").compare(type) == 0) { + return new SWGMapActions(); + } + if(QString("SWGMapItem").compare(type) == 0) { + return new SWGMapItem(); + } + if(QString("SWGMapItem_2").compare(type) == 0) { + return new SWGMapItem_2(); + } + if(QString("SWGMapSettings").compare(type) == 0) { + return new SWGMapSettings(); + } if(QString("SWGMetisMISOSettings").compare(type) == 0) { return new SWGMetisMISOSettings(); } @@ -649,6 +672,9 @@ namespace SWGSDRangel { if(QString("SWGNamedEnum").compare(type) == 0) { return new SWGNamedEnum(); } + if(QString("SWGPacketDemodSettings").compare(type) == 0) { + return new SWGPacketDemodSettings(); + } if(QString("SWGPacketModActions").compare(type) == 0) { return new SWGPacketModActions(); } @@ -820,9 +846,15 @@ namespace SWGSDRangel { if(QString("SWGSpectrumServer_clients").compare(type) == 0) { return new SWGSpectrumServer_clients(); } + if(QString("SWGStarTrackerSettings").compare(type) == 0) { + return new SWGStarTrackerSettings(); + } if(QString("SWGSuccessResponse").compare(type) == 0) { return new SWGSuccessResponse(); } + if(QString("SWGTargetAzimuthElevation").compare(type) == 0) { + return new SWGTargetAzimuthElevation(); + } if(QString("SWGTestMISettings").compare(type) == 0) { return new SWGTestMISettings(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.cpp new file mode 100644 index 000000000..0e6a75bc3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.cpp @@ -0,0 +1,367 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPacketDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPacketDemodSettings::SWGPacketDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPacketDemodSettings::SWGPacketDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + mode = nullptr; + m_mode_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGPacketDemodSettings::~SWGPacketDemodSettings() { + this->cleanup(); +} + +void +SWGPacketDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + mode = new QString(""); + m_mode_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGPacketDemodSettings::cleanup() { + + if(mode != nullptr) { + delete mode; + } + + + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGPacketDemodSettings* +SWGPacketDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPacketDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&mode, pJson["mode"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "float", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGPacketDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPacketDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(mode != nullptr && *mode != QString("")){ + toJsonValue(QString("mode"), mode, obj, QString("QString")); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_stream_index_isSet){ + obj->insert("streamIndex", QJsonValue(stream_index)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +qint64 +SWGPacketDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGPacketDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +QString* +SWGPacketDemodSettings::getMode() { + return mode; +} +void +SWGPacketDemodSettings::setMode(QString* mode) { + this->mode = mode; + this->m_mode_isSet = true; +} + +float +SWGPacketDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGPacketDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGPacketDemodSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGPacketDemodSettings::setFmDeviation(float fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGPacketDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGPacketDemodSettings::getTitle() { + return title; +} +void +SWGPacketDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getStreamIndex() { + return stream_index; +} +void +SWGPacketDemodSettings::setStreamIndex(qint32 stream_index) { + this->stream_index = stream_index; + this->m_stream_index_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGPacketDemodSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGPacketDemodSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGPacketDemodSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGPacketDemodSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGPacketDemodSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGPacketDemodSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGPacketDemodSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGPacketDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ + isObjectUpdated = true; break; + } + if(mode && *mode != QString("")){ + isObjectUpdated = true; break; + } + if(m_rf_bandwidth_isSet){ + isObjectUpdated = true; break; + } + if(m_fm_deviation_isSet){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_stream_index_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.h new file mode 100644 index 000000000..d7bab93dd --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPacketDemodSettings.h @@ -0,0 +1,125 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPacketDemodSettings.h + * + * PacketDemod + */ + +#ifndef SWGPacketDemodSettings_H_ +#define SWGPacketDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPacketDemodSettings: public SWGObject { +public: + SWGPacketDemodSettings(); + SWGPacketDemodSettings(QString* json); + virtual ~SWGPacketDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPacketDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + QString* getMode(); + void setMode(QString* mode); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getFmDeviation(); + void setFmDeviation(float fm_deviation); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getStreamIndex(); + void setStreamIndex(qint32 stream_index); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + QString* mode; + bool m_mode_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float fm_deviation; + bool m_fm_deviation_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + qint32 stream_index; + bool m_stream_index_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGPacketDemodSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.cpp new file mode 100644 index 000000000..43beb03a0 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.cpp @@ -0,0 +1,722 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGStarTrackerSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGStarTrackerSettings::SWGStarTrackerSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGStarTrackerSettings::SWGStarTrackerSettings() { + target = nullptr; + m_target_isSet = false; + ra = nullptr; + m_ra_isSet = false; + dec = nullptr; + m_dec_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; + date_time = nullptr; + m_date_time_isSet = false; + refraction = nullptr; + m_refraction_isSet = false; + pressure = 0.0f; + m_pressure_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; + humidity = 0.0f; + m_humidity_isSet = false; + height_above_sea_level = 0.0f; + m_height_above_sea_level_isSet = false; + temperature_lapse_rate = 0.0f; + m_temperature_lapse_rate_isSet = false; + frequency = 0.0f; + m_frequency_isSet = false; + stellarium_server_enabled = 0; + m_stellarium_server_enabled_isSet = false; + stellarium_port = 0; + m_stellarium_port_isSet = false; + update_period = 0.0f; + m_update_period_isSet = false; + epoch = nullptr; + m_epoch_isSet = false; + draw_sun_on_map = 0; + m_draw_sun_on_map_isSet = false; + draw_moon_on_map = 0; + m_draw_moon_on_map_isSet = false; + draw_star_on_map = 0; + m_draw_star_on_map_isSet = false; + title = nullptr; + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGStarTrackerSettings::~SWGStarTrackerSettings() { + this->cleanup(); +} + +void +SWGStarTrackerSettings::init() { + target = new QString(""); + m_target_isSet = false; + ra = new QString(""); + m_ra_isSet = false; + dec = new QString(""); + m_dec_isSet = false; + latitude = 0.0f; + m_latitude_isSet = false; + longitude = 0.0f; + m_longitude_isSet = false; + date_time = new QString(""); + m_date_time_isSet = false; + refraction = new QString(""); + m_refraction_isSet = false; + pressure = 0.0f; + m_pressure_isSet = false; + temperature = 0.0f; + m_temperature_isSet = false; + humidity = 0.0f; + m_humidity_isSet = false; + height_above_sea_level = 0.0f; + m_height_above_sea_level_isSet = false; + temperature_lapse_rate = 0.0f; + m_temperature_lapse_rate_isSet = false; + frequency = 0.0f; + m_frequency_isSet = false; + stellarium_server_enabled = 0; + m_stellarium_server_enabled_isSet = false; + stellarium_port = 0; + m_stellarium_port_isSet = false; + update_period = 0.0f; + m_update_period_isSet = false; + epoch = new QString(""); + m_epoch_isSet = false; + draw_sun_on_map = 0; + m_draw_sun_on_map_isSet = false; + draw_moon_on_map = 0; + m_draw_moon_on_map_isSet = false; + draw_star_on_map = 0; + m_draw_star_on_map_isSet = false; + title = new QString(""); + m_title_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGStarTrackerSettings::cleanup() { + if(target != nullptr) { + delete target; + } + if(ra != nullptr) { + delete ra; + } + if(dec != nullptr) { + delete dec; + } + + + if(date_time != nullptr) { + delete date_time; + } + if(refraction != nullptr) { + delete refraction; + } + + + + + + + + + + if(epoch != nullptr) { + delete epoch; + } + + + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGStarTrackerSettings* +SWGStarTrackerSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGStarTrackerSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&target, pJson["target"], "QString", "QString"); + + ::SWGSDRangel::setValue(&ra, pJson["ra"], "QString", "QString"); + + ::SWGSDRangel::setValue(&dec, pJson["dec"], "QString", "QString"); + + ::SWGSDRangel::setValue(&latitude, pJson["latitude"], "float", ""); + + ::SWGSDRangel::setValue(&longitude, pJson["longitude"], "float", ""); + + ::SWGSDRangel::setValue(&date_time, pJson["dateTime"], "QString", "QString"); + + ::SWGSDRangel::setValue(&refraction, pJson["refraction"], "QString", "QString"); + + ::SWGSDRangel::setValue(&pressure, pJson["pressure"], "float", ""); + + ::SWGSDRangel::setValue(&temperature, pJson["temperature"], "float", ""); + + ::SWGSDRangel::setValue(&humidity, pJson["humidity"], "float", ""); + + ::SWGSDRangel::setValue(&height_above_sea_level, pJson["heightAboveSeaLevel"], "float", ""); + + ::SWGSDRangel::setValue(&temperature_lapse_rate, pJson["temperatureLapseRate"], "float", ""); + + ::SWGSDRangel::setValue(&frequency, pJson["frequency"], "float", ""); + + ::SWGSDRangel::setValue(&stellarium_server_enabled, pJson["stellariumServerEnabled"], "qint32", ""); + + ::SWGSDRangel::setValue(&stellarium_port, pJson["stellariumPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&update_period, pJson["updatePeriod"], "float", ""); + + ::SWGSDRangel::setValue(&epoch, pJson["epoch"], "QString", "QString"); + + ::SWGSDRangel::setValue(&draw_sun_on_map, pJson["drawSunOnMap"], "qint32", ""); + + ::SWGSDRangel::setValue(&draw_moon_on_map, pJson["drawMoonOnMap"], "qint32", ""); + + ::SWGSDRangel::setValue(&draw_star_on_map, pJson["drawStarOnMap"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGStarTrackerSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGStarTrackerSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(target != nullptr && *target != QString("")){ + toJsonValue(QString("target"), target, obj, QString("QString")); + } + if(ra != nullptr && *ra != QString("")){ + toJsonValue(QString("ra"), ra, obj, QString("QString")); + } + if(dec != nullptr && *dec != QString("")){ + toJsonValue(QString("dec"), dec, obj, QString("QString")); + } + if(m_latitude_isSet){ + obj->insert("latitude", QJsonValue(latitude)); + } + if(m_longitude_isSet){ + obj->insert("longitude", QJsonValue(longitude)); + } + if(date_time != nullptr && *date_time != QString("")){ + toJsonValue(QString("dateTime"), date_time, obj, QString("QString")); + } + if(refraction != nullptr && *refraction != QString("")){ + toJsonValue(QString("refraction"), refraction, obj, QString("QString")); + } + if(m_pressure_isSet){ + obj->insert("pressure", QJsonValue(pressure)); + } + if(m_temperature_isSet){ + obj->insert("temperature", QJsonValue(temperature)); + } + if(m_humidity_isSet){ + obj->insert("humidity", QJsonValue(humidity)); + } + if(m_height_above_sea_level_isSet){ + obj->insert("heightAboveSeaLevel", QJsonValue(height_above_sea_level)); + } + if(m_temperature_lapse_rate_isSet){ + obj->insert("temperatureLapseRate", QJsonValue(temperature_lapse_rate)); + } + if(m_frequency_isSet){ + obj->insert("frequency", QJsonValue(frequency)); + } + if(m_stellarium_server_enabled_isSet){ + obj->insert("stellariumServerEnabled", QJsonValue(stellarium_server_enabled)); + } + if(m_stellarium_port_isSet){ + obj->insert("stellariumPort", QJsonValue(stellarium_port)); + } + if(m_update_period_isSet){ + obj->insert("updatePeriod", QJsonValue(update_period)); + } + if(epoch != nullptr && *epoch != QString("")){ + toJsonValue(QString("epoch"), epoch, obj, QString("QString")); + } + if(m_draw_sun_on_map_isSet){ + obj->insert("drawSunOnMap", QJsonValue(draw_sun_on_map)); + } + if(m_draw_moon_on_map_isSet){ + obj->insert("drawMoonOnMap", QJsonValue(draw_moon_on_map)); + } + if(m_draw_star_on_map_isSet){ + obj->insert("drawStarOnMap", QJsonValue(draw_star_on_map)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +QString* +SWGStarTrackerSettings::getTarget() { + return target; +} +void +SWGStarTrackerSettings::setTarget(QString* target) { + this->target = target; + this->m_target_isSet = true; +} + +QString* +SWGStarTrackerSettings::getRa() { + return ra; +} +void +SWGStarTrackerSettings::setRa(QString* ra) { + this->ra = ra; + this->m_ra_isSet = true; +} + +QString* +SWGStarTrackerSettings::getDec() { + return dec; +} +void +SWGStarTrackerSettings::setDec(QString* dec) { + this->dec = dec; + this->m_dec_isSet = true; +} + +float +SWGStarTrackerSettings::getLatitude() { + return latitude; +} +void +SWGStarTrackerSettings::setLatitude(float latitude) { + this->latitude = latitude; + this->m_latitude_isSet = true; +} + +float +SWGStarTrackerSettings::getLongitude() { + return longitude; +} +void +SWGStarTrackerSettings::setLongitude(float longitude) { + this->longitude = longitude; + this->m_longitude_isSet = true; +} + +QString* +SWGStarTrackerSettings::getDateTime() { + return date_time; +} +void +SWGStarTrackerSettings::setDateTime(QString* date_time) { + this->date_time = date_time; + this->m_date_time_isSet = true; +} + +QString* +SWGStarTrackerSettings::getRefraction() { + return refraction; +} +void +SWGStarTrackerSettings::setRefraction(QString* refraction) { + this->refraction = refraction; + this->m_refraction_isSet = true; +} + +float +SWGStarTrackerSettings::getPressure() { + return pressure; +} +void +SWGStarTrackerSettings::setPressure(float pressure) { + this->pressure = pressure; + this->m_pressure_isSet = true; +} + +float +SWGStarTrackerSettings::getTemperature() { + return temperature; +} +void +SWGStarTrackerSettings::setTemperature(float temperature) { + this->temperature = temperature; + this->m_temperature_isSet = true; +} + +float +SWGStarTrackerSettings::getHumidity() { + return humidity; +} +void +SWGStarTrackerSettings::setHumidity(float humidity) { + this->humidity = humidity; + this->m_humidity_isSet = true; +} + +float +SWGStarTrackerSettings::getHeightAboveSeaLevel() { + return height_above_sea_level; +} +void +SWGStarTrackerSettings::setHeightAboveSeaLevel(float height_above_sea_level) { + this->height_above_sea_level = height_above_sea_level; + this->m_height_above_sea_level_isSet = true; +} + +float +SWGStarTrackerSettings::getTemperatureLapseRate() { + return temperature_lapse_rate; +} +void +SWGStarTrackerSettings::setTemperatureLapseRate(float temperature_lapse_rate) { + this->temperature_lapse_rate = temperature_lapse_rate; + this->m_temperature_lapse_rate_isSet = true; +} + +float +SWGStarTrackerSettings::getFrequency() { + return frequency; +} +void +SWGStarTrackerSettings::setFrequency(float frequency) { + this->frequency = frequency; + this->m_frequency_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getStellariumServerEnabled() { + return stellarium_server_enabled; +} +void +SWGStarTrackerSettings::setStellariumServerEnabled(qint32 stellarium_server_enabled) { + this->stellarium_server_enabled = stellarium_server_enabled; + this->m_stellarium_server_enabled_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getStellariumPort() { + return stellarium_port; +} +void +SWGStarTrackerSettings::setStellariumPort(qint32 stellarium_port) { + this->stellarium_port = stellarium_port; + this->m_stellarium_port_isSet = true; +} + +float +SWGStarTrackerSettings::getUpdatePeriod() { + return update_period; +} +void +SWGStarTrackerSettings::setUpdatePeriod(float update_period) { + this->update_period = update_period; + this->m_update_period_isSet = true; +} + +QString* +SWGStarTrackerSettings::getEpoch() { + return epoch; +} +void +SWGStarTrackerSettings::setEpoch(QString* epoch) { + this->epoch = epoch; + this->m_epoch_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getDrawSunOnMap() { + return draw_sun_on_map; +} +void +SWGStarTrackerSettings::setDrawSunOnMap(qint32 draw_sun_on_map) { + this->draw_sun_on_map = draw_sun_on_map; + this->m_draw_sun_on_map_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getDrawMoonOnMap() { + return draw_moon_on_map; +} +void +SWGStarTrackerSettings::setDrawMoonOnMap(qint32 draw_moon_on_map) { + this->draw_moon_on_map = draw_moon_on_map; + this->m_draw_moon_on_map_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getDrawStarOnMap() { + return draw_star_on_map; +} +void +SWGStarTrackerSettings::setDrawStarOnMap(qint32 draw_star_on_map) { + this->draw_star_on_map = draw_star_on_map; + this->m_draw_star_on_map_isSet = true; +} + +QString* +SWGStarTrackerSettings::getTitle() { + return title; +} +void +SWGStarTrackerSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getRgbColor() { + return rgb_color; +} +void +SWGStarTrackerSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGStarTrackerSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGStarTrackerSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGStarTrackerSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGStarTrackerSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGStarTrackerSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGStarTrackerSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGStarTrackerSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGStarTrackerSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(target && *target != QString("")){ + isObjectUpdated = true; break; + } + if(ra && *ra != QString("")){ + isObjectUpdated = true; break; + } + if(dec && *dec != QString("")){ + isObjectUpdated = true; break; + } + if(m_latitude_isSet){ + isObjectUpdated = true; break; + } + if(m_longitude_isSet){ + isObjectUpdated = true; break; + } + if(date_time && *date_time != QString("")){ + isObjectUpdated = true; break; + } + if(refraction && *refraction != QString("")){ + isObjectUpdated = true; break; + } + if(m_pressure_isSet){ + isObjectUpdated = true; break; + } + if(m_temperature_isSet){ + isObjectUpdated = true; break; + } + if(m_humidity_isSet){ + isObjectUpdated = true; break; + } + if(m_height_above_sea_level_isSet){ + isObjectUpdated = true; break; + } + if(m_temperature_lapse_rate_isSet){ + isObjectUpdated = true; break; + } + if(m_frequency_isSet){ + isObjectUpdated = true; break; + } + if(m_stellarium_server_enabled_isSet){ + isObjectUpdated = true; break; + } + if(m_stellarium_port_isSet){ + isObjectUpdated = true; break; + } + if(m_update_period_isSet){ + isObjectUpdated = true; break; + } + if(epoch && *epoch != QString("")){ + isObjectUpdated = true; break; + } + if(m_draw_sun_on_map_isSet){ + isObjectUpdated = true; break; + } + if(m_draw_moon_on_map_isSet){ + isObjectUpdated = true; break; + } + if(m_draw_star_on_map_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.h b/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.h new file mode 100644 index 000000000..ef414b10f --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGStarTrackerSettings.h @@ -0,0 +1,215 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGStarTrackerSettings.h + * + * Star Tracker settings + */ + +#ifndef SWGStarTrackerSettings_H_ +#define SWGStarTrackerSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGStarTrackerSettings: public SWGObject { +public: + SWGStarTrackerSettings(); + SWGStarTrackerSettings(QString* json); + virtual ~SWGStarTrackerSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGStarTrackerSettings* fromJson(QString &jsonString) override; + + QString* getTarget(); + void setTarget(QString* target); + + QString* getRa(); + void setRa(QString* ra); + + QString* getDec(); + void setDec(QString* dec); + + float getLatitude(); + void setLatitude(float latitude); + + float getLongitude(); + void setLongitude(float longitude); + + QString* getDateTime(); + void setDateTime(QString* date_time); + + QString* getRefraction(); + void setRefraction(QString* refraction); + + float getPressure(); + void setPressure(float pressure); + + float getTemperature(); + void setTemperature(float temperature); + + float getHumidity(); + void setHumidity(float humidity); + + float getHeightAboveSeaLevel(); + void setHeightAboveSeaLevel(float height_above_sea_level); + + float getTemperatureLapseRate(); + void setTemperatureLapseRate(float temperature_lapse_rate); + + float getFrequency(); + void setFrequency(float frequency); + + qint32 getStellariumServerEnabled(); + void setStellariumServerEnabled(qint32 stellarium_server_enabled); + + qint32 getStellariumPort(); + void setStellariumPort(qint32 stellarium_port); + + float getUpdatePeriod(); + void setUpdatePeriod(float update_period); + + QString* getEpoch(); + void setEpoch(QString* epoch); + + qint32 getDrawSunOnMap(); + void setDrawSunOnMap(qint32 draw_sun_on_map); + + qint32 getDrawMoonOnMap(); + void setDrawMoonOnMap(qint32 draw_moon_on_map); + + qint32 getDrawStarOnMap(); + void setDrawStarOnMap(qint32 draw_star_on_map); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + QString* target; + bool m_target_isSet; + + QString* ra; + bool m_ra_isSet; + + QString* dec; + bool m_dec_isSet; + + float latitude; + bool m_latitude_isSet; + + float longitude; + bool m_longitude_isSet; + + QString* date_time; + bool m_date_time_isSet; + + QString* refraction; + bool m_refraction_isSet; + + float pressure; + bool m_pressure_isSet; + + float temperature; + bool m_temperature_isSet; + + float humidity; + bool m_humidity_isSet; + + float height_above_sea_level; + bool m_height_above_sea_level_isSet; + + float temperature_lapse_rate; + bool m_temperature_lapse_rate_isSet; + + float frequency; + bool m_frequency_isSet; + + qint32 stellarium_server_enabled; + bool m_stellarium_server_enabled_isSet; + + qint32 stellarium_port; + bool m_stellarium_port_isSet; + + float update_period; + bool m_update_period_isSet; + + QString* epoch; + bool m_epoch_isSet; + + qint32 draw_sun_on_map; + bool m_draw_sun_on_map_isSet; + + qint32 draw_moon_on_map; + bool m_draw_moon_on_map_isSet; + + qint32 draw_star_on_map; + bool m_draw_star_on_map_isSet; + + QString* title; + bool m_title_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGStarTrackerSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.cpp b/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.cpp new file mode 100644 index 000000000..54b94a1c3 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.cpp @@ -0,0 +1,156 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGTargetAzimuthElevation.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGTargetAzimuthElevation::SWGTargetAzimuthElevation(QString* json) { + init(); + this->fromJson(*json); +} + +SWGTargetAzimuthElevation::SWGTargetAzimuthElevation() { + name = nullptr; + m_name_isSet = false; + azimuth = 0.0f; + m_azimuth_isSet = false; + elevation = 0.0f; + m_elevation_isSet = false; +} + +SWGTargetAzimuthElevation::~SWGTargetAzimuthElevation() { + this->cleanup(); +} + +void +SWGTargetAzimuthElevation::init() { + name = new QString(""); + m_name_isSet = false; + azimuth = 0.0f; + m_azimuth_isSet = false; + elevation = 0.0f; + m_elevation_isSet = false; +} + +void +SWGTargetAzimuthElevation::cleanup() { + if(name != nullptr) { + delete name; + } + + +} + +SWGTargetAzimuthElevation* +SWGTargetAzimuthElevation::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGTargetAzimuthElevation::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&name, pJson["name"], "QString", "QString"); + + ::SWGSDRangel::setValue(&azimuth, pJson["azimuth"], "float", ""); + + ::SWGSDRangel::setValue(&elevation, pJson["elevation"], "float", ""); + +} + +QString +SWGTargetAzimuthElevation::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGTargetAzimuthElevation::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(name != nullptr && *name != QString("")){ + toJsonValue(QString("name"), name, obj, QString("QString")); + } + if(m_azimuth_isSet){ + obj->insert("azimuth", QJsonValue(azimuth)); + } + if(m_elevation_isSet){ + obj->insert("elevation", QJsonValue(elevation)); + } + + return obj; +} + +QString* +SWGTargetAzimuthElevation::getName() { + return name; +} +void +SWGTargetAzimuthElevation::setName(QString* name) { + this->name = name; + this->m_name_isSet = true; +} + +float +SWGTargetAzimuthElevation::getAzimuth() { + return azimuth; +} +void +SWGTargetAzimuthElevation::setAzimuth(float azimuth) { + this->azimuth = azimuth; + this->m_azimuth_isSet = true; +} + +float +SWGTargetAzimuthElevation::getElevation() { + return elevation; +} +void +SWGTargetAzimuthElevation::setElevation(float elevation) { + this->elevation = elevation; + this->m_elevation_isSet = true; +} + + +bool +SWGTargetAzimuthElevation::isSet(){ + bool isObjectUpdated = false; + do{ + if(name && *name != QString("")){ + isObjectUpdated = true; break; + } + if(m_azimuth_isSet){ + isObjectUpdated = true; break; + } + if(m_elevation_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.h b/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.h new file mode 100644 index 000000000..4c6b48656 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGTargetAzimuthElevation.h @@ -0,0 +1,71 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGTargetAzimuthElevation.h + * + * A target azimuth and elevation + */ + +#ifndef SWGTargetAzimuthElevation_H_ +#define SWGTargetAzimuthElevation_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGTargetAzimuthElevation: public SWGObject { +public: + SWGTargetAzimuthElevation(); + SWGTargetAzimuthElevation(QString* json); + virtual ~SWGTargetAzimuthElevation(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGTargetAzimuthElevation* fromJson(QString &jsonString) override; + + QString* getName(); + void setName(QString* name); + + float getAzimuth(); + void setAzimuth(float azimuth); + + float getElevation(); + void setElevation(float elevation); + + + virtual bool isSet() override; + +private: + QString* name; + bool m_name_isSet; + + float azimuth; + bool m_azimuth_isSet; + + float elevation; + bool m_elevation_isSet; + +}; + +} + +#endif /* SWGTargetAzimuthElevation_H_ */