diff --git a/doc/img/RadioAstronomy_Calibration.png b/doc/img/RadioAstronomy_Calibration.png new file mode 100644 index 000000000..d60d2dd5c Binary files /dev/null and b/doc/img/RadioAstronomy_Calibration.png differ diff --git a/doc/img/RadioAstronomy_CalibrationSettings.png b/doc/img/RadioAstronomy_CalibrationSettings.png new file mode 100644 index 000000000..66a96adc2 Binary files /dev/null and b/doc/img/RadioAstronomy_CalibrationSettings.png differ diff --git a/doc/img/RadioAstronomy_DistanceToHICloud.png b/doc/img/RadioAstronomy_DistanceToHICloud.png new file mode 100644 index 000000000..8e6377b2e Binary files /dev/null and b/doc/img/RadioAstronomy_DistanceToHICloud.png differ diff --git a/doc/img/RadioAstronomy_LAB.png b/doc/img/RadioAstronomy_LAB.png new file mode 100644 index 000000000..afd7f50d9 Binary files /dev/null and b/doc/img/RadioAstronomy_LAB.png differ diff --git a/doc/img/RadioAstronomy_Radiometer.png b/doc/img/RadioAstronomy_Radiometer.png new file mode 100644 index 000000000..4877d196e Binary files /dev/null and b/doc/img/RadioAstronomy_Radiometer.png differ diff --git a/doc/img/RadioAstronomy_Radiometer2D.png b/doc/img/RadioAstronomy_Radiometer2D.png new file mode 100644 index 000000000..e0c80f2c1 Binary files /dev/null and b/doc/img/RadioAstronomy_Radiometer2D.png differ diff --git a/doc/img/RadioAstronomy_RadiometerData.png b/doc/img/RadioAstronomy_RadiometerData.png new file mode 100644 index 000000000..fa54cde1e Binary files /dev/null and b/doc/img/RadioAstronomy_RadiometerData.png differ diff --git a/doc/img/RadioAstronomy_RadiometerGaussian.png b/doc/img/RadioAstronomy_RadiometerGaussian.png new file mode 100644 index 000000000..0bac01328 Binary files /dev/null and b/doc/img/RadioAstronomy_RadiometerGaussian.png differ diff --git a/doc/img/RadioAstronomy_RefLine.png b/doc/img/RadioAstronomy_RefLine.png new file mode 100644 index 000000000..5854cb0ce Binary files /dev/null and b/doc/img/RadioAstronomy_RefLine.png differ diff --git a/doc/img/RadioAstronomy_RunControl.png b/doc/img/RadioAstronomy_RunControl.png new file mode 100644 index 000000000..8b361da01 Binary files /dev/null and b/doc/img/RadioAstronomy_RunControl.png differ diff --git a/doc/img/RadioAstronomy_SensorSettings.png b/doc/img/RadioAstronomy_SensorSettings.png new file mode 100644 index 000000000..38c2dc10f Binary files /dev/null and b/doc/img/RadioAstronomy_SensorSettings.png differ diff --git a/doc/img/RadioAstronomy_Settings.png b/doc/img/RadioAstronomy_Settings.png new file mode 100644 index 000000000..8c3ae1da6 Binary files /dev/null and b/doc/img/RadioAstronomy_Settings.png differ diff --git a/doc/img/RadioAstronomy_Spectrometer.png b/doc/img/RadioAstronomy_Spectrometer.png new file mode 100644 index 000000000..c07085176 Binary files /dev/null and b/doc/img/RadioAstronomy_Spectrometer.png differ diff --git a/doc/img/RadioAstronomy_SpectrumGaussian.png b/doc/img/RadioAstronomy_SpectrumGaussian.png new file mode 100644 index 000000000..f6476b86e Binary files /dev/null and b/doc/img/RadioAstronomy_SpectrumGaussian.png differ diff --git a/doc/img/RadioAstronomy_plugin.png b/doc/img/RadioAstronomy_plugin.png new file mode 100644 index 000000000..235c887f5 Binary files /dev/null and b/doc/img/RadioAstronomy_plugin.png differ diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 63330f55f..aadc7da63 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(demodpacket) add_subdirectory(demodais) add_subdirectory(demodpager) add_subdirectory(radioclock) +add_subdirectory(radioastronomy) if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND) add_subdirectory(demoddab) diff --git a/plugins/channelrx/radioastronomy/CMakeLists.txt b/plugins/channelrx/radioastronomy/CMakeLists.txt new file mode 100644 index 000000000..feed4b494 --- /dev/null +++ b/plugins/channelrx/radioastronomy/CMakeLists.txt @@ -0,0 +1,67 @@ +project(radioastronomy) + +set(radioastronomy_SOURCES + radioastronomy.cpp + radioastronomysettings.cpp + radioastronomybaseband.cpp + radioastronomysink.cpp + radioastronomyplugin.cpp + radioastronomywebapiadapter.cpp + radioastronomyworker.cpp +) + +set(radioastronomy_HEADERS + radioastronomy.h + radioastronomysettings.h + radioastronomybaseband.h + radioastronomysink.h + radioastronomyplugin.h + radioastronomywebapiadapter.h + radioastronomyworker.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(radioastronomy_SOURCES + ${radioastronomy_SOURCES} + radioastronomygui.cpp + radioastronomygui.ui + radioastronomycalibrationdialog.cpp + radioastronomycalibrationdialog.ui + radioastronomysensordialog.cpp + radioastronomysensordialog.ui + icons.qrc + ) + set(radioastronomy_HEADERS + ${radioastronomy_HEADERS} + radioastronomygui.h + radioastronomycalibrationdialog.h + radioastronomysensordialog.h + ) + + set(TARGET_NAME radioastronomy) + set(TARGET_LIB "Qt5::Widgets" Qt5::Charts) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME radioastronomysrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${radioastronomy_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/radioastronomy/icons.qrc b/plugins/channelrx/radioastronomy/icons.qrc new file mode 100644 index 000000000..45a384156 --- /dev/null +++ b/plugins/channelrx/radioastronomy/icons.qrc @@ -0,0 +1,16 @@ + + + icons/hot.png + icons/cold.png + icons/temperature.png + icons/reverse.png + icons/gaussian.png + icons/peak.png + icons/marker.png + icons/refline.png + icons/legend.png + icons/lab.png + icons/galactictriangle.png + icons/noise.png + + diff --git a/plugins/channelrx/radioastronomy/icons/cold.png b/plugins/channelrx/radioastronomy/icons/cold.png new file mode 100644 index 000000000..44cba24e9 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/cold.png differ diff --git a/plugins/channelrx/radioastronomy/icons/galactictriangle.png b/plugins/channelrx/radioastronomy/icons/galactictriangle.png new file mode 100644 index 000000000..6f3d7810b Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/galactictriangle.png differ diff --git a/plugins/channelrx/radioastronomy/icons/gaussian.png b/plugins/channelrx/radioastronomy/icons/gaussian.png new file mode 100644 index 000000000..98a1daf1e Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/gaussian.png differ diff --git a/plugins/channelrx/radioastronomy/icons/hot.png b/plugins/channelrx/radioastronomy/icons/hot.png new file mode 100644 index 000000000..96b3d6ad0 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/hot.png differ diff --git a/plugins/channelrx/radioastronomy/icons/lab.png b/plugins/channelrx/radioastronomy/icons/lab.png new file mode 100644 index 000000000..0ec04366d Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/lab.png differ diff --git a/plugins/channelrx/radioastronomy/icons/legend.png b/plugins/channelrx/radioastronomy/icons/legend.png new file mode 100644 index 000000000..dbef748b5 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/legend.png differ diff --git a/plugins/channelrx/radioastronomy/icons/marker.png b/plugins/channelrx/radioastronomy/icons/marker.png new file mode 100644 index 000000000..9b37f8347 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/marker.png differ diff --git a/plugins/channelrx/radioastronomy/icons/noise.png b/plugins/channelrx/radioastronomy/icons/noise.png new file mode 100644 index 000000000..4084374ad Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/noise.png differ diff --git a/plugins/channelrx/radioastronomy/icons/peak.png b/plugins/channelrx/radioastronomy/icons/peak.png new file mode 100644 index 000000000..376971da6 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/peak.png differ diff --git a/plugins/channelrx/radioastronomy/icons/refline.png b/plugins/channelrx/radioastronomy/icons/refline.png new file mode 100644 index 000000000..157a7ad22 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/refline.png differ diff --git a/plugins/channelrx/radioastronomy/icons/reverse.png b/plugins/channelrx/radioastronomy/icons/reverse.png new file mode 100644 index 000000000..9f3b1922b Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/reverse.png differ diff --git a/plugins/channelrx/radioastronomy/icons/temperature.png b/plugins/channelrx/radioastronomy/icons/temperature.png new file mode 100644 index 000000000..35db3dee5 Binary files /dev/null and b/plugins/channelrx/radioastronomy/icons/temperature.png differ diff --git a/plugins/channelrx/radioastronomy/radioastronomy.cpp b/plugins/channelrx/radioastronomy/radioastronomy.cpp new file mode 100644 index 000000000..788b4fe6c --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomy.cpp @@ -0,0 +1,1109 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "radioastronomy.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" +#include "SWGChannelActions.h" +#include "SWGRadioAstronomyActions.h" + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/deviceapi.h" +#include "feature/feature.h" +#include "channel/channelwebapiutils.h" +#include "util/astronomy.h" +#include "util/db.h" +#include "maincore.h" + +#include "radioastronomyworker.h" + +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgConfigureRadioAstronomy, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgStartMeasurements, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgStopMeasurements, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgMeasurementProgress, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgStartCal, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgCalComplete, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgFFTMeasurement, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgSensorMeasurement, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgStartSweep, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgStopSweep, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgSweepComplete, Message) +MESSAGE_CLASS_DEFINITION(RadioAstronomy::MsgSweepStatus, Message) + +const char * const RadioAstronomy::m_channelIdURI = "sdrangel.channel.radioastronomy"; +const char * const RadioAstronomy::m_channelId = "RadioAstronomy"; + +RadioAstronomy::RadioAstronomy(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0), + m_sweeping(false) +{ + setObjectName(m_channelId); + + m_basebandSink = new RadioAstronomyBaseband(this); + m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); + m_basebandSink->setChannel(this); + m_basebandSink->moveToThread(&m_thread); + + m_worker = new RadioAstronomyWorker(this); + m_worker->setMessageQueueToChannel(getInputMessageQueue()); + m_worker->moveToThread(&m_workerThread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); + m_deviceAPI->addChannelSinkAPI(this); + + m_selectedPipe = nullptr; + connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes())); + m_updatePipesTimer.start(1000); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + connect(&m_channelMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleChannelMessages())); + + m_sweepTimer.setSingleShot(true); +} + +RadioAstronomy::~RadioAstronomy() +{ + qDebug("RadioAstronomy::~RadioAstronomy"); + 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; + if (m_worker->isRunning()) { + stop(); + } + delete m_worker; +} + +uint32_t RadioAstronomy::getNumberOfDeviceStreams() const +{ + return m_deviceAPI->getNbSourceStreams(); +} + +void RadioAstronomy::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +{ + (void) firstOfBurst; + m_basebandSink->feed(begin, end); +} + +void RadioAstronomy::start() +{ + qDebug("RadioAstronomy::start"); + + m_basebandSink->reset(); + m_basebandSink->startWork(); + m_thread.start(); + + m_worker->reset(); + m_worker->setMessageQueueToGUI(getMessageQueueToGUI()); + m_worker->startWork(); + m_workerThread.start(); + + m_basebandSink->getInputMessageQueue()->push(new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency)); + + m_basebandSink->getInputMessageQueue()->push(RadioAstronomyBaseband::MsgConfigureRadioAstronomyBaseband::create(m_settings, true)); + + m_worker->getInputMessageQueue()->push(RadioAstronomyWorker::MsgConfigureRadioAstronomyWorker::create(m_settings, true)); +} + +void RadioAstronomy::stop() +{ + qDebug("RadioAstronomy::stop"); + m_basebandSink->stopWork(); + m_thread.quit(); + m_thread.wait(); + m_worker->stopWork(); + m_workerThread.quit(); + m_workerThread.wait(); +} + +bool RadioAstronomy::handleMessage(const Message& cmd) +{ + if (MsgConfigureRadioAstronomy::match(cmd)) + { + MsgConfigureRadioAstronomy& cfg = (MsgConfigureRadioAstronomy&) cmd; + qDebug() << "RadioAstronomy::handleMessage: MsgConfigureRadioAstronomy"; + 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() << "RadioAstronomy::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + // Forward to GUI + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(new DSPSignalNotification(notif)); + } + + return true; + } + else if (MainCore::MsgStarTrackerTarget::match(cmd)) + { + MainCore::MsgStarTrackerTarget& msg = (MainCore::MsgStarTrackerTarget&)cmd; + if (msg.getPipeSource() == m_selectedPipe) + { + // Forward to GUI + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(new MainCore::MsgStarTrackerTarget(msg)); + } + } + return true; + } + else if (MsgMeasurementProgress::match(cmd)) + { + // Forward to GUI + MsgMeasurementProgress& report = (MsgMeasurementProgress&)cmd; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(new MsgMeasurementProgress(report)); + } + + return true; + } + else if (MsgStartCal::match(cmd)) + { + // Forward to the sink + MsgStartCal& calCmd = (MsgStartCal&)cmd; + + startCal(calCmd.getHot()); + + return true; + } + else if (MsgCalComplete::match(cmd)) + { + // Take copy to forward to GUI + MsgCalComplete& report = (MsgCalComplete&)cmd; + MsgCalComplete* copy = nullptr; + if (getMessageQueueToGUI()) { + copy = new MsgCalComplete(report); + } + calComplete(copy); + + return true; + } + else if (MsgFFTMeasurement::match(cmd)) + { + // Forward to GUI + MsgFFTMeasurement& report = (MsgFFTMeasurement&)cmd; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(new MsgFFTMeasurement(report)); + } + + if (m_sweeping) { + m_sweeping = false; + sweepNext(); + } + + return true; + } + else if (MsgStartSweep::match(cmd)) + { + if (m_settings.m_runMode == RadioAstronomySettings::SWEEP) { + sweepStart(); + } else { + callOnStartTime(&RadioAstronomy::startMeasurement); + } + return true; + } + else if (MsgStopSweep::match(cmd)) + { + if (m_settings.m_runMode == RadioAstronomySettings::SWEEP) + { + m_sweepStop = true; + m_sweepTimer.setInterval(0); + } + else + { + m_basebandSink->getInputMessageQueue()->push(MsgStopMeasurements::create()); + } + return true; + } + else + { + return false; + } +} + +void RadioAstronomy::startCal(bool hot) +{ + // Set GPIO pin in SDR to enable calibration + if (m_settings.m_gpioEnabled) + { + int gpioPins; + int gpioDir; + if (ChannelWebAPIUtils::getDeviceSetting(getDeviceSetIndex(), "gpioDir", gpioDir)) + { + // Set pin as output + gpioDir |= 1 << m_settings.m_gpioPin; + ChannelWebAPIUtils::patchDeviceSetting(getDeviceSetIndex(), "gpioDir", gpioDir); + if (ChannelWebAPIUtils::getDeviceSetting(getDeviceSetIndex(), "gpioPins", gpioPins)) + { + // Set state of pin + if (m_settings.m_gpioSense) { + gpioPins |= 1 << m_settings.m_gpioPin; + } else { + gpioPins &= ~(1 << m_settings.m_gpioPin); + } + ChannelWebAPIUtils::patchDeviceSetting(getDeviceSetIndex(), "gpioPins", gpioPins); + } + else + { + qDebug() << "RadioAstronomy::startCal - Failed to read gpioPins setting. Does this SDR support it?"; + } + } + else + { + qDebug() << "RadioAstronomy::startCal - Failed to read gpioDir setting. Does this SDR support it?"; + } + } + + // Execute command to enable calibration + if (!m_settings.m_startCalCommand.isEmpty()) { + QProcess::startDetached(m_settings.m_startCalCommand); + } + + // Start calibration after requested delay + MsgStartCal* startCal = MsgStartCal::create(hot); + QTimer::singleShot(m_settings.m_calCommandDelay * 1000, [this, startCal] { + m_basebandSink->getInputMessageQueue()->push(startCal); + }); +} + +void RadioAstronomy::calComplete(MsgCalComplete* report) +{ + // Set GPIO pin in SDR to disable calibration + if (m_settings.m_gpioEnabled) + { + int gpioPins; + if (ChannelWebAPIUtils::getDeviceSetting(getDeviceSetIndex(), "gpioPins", gpioPins)) + { + if (m_settings.m_gpioSense) { + gpioPins &= ~(1 << m_settings.m_gpioPin); + } else { + gpioPins |= 1 << m_settings.m_gpioPin; + } + ChannelWebAPIUtils::patchDeviceSetting(getDeviceSetIndex(), "gpioPins", gpioPins); + } + else + { + qDebug() << "RadioAstronomy::calComplete - Failed to read gpioPins setting. Does this SDR support it?"; + } + } + + // Execute command to disable calibration + if (!m_settings.m_stopCalCommand.isEmpty()) { + QProcess::startDetached(m_settings.m_stopCalCommand); + } + + // Send calibration result to GUI + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(report); + } +} + +void RadioAstronomy::callOnStartTime(void (RadioAstronomy::*f)()) +{ + qint64 delayMSecs = 0; + + if (m_settings.m_sweepStartAtTime) { + delayMSecs = QDateTime::currentDateTime().msecsTo(m_settings.m_sweepStartDateTime); + } + + if (delayMSecs > 0) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create(QString("Waiting: %1").arg(m_settings.m_sweepStartDateTime.toString()))); + } + qDebug() << "RadioAstronomy::callOnStartTime - Wait until " << m_settings.m_sweepStartDateTime.toString(); + // Wait before calling + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, f); + m_sweepTimer.start(delayMSecs); + } + else + { + // Call immediately + (this->*f)(); + } +} + +void RadioAstronomy::startMeasurement() +{ + m_basebandSink->getInputMessageQueue()->push(MsgStartMeasurements::create()); +} + +void RadioAstronomy::sweepStart() +{ + m_sweepStop = false; + m_sweep1Start = m_settings.m_sweep1Start; + m_sweep1Stop = m_settings.m_sweep1Stop; + + // Handle azimuth/l sweep through 0. E.g. 340deg -> 20deg with +vs step, or 20deg -> 340deg with -ve step + if ((m_settings.m_sweep1Stop < m_settings.m_sweep1Start) && (m_settings.m_sweep1Step > 0)) { + m_sweep1Stop = m_settings.m_sweep1Stop + 360.0; + } else if ((m_settings.m_sweep1Stop > m_settings.m_sweep1Start) && (m_settings.m_sweep1Step < 0)) { + m_sweep1Start += 360.0; + } + m_sweep1 = m_sweep1Start; + m_sweep2 = m_settings.m_sweep2Start; + + const QRegExp re("F([0-9]+):([0-9]+)"); + if (re.indexIn(m_settings.m_starTracker) >= 0) + { + m_starTrackerFeatureSetIndex = re.capturedTexts()[1].toInt(); + m_starTrackerFeatureIndex = re.capturedTexts()[2].toInt(); + + if (m_settings.m_sweepType == RadioAstronomySettings::SWP_AZEL) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "target", "Custom Az/El"); + } else if (m_settings.m_sweepType == RadioAstronomySettings::SWP_LB) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "target", "Custom l/b"); + } + + if (m_settings.m_rotator == "None") + { + m_rotatorFeatureSetIndex = -1; + m_rotatorFeatureIndex = -1; + + sweep2(); + callOnStartTime(&RadioAstronomy::sweep1); + } + else if (re.indexIn(m_settings.m_rotator) >= 0) + { + m_rotatorFeatureSetIndex = re.capturedTexts()[1].toInt(); + m_rotatorFeatureIndex = re.capturedTexts()[2].toInt(); + + sweep2(); + callOnStartTime(&RadioAstronomy::sweep1); + } + else + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Invalid rotator")); + } + qDebug() << "RadioAstronomy::sweepStart: No valid rotator feature is set"; + } + } + else + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Invalid Star Tracker")); + } + qDebug() << "RadioAstronomy::sweepStart: No valid StarTracker feature is set"; + } +} + +void RadioAstronomy::sweep1() +{ + if (m_sweepStop) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Stopped")); + } + sweepComplete(); + } + else + { + if (m_settings.m_sweepType == RadioAstronomySettings::SWP_AZEL) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "azimuth", Astronomy::modulo(m_sweep1, 360.0)); + } else if (m_settings.m_sweepType == RadioAstronomySettings::SWP_LB) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "l", Astronomy::modulo(m_sweep1, 360.0)); + } else if (m_settings.m_sweepType == RadioAstronomySettings::SWP_OFFSET) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "azimuthOffset", m_sweep1); + } + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create(QString("Rotating: %1,%2").arg(m_sweep1).arg(m_sweep2))); + } + qDebug() << "RadioAstronomy::sweep1 - Sweeping " << m_sweep1 << m_sweep2; + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, &RadioAstronomy::waitUntilOnTarget); + m_sweepTimer.start(100); + } +} + +void RadioAstronomy::sweep2() +{ + if (m_settings.m_sweepType == RadioAstronomySettings::SWP_AZEL) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "elevation", m_sweep2); + } else if (m_settings.m_sweepType == RadioAstronomySettings::SWP_LB) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "b", m_sweep2); + } else if (m_settings.m_sweepType == RadioAstronomySettings::SWP_OFFSET) { + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "elevationOffset", m_sweep2); + } +} + +// Wait until the antenna is pointing at the target +void RadioAstronomy::waitUntilOnTarget() +{ + int onTarget; + + if (m_sweepStop) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Stopped")); + } + sweepComplete(); + } + else + { + if (m_settings.m_rotator == "None") + { + onTarget = true; + } + else if (!ChannelWebAPIUtils::getFeatureReportValue(m_rotatorFeatureSetIndex, m_rotatorFeatureIndex, "onTarget", onTarget)) + { + sweepComplete(); + return; + } + + if (onTarget) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Settle")); + } + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, &RadioAstronomy::sweepStartMeasurement); + m_sweepTimer.start(m_settings.m_sweep1Delay * 1000); + } + else + { + // Wait some more and retry + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, &RadioAstronomy::waitUntilOnTarget); + m_sweepTimer.start(100); + } + } +} + +void RadioAstronomy::sweepStartMeasurement() +{ + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create(QString("Measure: %1,%2").arg(m_sweep1).arg(m_sweep2))); + } + // Start measurement + m_sweeping = true; + m_basebandSink->getInputMessageQueue()->push(MsgStartMeasurements::create()); +} + +void RadioAstronomy::sweepNext() +{ + if (m_sweepStop) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Stopped")); + } + sweepComplete(); + } + else + { + if ( ((m_sweep1 >= m_sweep1Stop) && (m_settings.m_sweep1Step >= 0)) + || ((m_sweep1 <= m_sweep1Stop) && (m_settings.m_sweep1Step < 0)) + ) + { + if ( ((m_sweep2 >= m_settings.m_sweep2Stop) && (m_settings.m_sweep2Step >= 0)) + || ((m_sweep2 <= m_settings.m_sweep2Stop) && (m_settings.m_sweep2Step < 0)) + ) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Complete")); + } + // Finished + sweepComplete(); + } + else + { + m_sweep2 += m_settings.m_sweep2Step; + sweep2(); + m_sweep1 = m_sweep1Start; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Delay")); + } + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, &RadioAstronomy::sweep1); + m_sweepTimer.start(m_settings.m_sweep2Delay * 1000); + } + } + else + { + m_sweep1 += m_settings.m_sweep1Step; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepStatus::create("Delay")); + } + QObject::disconnect(m_sweepTimerConnection); + m_sweepTimerConnection = m_sweepTimer.callOnTimeout(this, &RadioAstronomy::sweep1); + m_sweepTimer.start(m_settings.m_sweep2Delay * 1000); + } + } +} + +void RadioAstronomy::sweepComplete() +{ + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "elevationOffset", 0); + ChannelWebAPIUtils::patchFeatureSetting(m_starTrackerFeatureSetIndex, m_starTrackerFeatureIndex, "azimuthOffset", 0); + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgSweepComplete::create()); + } +} + +void RadioAstronomy::updatePipes() +{ + QList availablePipes = updateAvailablePipeSources("startracker.target", RadioAstronomySettings::m_pipeTypes, RadioAstronomySettings::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); + } + } +} + +void RadioAstronomy::applySettings(const RadioAstronomySettings& settings, bool force) +{ + qDebug() << "RadioAstronomy::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_sampleRate != m_settings.m_sampleRate) || force) { + reverseAPIKeys.append("sampleRate"); + } + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); + } + if ((settings.m_integration != m_settings.m_integration) || force) { + reverseAPIKeys.append("integration"); + } + if ((settings.m_fftSize != m_settings.m_fftSize) || force) { + reverseAPIKeys.append("fftSize"); + } + if ((settings.m_fftWindow != m_settings.m_fftWindow) || force) { + reverseAPIKeys.append("fftWindow"); + } + if ((settings.m_filterFreqs != m_settings.m_filterFreqs) || force) { + reverseAPIKeys.append("filterFreqs"); + } + + if ((settings.m_starTracker != m_settings.m_starTracker) || force) { + reverseAPIKeys.append("starTracker"); + } + if ((settings.m_rotator != m_settings.m_rotator) || force) { + reverseAPIKeys.append("rotator"); + } + + if ((settings.m_runMode != m_settings.m_runMode) || force) { + reverseAPIKeys.append("runMode"); + } + if ((settings.m_sweepStartAtTime != m_settings.m_sweepStartAtTime) || force) { + reverseAPIKeys.append("sweepStartAtTime"); + } + if ((settings.m_sweepStartDateTime != m_settings.m_sweepStartDateTime) || force) { + reverseAPIKeys.append("sweepStartDateTime"); + } + if ((settings.m_sweepType != m_settings.m_sweepType) || force) { + reverseAPIKeys.append("sweepType"); + } + if ((settings.m_sweep1Start != m_settings.m_sweep1Start) || force) { + reverseAPIKeys.append("sweep1Start"); + } + if ((settings.m_sweep1Stop != m_settings.m_sweep1Stop) || force) { + reverseAPIKeys.append("sweep1Stop"); + } + if ((settings.m_sweep1Step != m_settings.m_sweep1Step) || force) { + reverseAPIKeys.append("sweep1Step"); + } + if ((settings.m_sweep1Delay != m_settings.m_sweep1Delay) || force) { + reverseAPIKeys.append("sweep1Delay"); + } + if ((settings.m_sweep2Start != m_settings.m_sweep2Start) || force) { + reverseAPIKeys.append("sweep2Start"); + } + if ((settings.m_sweep2Stop != m_settings.m_sweep2Stop) || force) { + reverseAPIKeys.append("sweep2Stop"); + } + if ((settings.m_sweep2Step != m_settings.m_sweep2Step) || force) { + reverseAPIKeys.append("sweep2Step"); + } + if ((settings.m_sweep2Delay != m_settings.m_sweep2Delay) || force) { + reverseAPIKeys.append("sweep2Delay"); + } + + if ((m_settings.m_starTracker != settings.m_starTracker) + || (!settings.m_starTracker.isEmpty() && (m_selectedPipe == nullptr)) // Change in available pipes + || force) + { + if (!settings.m_starTracker.isEmpty()) + { + m_selectedPipe = getPipeEndPoint(settings.m_starTracker, m_availablePipes); + if (m_selectedPipe == nullptr) { + qDebug() << "RadioAstronomy::applySettings: No plugin corresponding to target " << settings.m_starTracker; + } + } + + reverseAPIKeys.append("starTracker"); + } + + 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"); + } + + m_basebandSink->getInputMessageQueue()->push(RadioAstronomyBaseband::MsgConfigureRadioAstronomyBaseband::create(settings, force)); + + m_worker->getInputMessageQueue()->push(RadioAstronomyWorker::MsgConfigureRadioAstronomyWorker::create(settings, force)); + + 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 RadioAstronomy::serialize() const +{ + return m_settings.serialize(); +} + +bool RadioAstronomy::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureRadioAstronomy *msg = MsgConfigureRadioAstronomy::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureRadioAstronomy *msg = MsgConfigureRadioAstronomy::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int RadioAstronomy::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setRadioAstronomySettings(new SWGSDRangel::SWGRadioAstronomySettings()); + response.getRadioAstronomySettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int RadioAstronomy::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + RadioAstronomySettings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigureRadioAstronomy *msg = MsgConfigureRadioAstronomy::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("RadioAstronomy::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureRadioAstronomy *msgToGUI = MsgConfigureRadioAstronomy::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int RadioAstronomy::webapiActionsPost( + const QStringList& channelActionsKeys, + SWGSDRangel::SWGChannelActions& query, + QString& errorMessage) +{ + SWGSDRangel::SWGRadioAstronomyActions *swgRadioAstronomyActions = query.getRadioAstronomyActions(); + + if (swgRadioAstronomyActions) + { + if (channelActionsKeys.contains("start")) + { + getInputMessageQueue()->push(MsgStartSweep::create()); + return 202; + } + else + { + errorMessage = "Unknown action"; + return 400; + } + } + else + { + errorMessage = "Missing RadioAstronomyActions in query"; + return 400; + } +} + +void RadioAstronomy::webapiUpdateChannelSettings( + RadioAstronomySettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("inputFrequencyOffset")) { + settings.m_inputFrequencyOffset = response.getRadioAstronomySettings()->getInputFrequencyOffset(); + } + if (channelSettingsKeys.contains("sampleRate")) { + settings.m_sampleRate = response.getRadioAstronomySettings()->getSampleRate(); + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getRadioAstronomySettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("integration")) { + settings.m_integration = response.getRadioAstronomySettings()->getIntegration(); + } + if (channelSettingsKeys.contains("fftSize")) { + settings.m_fftSize = response.getRadioAstronomySettings()->getFftSize(); + } + if (channelSettingsKeys.contains("fftWindow")) { + settings.m_fftWindow = (RadioAstronomySettings::FFTWindow)response.getRadioAstronomySettings()->getFftWindow(); + } + if (channelSettingsKeys.contains("filterFreqs")) { + settings.m_filterFreqs = *response.getRadioAstronomySettings()->getFilterFreqs(); + } + + if (channelSettingsKeys.contains("starTracker")) { + settings.m_starTracker = *response.getRadioAstronomySettings()->getStarTracker(); + } + if (channelSettingsKeys.contains("rotator")) { + settings.m_rotator = *response.getRadioAstronomySettings()->getRotator(); + } + + if (channelSettingsKeys.contains("runMode")) { + settings.m_runMode = (RadioAstronomySettings::RunMode)response.getRadioAstronomySettings()->getRunMode(); + } + if (channelSettingsKeys.contains("sweepStartAtTime")) { + settings.m_sweepStartAtTime = (bool)response.getRadioAstronomySettings()->getSweepStartAtTime(); + } + if (channelSettingsKeys.contains("sweepStartDateTime")) { + settings.m_sweepStartDateTime = QDateTime::fromString(*response.getRadioAstronomySettings()->getRotator(), Qt::ISODate); + } + if (channelSettingsKeys.contains("sweepType")) { + settings.m_sweepType = (RadioAstronomySettings::SweepType)response.getRadioAstronomySettings()->getSweepType(); + } + if (channelSettingsKeys.contains("sweep1Start")) { + settings.m_sweep1Start = response.getRadioAstronomySettings()->getSweep1Start(); + } + if (channelSettingsKeys.contains("sweep1Stop")) { + settings.m_sweep1Stop = response.getRadioAstronomySettings()->getSweep1Stop(); + } + if (channelSettingsKeys.contains("sweep1Step")) { + settings.m_sweep1Step = response.getRadioAstronomySettings()->getSweep1Step(); + } + if (channelSettingsKeys.contains("sweep1Delay")) { + settings.m_sweep1Delay = response.getRadioAstronomySettings()->getSweep1Delay(); + } + if (channelSettingsKeys.contains("sweep12Start")) { + settings.m_sweep2Start = response.getRadioAstronomySettings()->getSweep2Start(); + } + if (channelSettingsKeys.contains("sweep12Stop")) { + settings.m_sweep2Stop = response.getRadioAstronomySettings()->getSweep2Stop(); + } + if (channelSettingsKeys.contains("sweep2Step")) { + settings.m_sweep2Step = response.getRadioAstronomySettings()->getSweep2Step(); + } + if (channelSettingsKeys.contains("sweep2Delay")) { + settings.m_sweep2Delay = response.getRadioAstronomySettings()->getSweep2Delay(); + } + + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getRadioAstronomySettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getRadioAstronomySettings()->getTitle(); + } + if (channelSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getRadioAstronomySettings()->getStreamIndex(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getRadioAstronomySettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getRadioAstronomySettings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getRadioAstronomySettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getRadioAstronomySettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getRadioAstronomySettings()->getReverseApiChannelIndex(); + } +} + +void RadioAstronomy::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const RadioAstronomySettings& settings) +{ + response.getRadioAstronomySettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getRadioAstronomySettings()->setSampleRate(settings.m_sampleRate); + response.getRadioAstronomySettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getRadioAstronomySettings()->setIntegration(settings.m_integration); + response.getRadioAstronomySettings()->setFftSize(settings.m_fftSize); + response.getRadioAstronomySettings()->setFftWindow((int)settings.m_fftWindow); + response.getRadioAstronomySettings()->setFilterFreqs(new QString(settings.m_filterFreqs)); + + response.getRadioAstronomySettings()->setStarTracker(new QString(settings.m_starTracker)); + response.getRadioAstronomySettings()->setRotator(new QString(settings.m_rotator)); + + response.getRadioAstronomySettings()->setRunMode((int)settings.m_runMode); + response.getRadioAstronomySettings()->setSweepStartAtTime((int)settings.m_sweepStartAtTime); + response.getRadioAstronomySettings()->setSweepStartDateTime(new QString(settings.m_sweepStartDateTime.toString(Qt::ISODate))); + response.getRadioAstronomySettings()->setSweepType((int)settings.m_sweepType); + response.getRadioAstronomySettings()->setSweep1Start(settings.m_sweep1Start); + response.getRadioAstronomySettings()->setSweep1Stop(settings.m_sweep1Stop); + response.getRadioAstronomySettings()->setSweep1Step(settings.m_sweep1Step); + response.getRadioAstronomySettings()->setSweep1Delay(settings.m_sweep1Delay); + response.getRadioAstronomySettings()->setSweep2Start(settings.m_sweep2Start); + response.getRadioAstronomySettings()->setSweep2Stop(settings.m_sweep2Stop); + response.getRadioAstronomySettings()->setSweep2Step(settings.m_sweep2Step); + response.getRadioAstronomySettings()->setSweep2Delay(settings.m_sweep2Delay); + + response.getRadioAstronomySettings()->setRgbColor(settings.m_rgbColor); + if (response.getRadioAstronomySettings()->getTitle()) { + *response.getRadioAstronomySettings()->getTitle() = settings.m_title; + } else { + response.getRadioAstronomySettings()->setTitle(new QString(settings.m_title)); + } + + response.getRadioAstronomySettings()->setStreamIndex(settings.m_streamIndex); + response.getRadioAstronomySettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getRadioAstronomySettings()->getReverseApiAddress()) { + *response.getRadioAstronomySettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getRadioAstronomySettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getRadioAstronomySettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getRadioAstronomySettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getRadioAstronomySettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void RadioAstronomy::webapiReverseSendSettings(QList& channelSettingsKeys, const RadioAstronomySettings& 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 RadioAstronomy::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const RadioAstronomySettings& settings, + bool force +) +{ + swgChannelSettings->setDirection(0); // Single sink (Rx) + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("RadioAstronomy")); + swgChannelSettings->setRadioAstronomySettings(new SWGSDRangel::SWGRadioAstronomySettings()); + SWGSDRangel::SWGRadioAstronomySettings *swgRadioAstronomySettings = swgChannelSettings->getRadioAstronomySettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgRadioAstronomySettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("sampleRate") || force) { + swgRadioAstronomySettings->setInputFrequencyOffset(settings.m_sampleRate); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgRadioAstronomySettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("integration") || force) { + swgRadioAstronomySettings->setRfBandwidth(settings.m_integration); + } + if (channelSettingsKeys.contains("fftSize") || force) { + swgRadioAstronomySettings->setRfBandwidth(settings.m_fftSize); + } + if (channelSettingsKeys.contains("fftWindow") || force) { + swgRadioAstronomySettings->setRfBandwidth((int)settings.m_fftWindow); + } + if (channelSettingsKeys.contains("filterFreqs") || force) { + swgRadioAstronomySettings->setFilterFreqs(new QString(settings.m_filterFreqs)); + } + + if (channelSettingsKeys.contains("starTracker") || force) { + swgRadioAstronomySettings->setStarTracker(new QString(settings.m_starTracker)); + } + if (channelSettingsKeys.contains("rotator") || force) { + swgRadioAstronomySettings->setRotator(new QString(settings.m_rotator)); + } + + if (channelSettingsKeys.contains("runMode") || force) { + swgRadioAstronomySettings->setRunMode((int)settings.m_runMode); + } + if (channelSettingsKeys.contains("sweepStartAtTime") || force) { + swgRadioAstronomySettings->setSweepStartAtTime((int)settings.m_sweepStartAtTime); + } + if (channelSettingsKeys.contains("sweepStartDateTime") || force) { + swgRadioAstronomySettings->setSweepStartDateTime(new QString(settings.m_sweepStartDateTime.toString(Qt::ISODate))); + } + if (channelSettingsKeys.contains("sweepType") || force) { + swgRadioAstronomySettings->setSweepType(settings.m_sweepType); + } + if (channelSettingsKeys.contains("sweep1Start") || force) { + swgRadioAstronomySettings->setSweep1Start(settings.m_sweep1Start); + } + if (channelSettingsKeys.contains("sweep1Stop") || force) { + swgRadioAstronomySettings->setSweep1Stop(settings.m_sweep1Stop); + } + if (channelSettingsKeys.contains("sweep1Step") || force) { + swgRadioAstronomySettings->setSweep1Step(settings.m_sweep1Step); + } + if (channelSettingsKeys.contains("sweep2Delay") || force) { + swgRadioAstronomySettings->setSweep2Delay(settings.m_sweep2Delay); + } + if (channelSettingsKeys.contains("sweep2Start") || force) { + swgRadioAstronomySettings->setSweep2Start(settings.m_sweep2Start); + } + if (channelSettingsKeys.contains("sweep2Stop") || force) { + swgRadioAstronomySettings->setSweep2Stop(settings.m_sweep2Stop); + } + if (channelSettingsKeys.contains("sweep2Step") || force) { + swgRadioAstronomySettings->setSweep2Step(settings.m_sweep2Step); + } + if (channelSettingsKeys.contains("sweep2Delay") || force) { + swgRadioAstronomySettings->setSweep2Delay(settings.m_sweep2Delay); + } + + if (channelSettingsKeys.contains("rgbColor") || force) { + swgRadioAstronomySettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgRadioAstronomySettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("streamIndex") || force) { + swgRadioAstronomySettings->setStreamIndex(settings.m_streamIndex); + } +} + +void RadioAstronomy::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "RadioAstronomy::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("RadioAstronomy::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} + +void RadioAstronomy::handleChannelMessages() +{ + Message* message; + + while ((message = m_channelMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} diff --git a/plugins/channelrx/radioastronomy/radioastronomy.h b/plugins/channelrx/radioastronomy/radioastronomy.h new file mode 100644 index 000000000..305cdc390 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomy.h @@ -0,0 +1,437 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMY_H +#define INCLUDE_RADIOASTRONOMY_H + +#include + +#include +#include +#include +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelapi.h" +#include "util/message.h" +#include "pipes/pipeendpoint.h" + +#include "radioastronomybaseband.h" +#include "radioastronomysettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class DeviceAPI; +class RadioAstronomyWorker; + +class RadioAstronomy : public BasebandSampleSink, public ChannelAPI { + Q_OBJECT +public: + class MsgConfigureRadioAstronomy : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const RadioAstronomySettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureRadioAstronomy* create(const RadioAstronomySettings& settings, bool force) + { + return new MsgConfigureRadioAstronomy(settings, force); + } + + private: + RadioAstronomySettings m_settings; + bool m_force; + + MsgConfigureRadioAstronomy(const RadioAstronomySettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgSensorMeasurement : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSensor() const { return m_sensor; } + double getValue() const { return m_value; } + QDateTime getDateTime() const { return m_dateTime; } + + static MsgSensorMeasurement* create(int sensor, double value) + { + return new MsgSensorMeasurement(sensor, value, QDateTime::currentDateTime()); + } + + private: + int m_sensor; + double m_value; + QDateTime m_dateTime; + + MsgSensorMeasurement(int sensor, double value, QDateTime dateTime) : + Message(), + m_sensor(sensor), + m_value(value), + m_dateTime(dateTime) + { + } + }; + + class MsgFFTMeasurement : public Message { + MESSAGE_CLASS_DECLARATION + + public: + Real *getFFT() const { return m_fft; } + int getSize() const { return m_size; } + QDateTime getDateTime() const { return m_dateTime; } + + static MsgFFTMeasurement* create(const Real *fft, int size, QDateTime dateTime) + { + return new MsgFFTMeasurement(fft, size, dateTime); + } + + private: + Real *m_fft; + int m_size; + QDateTime m_dateTime; + + MsgFFTMeasurement(const Real *fft, int size, QDateTime dateTime) : + Message(), + m_size(size), + m_dateTime(dateTime) + { + // Take a copy of the data + m_fft = new Real[size]; + std::copy(fft, fft + size, m_fft); + } + + }; + + class MsgStartMeasurements : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgStartMeasurements* create() + { + return new MsgStartMeasurements(); + } + + private: + + MsgStartMeasurements() : + Message() + { + } + }; + + class MsgStopMeasurements : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgStopMeasurements* create() + { + return new MsgStopMeasurements(); + } + + private: + + MsgStopMeasurements() : + Message() + { + } + }; + + class MsgMeasurementProgress : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getPercentComplete() const { return m_percentComplete; } + + static MsgMeasurementProgress* create(int percentComplete) + { + return new MsgMeasurementProgress(percentComplete); + } + + private: + int m_percentComplete; + + MsgMeasurementProgress(int percentComplete) : + Message(), + m_percentComplete(percentComplete) + { + } + }; + + class MsgStartCal : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getHot() const { return m_hot; } + + static MsgStartCal* create(bool hot) + { + return new MsgStartCal(hot); + } + + private: + bool m_hot; + + MsgStartCal(bool hot) : + Message(), + m_hot(hot) + { + } + }; + + class MsgCalComplete : public Message { + MESSAGE_CLASS_DECLARATION + + public: + Real *getCal() const { return m_cal; } + int getSize() const { return m_size; } + bool getHot() const { return m_hot; } + QDateTime getDateTime() const { return m_dateTime; } + + static MsgCalComplete* create(const Real *cal, int size, QDateTime dateTime, bool hot) + { + return new MsgCalComplete(cal, size, dateTime, hot); + } + + private: + Real *m_cal; + int m_size; + QDateTime m_dateTime; + bool m_hot; + + MsgCalComplete(const Real *cal, int size, QDateTime dateTime, bool hot) : + Message(), + m_size(size), + m_dateTime(dateTime), + m_hot(hot) + { + // Take a copy of the data + m_cal = new Real[size]; + std::copy(cal, cal + size, m_cal); + } + }; + + class MsgStartSweep : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgStartSweep* create() + { + return new MsgStartSweep(); + } + + private: + + MsgStartSweep() : + Message() + { + } + }; + + class MsgStopSweep : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgStopSweep* create() + { + return new MsgStopSweep(); + } + + private: + + MsgStopSweep() : + Message() + { + } + }; + + class MsgSweepComplete : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgSweepComplete* create() + { + return new MsgSweepComplete(); + } + + private: + + MsgSweepComplete() : + Message() + { + } + }; + + class MsgSweepStatus : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QString getStatus() const { return m_status; } + + static MsgSweepStatus* create(const QString& status) + { + return new MsgSweepStatus(status); + } + + private: + QString m_status; + + MsgSweepStatus(const QString& status) : + Message(), + m_status(status) + { + } + }; + + + RadioAstronomy(DeviceAPI *deviceAPI); + virtual ~RadioAstronomy(); + 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) override; + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) override; + + virtual int webapiActionsPost( + const QStringList& channelActionsKeys, + SWGSDRangel::SWGChannelActions& query, + QString& errorMessage) override; + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const RadioAstronomySettings& settings); + + static void webapiUpdateChannelSettings( + RadioAstronomySettings& 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 { + ChannelAPI::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; + QThread m_workerThread; + RadioAstronomyBaseband* m_basebandSink; + RadioAstronomyWorker *m_worker; + RadioAstronomySettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + qint64 m_centerFrequency; + + QList m_availablePipes; + PipeEndPoint *m_selectedPipe; + QTimer m_updatePipesTimer; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + int m_starTrackerFeatureSetIndex; + int m_starTrackerFeatureIndex; + int m_rotatorFeatureSetIndex; + int m_rotatorFeatureIndex; + + void (*sweepState)(); + float m_sweep1; // Current sweep position + float m_sweep2; + float m_sweep1Stop; + float m_sweep1Start; + bool m_sweeping; + bool m_sweepStop; + QTimer m_sweepTimer; + QMetaObject::Connection m_sweepTimerConnection; + + void applySettings(const RadioAstronomySettings& settings, bool force = false); + void webapiReverseSendSettings(QList& channelSettingsKeys, const RadioAstronomySettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const RadioAstronomySettings& settings, + bool force + ); + void callOnStartTime(void (RadioAstronomy::*f)()); + void sweepStart(); + void startCal(bool hot); + void calComplete(MsgCalComplete* report); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + void updatePipes(); + void handleChannelMessages(); + void startMeasurement(); + void sweepStartMeasurement(); + void sweep1(); + void sweep2(); + void waitUntilOnTarget(); + void sweepNext(); + void sweepComplete(); + +}; + +#endif // INCLUDE_RADIOASTRONOMY_H diff --git a/plugins/channelrx/radioastronomy/radioastronomybaseband.cpp b/plugins/channelrx/radioastronomy/radioastronomybaseband.cpp new file mode 100644 index 000000000..6cb8c1916 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomybaseband.cpp @@ -0,0 +1,197 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "radioastronomybaseband.h" +#include "radioastronomy.h" + +MESSAGE_CLASS_DEFINITION(RadioAstronomyBaseband::MsgConfigureRadioAstronomyBaseband, Message) + +RadioAstronomyBaseband::RadioAstronomyBaseband(RadioAstronomy *aisDemod) : + m_sink(aisDemod), + m_running(false), + m_mutex(QMutex::Recursive) +{ + qDebug("RadioAstronomyBaseband::RadioAstronomyBaseband"); + + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownChannelizer(&m_sink); +} + +RadioAstronomyBaseband::~RadioAstronomyBaseband() +{ + m_inputMessageQueue.clear(); + + delete m_channelizer; +} + +void RadioAstronomyBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); + m_sampleFifo.reset(); +} + +void RadioAstronomyBaseband::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &RadioAstronomyBaseband::handleData, + Qt::QueuedConnection + ); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; +} + +void RadioAstronomyBaseband::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + QObject::disconnect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &RadioAstronomyBaseband::handleData + ); + m_running = false; +} + +void RadioAstronomyBaseband::setChannel(ChannelAPI *channel) +{ + m_sink.setChannel(channel); +} + +void RadioAstronomyBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void RadioAstronomyBaseband::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 RadioAstronomyBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool RadioAstronomyBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureRadioAstronomyBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureRadioAstronomyBaseband& cfg = (MsgConfigureRadioAstronomyBaseband&) cmd; + qDebug() << "RadioAstronomyBaseband::handleMessage: MsgConfigureRadioAstronomyBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "RadioAstronomyBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + setBasebandSampleRate(notif.getSampleRate()); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + + return true; + } + else if (RadioAstronomy::MsgStartMeasurements::match(cmd)) + { + m_sink.startMeasurements(); + + return true; + } + else if (RadioAstronomy::MsgStopMeasurements::match(cmd)) + { + m_sink.stopMeasurements(); + + return true; + } + else if (RadioAstronomy::MsgStartCal::match(cmd)) + { + RadioAstronomy::MsgStartCal& cal = (RadioAstronomy::MsgStartCal&)cmd; + m_sink.startCal(cal.getHot()); + + return true; + } + else + { + return false; + } +} + +void RadioAstronomyBaseband::applySettings(const RadioAstronomySettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) + || (settings.m_sampleRate != m_settings.m_sampleRate) + || force) + { + m_channelizer->setChannelization(settings.m_sampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +void RadioAstronomyBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/radioastronomy/radioastronomybaseband.h b/plugins/channelrx/radioastronomy/radioastronomybaseband.h new file mode 100644 index 000000000..4e6bb183a --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomybaseband.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMYBASEBAND_H +#define INCLUDE_RADIOASTRONOMYBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "dsp/scopevis.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "radioastronomysink.h" + +class DownChannelizer; +class ChannelAPI; +class RadioAstronomy; + +class RadioAstronomyBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureRadioAstronomyBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const RadioAstronomySettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureRadioAstronomyBaseband* create(const RadioAstronomySettings& settings, bool force) + { + return new MsgConfigureRadioAstronomyBaseband(settings, force); + } + + private: + RadioAstronomySettings m_settings; + bool m_force; + + MsgConfigureRadioAstronomyBaseband(const RadioAstronomySettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + RadioAstronomyBaseband(RadioAstronomy *aisDemod); + ~RadioAstronomyBaseband(); + 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); + void setChannel(ChannelAPI *channel); + double getMagSq() const { return m_sink.getMagSq(); } + bool isRunning() const { return m_running; } + +private: + SampleSinkFifo m_sampleFifo; + DownChannelizer *m_channelizer; + RadioAstronomySink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + RadioAstronomySettings m_settings; + bool m_running; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void calculateOffset(RadioAstronomySink *sink); + void applySettings(const RadioAstronomySettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_RADIOASTRONOMYBASEBAND_H diff --git a/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.cpp b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.cpp new file mode 100644 index 000000000..bcef63b96 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.cpp @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "radioastronomycalibrationdialog.h" + +RadioAstronomyCalibrationDialog::RadioAstronomyCalibrationDialog(RadioAstronomySettings *settings, QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::RadioAstronomyCalibrationDialog) +{ + ui->setupUi(this); + ui->gpioEnabled->setChecked(settings->m_gpioEnabled); + ui->gpioPin->setValue(settings->m_gpioPin); + ui->gpioSense->setCurrentIndex(settings->m_gpioSense); + ui->startCalCommand->setText(settings->m_startCalCommand); + ui->stopCalCommand->setText(settings->m_stopCalCommand); + ui->calCommandDelay->setValue(settings->m_calCommandDelay); +} + +RadioAstronomyCalibrationDialog::~RadioAstronomyCalibrationDialog() +{ + delete ui; +} + +void RadioAstronomyCalibrationDialog::accept() +{ + m_settings->m_gpioEnabled = ui->gpioEnabled->isChecked(); + m_settings->m_gpioPin = ui->gpioPin->value(); + m_settings->m_gpioSense = ui->gpioSense->currentIndex(); + m_settings->m_startCalCommand = ui->stopCalCommand->text().trimmed(); + m_settings->m_stopCalCommand = ui->stopCalCommand->text().trimmed(); + m_settings->m_calCommandDelay = ui->calCommandDelay->value(); + QDialog::accept(); +} diff --git a/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.h b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.h new file mode 100644 index 000000000..396d32cac --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.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_RADIOASTRONOMYCALIBRATIONDIALOG_H +#define INCLUDE_RADIOASTRONOMYCALIBRATIONDIALOG_H + +#include "ui_radioastronomycalibrationdialog.h" +#include "radioastronomysettings.h" + +class RadioAstronomyCalibrationDialog : public QDialog { + Q_OBJECT + +public: + explicit RadioAstronomyCalibrationDialog(RadioAstronomySettings *settings, QWidget* parent = 0); + ~RadioAstronomyCalibrationDialog(); + + RadioAstronomySettings *m_settings; + +private slots: + void accept(); + +private: + Ui::RadioAstronomyCalibrationDialog* ui; +}; + +#endif // INCLUDE_RADIOASTRONOMYCALIBRATIONDIALOG_H diff --git a/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.ui b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.ui new file mode 100644 index 000000000..2ef3939c2 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomycalibrationdialog.ui @@ -0,0 +1,247 @@ + + + RadioAstronomyCalibrationDialog + + + + 0 + 0 + 514 + 345 + + + + + Liberation Sans + 9 + + + + Calibration Settings + + + + + + + 0 + 0 + + + + + + + SDR Device GPIO + + + + + + Enabled + + + + + + + Check to enable setting a GPIO pin in SDR to enable calibration + + + + + + + + + + Pin + + + + + + + SDR GPIO pin to set to start/stop calibration + + + 3 + + + + + + + Sense + + + + + + + Whether pin should be set to 1 or 0 to enable calibration + + + 1 + + + + Calibrate=0 + + + + + Calibrate=1 + + + + + + + + + + + Commands + + + + + + Stop calibration + + + + + + + Program/script to execute to start calibration + + + + + + + Start calibration + + + + + + + Program/script to execute to stop calibration + + + + + + + + + + Timing + + + + + + Pre-calibration delay (seconds) + + + + + + + Delay in seconds after command/GPIO before calibration starts + + + 3 + + + 0.000000000000000 + + + 100.000000000000000 + + + 0.000000000000000 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + gpioEnabled + gpioPin + gpioSense + startCalCommand + stopCalCommand + calCommandDelay + + + + + + + buttonBox + accepted() + RadioAstronomyCalibrationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RadioAstronomyCalibrationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/channelrx/radioastronomy/radioastronomygui.cpp b/plugins/channelrx/radioastronomy/radioastronomygui.cpp new file mode 100644 index 000000000..a510fd7a7 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomygui.cpp @@ -0,0 +1,6012 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 +#include +#include + +#include "radioastronomygui.h" + +#include "device/deviceuiset.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "ui_radioastronomygui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "util/astronomy.h" +#include "util/interpolation.h" +#include "util/png.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 "feature/featurewebapiutils.h" +#include "feature/feature.h" +#include "feature/featureset.h" + +#include "radioastronomy.h" +#include "radioastronomysink.h" +#include "radioastronomysensordialog.h" +#include "radioastronomycalibrationdialog.h" + +#include "SWGMapItem.h" +#include "SWGStarTrackerTarget.h" +#include "SWGStarTrackerDisplaySettings.h" +#include "SWGStarTrackerDisplayLoSSettings.h" + +// Delegate for table to display time +class TimeDelegate : public QStyledItemDelegate { + +public: + TimeDelegate(QString format = "hh:mm:ss") : + m_format(format) + { + } + + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + (void) locale; + if (value.toString() == "") { + return ""; + } else { + return value.toTime().toString(m_format); + } + } + +private: + QString m_format; + +}; + +// Time value is in milliseconds - Displays hh:mm:ss or d hh:mm:ss +class TimeDeltaDelegate : public QStyledItemDelegate { + +public: + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + (void) locale; + qint64 v = value.toLongLong(); // In milliseconds + bool neg = v < 0; + v = abs(v); + qint64 days = v / (1000*60*60*24); + v = v % (1000*60*60*24); + qint64 hours = v / (1000*60*60); + v = v % (1000*60*60); + qint64 minutes = v / (1000*60); + v = v % (1000*60); + qint64 seconds = v / (1000); + qint64 msec = v % 1000; + + if (days > 0) { + return QString("%1%2 %3:%4:%5").arg(neg ? "-" : "").arg(days).arg(hours, 2, 10, QChar('0')).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); + } else { + return QString("%1%2:%3:%4").arg(neg ? "-" : "").arg(hours, 2, 10, QChar('0')).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0')); + } + } + +private: + QString m_format; + +}; + +// Delegate for table to control precision used to display floating point values - also supports strings +class DecimalDelegate : public QStyledItemDelegate { + +public: + DecimalDelegate(int precision = 2) : + m_precision(precision) + { + } + + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + (void) locale; + bool ok; + double d = value.toDouble(&ok); + if (ok) { + return QString::number(d, 'f', m_precision); + } else { + return value.toString(); + } + } + +private: + int m_precision; + +}; + +// Delegate for table to display hours, minutes and seconds +class HMSDelegate : public QStyledItemDelegate { + +public: + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + (void) locale; + return Units::decimalHoursToHoursMinutesAndSeconds(value.toDouble()); + } + +}; + +// Delegate for table to display degrees, minutes and seconds +class DMSDelegate : public QStyledItemDelegate { + +public: + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + (void) locale; + return Units::decimalDegreesToDegreeMinutesAndSeconds(value.toDouble()); + } + +}; + +void RadioAstronomyGUI::LABData::read(QFile* file, float l, float b) +{ + m_l = l; + m_b = b; + m_vlsr.clear(); + m_temp.clear(); + QTextStream in(file); + + while (!in.atEnd()) + { + QString line = in.readLine().trimmed(); + if (!line.startsWith("%") && (line.size() > 0)) // Lines starting with % are comments + { + // 4 cols: v_lsr [km/s], T_B [K], freq. [Mhz], wavel. [cm] + QStringList cols = line.split(" ", Qt::SkipEmptyParts); + if (cols.size() == 4) + { + m_vlsr.append(cols[0].toFloat()); + m_temp.append(cols[1].toFloat()); + } + else + { + qDebug() << "RadioAstronomyGUI::parseLAB: Unexpected number of columns"; + } + } + } +} + +void RadioAstronomyGUI::LABData::toSeries(QLineSeries *series) +{ + series->clear(); + series->setName(QString("LAB l=%1 b=%2").arg(m_l).arg(m_b)); + for (int i = 0; i < m_vlsr.size(); i++) { + series->append(m_vlsr[i], m_temp[i]); + } +} + +void RadioAstronomyGUI::SensorMeasurements::init(const QString& name, bool visible) +{ + m_series = new QLineSeries(); + m_series->setName(name); + m_series->setVisible(visible); + m_yAxis = new QValueAxis(); + m_yAxis->setTitleText(name); + m_yAxis->setVisible(visible); + m_min = std::numeric_limits::max(); + m_max = -std::numeric_limits::max(); +} + +void RadioAstronomyGUI::SensorMeasurements::setName(const QString& name) +{ + if (m_series) { + m_series->setName(name); + } + if (m_yAxis) { + m_yAxis->setTitleText(name); + } +} + +void RadioAstronomyGUI::SensorMeasurements::clicked(bool checked) +{ + if (m_series) { + m_series->setVisible(checked); + } + if (m_yAxis) { + m_yAxis->setVisible(checked); + } +} + +void RadioAstronomyGUI::SensorMeasurements::append(SensorMeasurement *measurement) +{ + m_measurements.append(measurement); + addToSeries(measurement); +} + +void RadioAstronomyGUI::SensorMeasurements::addToSeries(SensorMeasurement *measurement) +{ + m_series->append(measurement->m_dateTime.toMSecsSinceEpoch(), measurement->m_value); + + m_max = std::max(m_max, measurement->m_value); + m_min = std::min(m_min, measurement->m_value); + if (m_min == m_max) { + // Axis isn't drawn properly if min and max are the same + m_yAxis->setRange(m_min*0.9, m_max*1.1); + } else { + m_yAxis->setRange(m_min, m_max); + } +} + +void RadioAstronomyGUI::SensorMeasurements::addAllToSeries() +{ + for (int i = 0; i < m_measurements.size(); i++) { + addToSeries(m_measurements[i]); + } +} + +void RadioAstronomyGUI::SensorMeasurements::clear() +{ + m_series->clear(); + qDeleteAll(m_measurements); + m_measurements.clear(); +} + +void RadioAstronomyGUI::SensorMeasurements::addToChart(QChart* chart, QDateTimeAxis* xAxis) +{ + chart->addSeries(m_series); + m_series->attachAxis(xAxis); + m_series->attachAxis(m_yAxis); +} + +void RadioAstronomyGUI::SensorMeasurements::setPen(const QPen& pen) +{ + m_series->setPen(pen); +} + +QValueAxis* RadioAstronomyGUI::SensorMeasurements::yAxis() const +{ + return m_yAxis; +} + +qreal RadioAstronomyGUI::SensorMeasurements::lastValue() +{ + if (m_measurements.size() > 0) { + return m_measurements.last()->m_value; + } else { + return 0.0; + } +} + +void RadioAstronomyGUI::resizePowerTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + // Trailing spaces are for sort arrow + int row = ui->powerTable->rowCount(); + ui->powerTable->setRowCount(row + 1); + ui->powerTable->setItem(row, POWER_COL_DATE, new QTableWidgetItem("15/04/2016")); + ui->powerTable->setItem(row, POWER_COL_TIME, new QTableWidgetItem("10:17:00")); + ui->powerTable->setItem(row, POWER_COL_POWER, new QTableWidgetItem("1.235-e5")); + ui->powerTable->setItem(row, POWER_COL_POWER_DB, new QTableWidgetItem("-100.0")); + ui->powerTable->setItem(row, POWER_COL_POWER_DBM, new QTableWidgetItem("-100.0")); + ui->powerTable->setItem(row, POWER_COL_TSYS, new QTableWidgetItem("3000")); + ui->powerTable->setItem(row, POWER_COL_TSYS0, new QTableWidgetItem("100")); + ui->powerTable->setItem(row, POWER_COL_TSOURCE, new QTableWidgetItem("300")); + ui->powerTable->setItem(row, POWER_COL_TB, new QTableWidgetItem("100000")); + ui->powerTable->setItem(row, POWER_COL_TSKY, new QTableWidgetItem("300")); + ui->powerTable->setItem(row, POWER_COL_FLUX, new QTableWidgetItem("100000.00")); + ui->powerTable->setItem(row, POWER_COL_SIGMA_T, new QTableWidgetItem("0.01")); + ui->powerTable->setItem(row, POWER_COL_SIGMA_S, new QTableWidgetItem("1000.0")); + ui->powerTable->setItem(row, POWER_COL_OMEGA_A, new QTableWidgetItem("0.000001")); + ui->powerTable->setItem(row, POWER_COL_OMEGA_S, new QTableWidgetItem("0.000001")); + ui->powerTable->setItem(row, POWER_COL_RA, new QTableWidgetItem("12h59m59.10s")); + ui->powerTable->setItem(row, POWER_COL_DEC, new QTableWidgetItem("-90d59\'59.00\"")); + ui->powerTable->setItem(row, POWER_COL_GAL_LAT, new QTableWidgetItem("-90.0")); + ui->powerTable->setItem(row, POWER_COL_GAL_LON, new QTableWidgetItem("359.0")); + ui->powerTable->setItem(row, POWER_COL_AZ, new QTableWidgetItem("359.0")); + ui->powerTable->setItem(row, POWER_COL_EL, new QTableWidgetItem("90.0")); + ui->powerTable->setItem(row, POWER_COL_VBCRS, new QTableWidgetItem("10.0")); + ui->powerTable->setItem(row, POWER_COL_VLSR, new QTableWidgetItem("10.0")); + ui->powerTable->setItem(row, POWER_COL_SOLAR_FLUX, new QTableWidgetItem("60.0")); + ui->powerTable->setItem(row, POWER_COL_AIR_TEMP, new QTableWidgetItem("20.0")); + ui->powerTable->setItem(row, POWER_COL_SENSOR_1, new QTableWidgetItem("1.0000000")); + ui->powerTable->setItem(row, POWER_COL_SENSOR_2, new QTableWidgetItem("1.0000000")); + ui->powerTable->resizeColumnsToContents(); + ui->powerTable->removeRow(row); +} + +void RadioAstronomyGUI::resizePowerMarkerTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + int row = ui->powerMarkerTable->rowCount(); + ui->powerMarkerTable->setRowCount(row + 1); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_NAME, new QTableWidgetItem("Max")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DATE, new QTableWidgetItem("15/04/2016")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_TIME, new QTableWidgetItem("10:17:00")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_VALUE, new QTableWidgetItem("1000.0")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_X, new QTableWidgetItem("1 23:59:59")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_Y, new QTableWidgetItem("1000.0")); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_TO, new QTableWidgetItem("Max")); + ui->powerMarkerTable->resizeColumnsToContents(); + ui->powerMarkerTable->removeRow(row); +} + +void RadioAstronomyGUI::resizeSpectrumMarkerTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + int row = ui->spectrumMarkerTable->rowCount(); + ui->spectrumMarkerTable->setRowCount(row + 1); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_NAME, new QTableWidgetItem("Max")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_FREQ, new QTableWidgetItem("1420.405000")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VALUE, new QTableWidgetItem("1000.0")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_X, new QTableWidgetItem("1420.405000")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_Y, new QTableWidgetItem("1000.0")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_TO, new QTableWidgetItem("M1")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VR, new QTableWidgetItem("-100.0")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R, new QTableWidgetItem("10.0")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_D, new QTableWidgetItem("10.0/10.0")); + QTableWidgetItem* check = new QTableWidgetItem(); + check->setFlags(Qt::ItemIsUserCheckable); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_PLOT_MAX, check); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R_MIN, new QTableWidgetItem("10.0")); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_V, new QTableWidgetItem("250.0")); + ui->spectrumMarkerTable->resizeColumnsToContents(); + ui->spectrumMarkerTable->removeRow(row); +} + +void RadioAstronomyGUI::calcSpectrumMarkerDelta() +{ + if (m_spectrumM1Valid && m_spectrumM2Valid) + { + qreal dx = m_spectrumM2X - m_spectrumM1X; + qreal dy = m_spectrumM2Y - m_spectrumM1Y; + + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "M1"); + } +} + +void RadioAstronomyGUI::calcPowerMarkerDelta() +{ + if (m_powerM1Valid && m_powerM2Valid) + { + qreal dx = m_powerM2X - m_powerM1X; + qreal dy = m_powerM2Y - m_powerM1Y; + + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "M1"); + } +} + +void RadioAstronomyGUI::calcPowerPeakDelta() +{ + qreal dx = m_powerMaxX - m_powerMinX; + qreal dy = m_powerMaxY - m_powerMinY; + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, dx); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, dy); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "Max"); +} + +void RadioAstronomyGUI::addToPowerSeries(FFTMeasurement *fft, bool skipCalcs) +{ + if ( ((m_settings.m_powerYUnits == RadioAstronomySettings::PY_DBFS) || fft->m_temp) // Only dBFS valid if no temp was calculated + && !((m_settings.m_powerYUnits == RadioAstronomySettings::PY_DBM) && (fft->m_tSys == 0.0f)) // dBm value not valid if temp is 0 + ) + { + qreal power; + switch (m_settings.m_powerYData) + { + case RadioAstronomySettings::PY_POWER: + switch (m_settings.m_powerYUnits) + { + case RadioAstronomySettings::PY_DBFS: + power = fft->m_totalPowerdBFS; + break; + case RadioAstronomySettings::PY_DBM: + power = fft->m_totalPowerdBm; + break; + case RadioAstronomySettings::PY_WATTS: + power = fft->m_totalPowerWatts; + break; + } + break; + case RadioAstronomySettings::PY_TSYS: + power = fft->m_tSys; + break; + case RadioAstronomySettings::PY_TSOURCE: + power = fft->m_tSource; + break; + case RadioAstronomySettings::PY_FLUX: + switch (m_settings.m_powerYUnits) + { + case RadioAstronomySettings::PY_SFU: + power = Units::wattsPerMetrePerHertzToSolarFluxUnits(fft->m_flux); + break; + case RadioAstronomySettings::PY_JANSKY: + power = Units::wattsPerMetrePerHertzToJansky(fft->m_flux); + break; + } + break; + } + QDateTime dateTime = fft->m_dateTime; + + if (m_powerSeries->count() == 0) + { + m_powerMin = power; + m_powerMax = power; + } + else + { + m_powerMin = std::min(power, m_powerMin); + m_powerMax = std::max(power, m_powerMax); + } + m_powerSeries->append(dateTime.toMSecsSinceEpoch(), power); + if (!skipCalcs) + { + if (m_settings.m_powerAutoscale) + { + double max = m_powerMax + (m_powerMax-m_powerMin)*0.2; // Add 20% space for markers + double range = max - m_powerMin; + blockApplySettings(true); + ui->powerRange->setValue(range); + ui->powerReference->setValue(max); + blockApplySettings(false); + } + } + + if (m_settings.m_powerYData == RadioAstronomySettings::PY_TSYS) { + m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), fft->m_tSys0); + } else if (m_settings.m_powerYData == RadioAstronomySettings::PY_DBM) { + m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), Astronomy::noisePowerdBm(fft->m_tSys0, fft->m_sampleRate)); + } else if (m_settings.m_powerYData == RadioAstronomySettings::PY_WATTS) { + m_powerTsys0Series->append(dateTime.toMSecsSinceEpoch(), Astronomy::m_boltzmann * fft->m_tSys0 * fft->m_sampleRate); + } + + if (!m_powerPeakValid) + { + m_powerPeakValid = true; + m_powerMinY = power; + m_powerMinX = dateTime.toMSecsSinceEpoch(); + m_powerMaxY = power; + m_powerMaxX = dateTime.toMSecsSinceEpoch(); + m_powerPeakSeries->clear(); + m_powerPeakSeries->append(m_powerMaxX, m_powerMaxY); + m_powerPeakSeries->append(m_powerMaxX, m_powerMaxY); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMaxX); + if (!skipCalcs) + { + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMaxY); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMinY); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_X)->setData(Qt::DisplayRole, 0.0); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_Y)->setData(Qt::DisplayRole, 0.0); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DELTA_TO)->setData(Qt::DisplayRole, "Max"); + } + } + + if (power > m_powerMaxY) + { + m_powerMaxY = power; + m_powerMaxX = dateTime.toMSecsSinceEpoch(); + m_powerPeakSeries->replace(0, m_powerMaxX, m_powerMaxY); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMaxX); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMaxY); + calcPowerPeakDelta(); + } + else if (power < m_powerMinY) + { + m_powerMinY = power; + m_powerMinX = dateTime.toMSecsSinceEpoch(); + m_powerPeakSeries->replace(1, m_powerMinX, m_powerMinY); + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerMinX); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerMinY); + calcPowerPeakDelta(); + } + + // Update markers (E.g. if scale changes) + int c = m_powerSeries->count(); + if (c >= 2) + { + QPointF p1 = m_powerSeries->at(c-2); + QPointF p2 = m_powerSeries->at(c-1); + if (m_powerM1Valid && (m_powerM1X >= p1.x()) && (m_powerM1X < p2.x())) + { + m_powerM1Y = Interpolation::interpolate(p1.x(), p1.y(), p2.x(), p2.y(), m_powerM1X); + m_powerMarkerSeries->insert(0, QPointF(dateTime.toMSecsSinceEpoch(), m_powerM1Y)); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM1Y); + calcPowerMarkerDelta(); + } + if (m_powerM2Valid && (m_powerM2X >= p1.x()) && (m_powerM2X < p2.x())) + { + m_powerM2Y = Interpolation::interpolate(p1.x(), p1.y(), p2.x(), p2.y(), m_powerM2X); + m_powerMarkerSeries->append(dateTime.toMSecsSinceEpoch(), m_powerM2Y); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM2Y); + calcPowerMarkerDelta(); + } + } + + if (!skipCalcs) + { + // Set X axis format to include date if measurements span over different days + // Seems there's a QT bug here, if we call m_powerXAxis->setFormat, the chart isn't + // redrawn properly, so we have to redraw the whole thing + QDateTime minDateTime = m_powerXAxis->min(); + QDateTime maxDateTime = m_powerXAxis->max(); + bool sameDay = minDateTime.date() == maxDateTime.date(); + if (!sameDay && m_powerXAxisSameDay) + { + m_powerXAxisSameDay = true; + QTimer::singleShot(100, this, SLOT(plotPowerChart())); + } + } + + if (!skipCalcs && ui->powerShowAvg->isChecked()) { + calcAverages(); + } + } + if (m_powerSeries->count() <= 1) // Don't check skipCalcs here, as that will be set for first data + { + ui->powerStartTime->setMinimumDateTime(fft->m_dateTime); + ui->powerEndTime->setMinimumDateTime(fft->m_dateTime); + if (m_settings.m_powerAutoscale) + { + ui->powerStartTime->setDateTime(fft->m_dateTime); + ui->powerEndTime->setDateTime(fft->m_dateTime); + } + } + if (!skipCalcs) + { + ui->powerStartTime->setMaximumDateTime(fft->m_dateTime); + ui->powerEndTime->setMaximumDateTime(fft->m_dateTime); + if (m_settings.m_powerAutoscale) { + ui->powerEndTime->setDateTime(fft->m_dateTime); + } + } +} + +double RadioAstronomyGUI::degreesToSteradian(double deg) const +{ + // https://en.wikipedia.org/wiki/Steradian - Other properties + double s = sin(Units::degreesToRadians(deg) / 4.0); + return 4.0 * M_PI * s * s; +} + +double RadioAstronomyGUI::hpbwToSteradians(double hpbw) const +{ + // https://www.cv.nrao.edu/~sransom/web/Ch3.html#E118 + double theta = Units::degreesToRadians(hpbw); + return theta * theta * M_PI / (4.0 * M_LN2); +} + +double RadioAstronomyGUI::calcOmegaA() const +{ + return hpbwToSteradians(m_beamWidth); +} + +double RadioAstronomyGUI::calcOmegaS() const +{ + if (m_settings.m_sourceType == RadioAstronomySettings::UNKNOWN) + { + return 0.0; + } + else if (m_settings.m_sourceType == RadioAstronomySettings::EXTENDED) + { + return calcOmegaA(); + } + else + { + return m_settings.m_omegaSUnits == RadioAstronomySettings::STERRADIANS ? m_settings.m_omegaS : degreesToSteradian(m_settings.m_omegaS); + } +} + +double RadioAstronomyGUI::beamFillingFactor() const +{ + if (m_settings.m_sourceType == RadioAstronomySettings::EXTENDED) + { + return 1.0; + } + else + { + // https://www.cv.nrao.edu/~sransom/web/Ch3.html#E55 + return calcOmegaS() / calcOmegaA(); + } +} + +void RadioAstronomyGUI::powerMeasurementReceived(FFTMeasurement *fft, bool skipCalcs) +{ + int row = ui->powerTable->rowCount(); + ui->powerTable->setRowCount(row + 1); + + QTableWidgetItem* dateItem = new QTableWidgetItem(); + QTableWidgetItem* timeItem = new QTableWidgetItem(); + QTableWidgetItem* powerItem = new QTableWidgetItem(); + QTableWidgetItem* powerDBItem = new QTableWidgetItem(); + QTableWidgetItem* powerdBmItem = new QTableWidgetItem(); + QTableWidgetItem* tSysItem = new QTableWidgetItem(); + QTableWidgetItem* tSys0Item = new QTableWidgetItem(); + QTableWidgetItem* tSourceItem = new QTableWidgetItem(); + QTableWidgetItem* tBItem = new QTableWidgetItem(); + QTableWidgetItem* tSkyItem = new QTableWidgetItem(); + QTableWidgetItem* fluxItem = new QTableWidgetItem(); + QTableWidgetItem* sigmaTItem = new QTableWidgetItem(); + QTableWidgetItem* sigmaSItem = new QTableWidgetItem(); + QTableWidgetItem* omegaAItem = new QTableWidgetItem(); + QTableWidgetItem* omegaSItem = new QTableWidgetItem(); + QTableWidgetItem* raItem = new QTableWidgetItem(); + QTableWidgetItem* decItem = new QTableWidgetItem(); + QTableWidgetItem* lonItem = new QTableWidgetItem(); + QTableWidgetItem* latItem = new QTableWidgetItem(); + QTableWidgetItem* azItem = new QTableWidgetItem(); + QTableWidgetItem* elItem = new QTableWidgetItem(); + QTableWidgetItem* vBCRSItem = new QTableWidgetItem(); + QTableWidgetItem* vLSRItem = new QTableWidgetItem(); + QTableWidgetItem* solarFluxItem = new QTableWidgetItem(); + QTableWidgetItem* airTempItem = new QTableWidgetItem(); + QTableWidgetItem* sensor1Item = new QTableWidgetItem(); + QTableWidgetItem* sensor2Item = new QTableWidgetItem(); + + ui->powerTable->setItem(row, POWER_COL_DATE, dateItem); + ui->powerTable->setItem(row, POWER_COL_TIME, timeItem); + ui->powerTable->setItem(row, POWER_COL_POWER, powerItem); + ui->powerTable->setItem(row, POWER_COL_POWER_DB, powerDBItem); + ui->powerTable->setItem(row, POWER_COL_POWER_DBM, powerdBmItem); + ui->powerTable->setItem(row, POWER_COL_TSYS, tSysItem); + ui->powerTable->setItem(row, POWER_COL_TSYS0, tSys0Item); + ui->powerTable->setItem(row, POWER_COL_TSOURCE, tSourceItem); + ui->powerTable->setItem(row, POWER_COL_TB, tBItem); + ui->powerTable->setItem(row, POWER_COL_TSKY, tSkyItem); + ui->powerTable->setItem(row, POWER_COL_FLUX, fluxItem); + ui->powerTable->setItem(row, POWER_COL_SIGMA_T, sigmaTItem); + ui->powerTable->setItem(row, POWER_COL_SIGMA_S, sigmaSItem); + ui->powerTable->setItem(row, POWER_COL_OMEGA_A, omegaAItem); + ui->powerTable->setItem(row, POWER_COL_OMEGA_S, omegaSItem); + ui->powerTable->setItem(row, POWER_COL_RA, raItem); + ui->powerTable->setItem(row, POWER_COL_DEC, decItem); + ui->powerTable->setItem(row, POWER_COL_GAL_LON, lonItem); + ui->powerTable->setItem(row, POWER_COL_GAL_LAT, latItem); + ui->powerTable->setItem(row, POWER_COL_AZ, azItem); + ui->powerTable->setItem(row, POWER_COL_EL, elItem); + ui->powerTable->setItem(row, POWER_COL_VBCRS, vBCRSItem); + ui->powerTable->setItem(row, POWER_COL_VLSR, vLSRItem); + ui->powerTable->setItem(row, POWER_COL_SOLAR_FLUX, solarFluxItem); + ui->powerTable->setItem(row, POWER_COL_AIR_TEMP, airTempItem); + ui->powerTable->setItem(row, POWER_COL_SENSOR_1, sensor1Item); + ui->powerTable->setItem(row, POWER_COL_SENSOR_2, sensor2Item); + + QDateTime dateTime = fft->m_dateTime; + dateItem->setData(Qt::DisplayRole, dateTime.date()); + timeItem->setData(Qt::DisplayRole, dateTime.time()); + + powerItem->setData(Qt::DisplayRole, fft->m_totalPower); + powerDBItem->setData(Qt::DisplayRole, fft->m_totalPowerdBFS); + if (fft->m_tSys != 0.0f) { + powerdBmItem->setData(Qt::DisplayRole, fft->m_totalPowerdBm); + } + if (fft->m_temp) { + updatePowerColumns(row, fft); + } + if (fft->m_coordsValid) + { + raItem->setData(Qt::DisplayRole, fft->m_ra); + decItem->setData(Qt::DisplayRole, fft->m_dec); + latItem->setData(Qt::DisplayRole, fft->m_b); + lonItem->setData(Qt::DisplayRole, fft->m_l); + azItem->setData(Qt::DisplayRole, fft->m_azimuth); + elItem->setData(Qt::DisplayRole, fft->m_elevation); + vBCRSItem->setData(Qt::DisplayRole, fft->m_vBCRS); + vLSRItem->setData(Qt::DisplayRole, fft->m_vLSR); + tSkyItem->setData(Qt::DisplayRole, fft->m_skyTemp); + } + solarFluxItem->setData(Qt::DisplayRole, fft->m_solarFlux); + airTempItem->setData(Qt::DisplayRole, fft->m_airTemp); + sensor1Item->setData(Qt::DisplayRole, fft->m_sensor[0]); + sensor2Item->setData(Qt::DisplayRole, fft->m_sensor[1]); + + addToPowerSeries(fft, skipCalcs); +} + +void RadioAstronomyGUI::powerAutoscale() +{ + if (m_settings.m_powerAutoscale) + { + on_powerAutoscaleX_clicked(); + on_powerAutoscaleY_clicked(); + } +} + +// Scale X and Y axis according to min and max values +void RadioAstronomyGUI::on_powerAutoscale_toggled(bool checked) +{ + m_settings.m_powerAutoscale = checked; + ui->powerAutoscaleX->setEnabled(!m_settings.m_powerAutoscale); + ui->powerAutoscaleY->setEnabled(!m_settings.m_powerAutoscale); + ui->powerReference->setEnabled(!m_settings.m_powerAutoscale); + ui->powerRange->setEnabled(!m_settings.m_powerAutoscale); + ui->powerStartTime->setEnabled(!m_settings.m_powerAutoscale); + ui->powerEndTime->setEnabled(!m_settings.m_powerAutoscale); + powerAutoscale(); + applySettings(); +} + +// Scale Y axis according to min and max values +void RadioAstronomyGUI::on_powerAutoscaleY_clicked() +{ + if (m_powerYAxis) + { + double min = m_powerMin; + double max = m_powerMax + (m_powerMax-m_powerMin)*0.2; // Add 20% space for markers + double range = m_powerMax - m_powerMin; + range = std::max(0.1, range); // Don't be smaller than minimum value we can set in GUI + + m_powerYAxis->setRange(min, max); + ui->powerRange->setValue(range); // Call before setting reference, so number of decimals are adjusted + ui->powerReference->setValue(max); + } +} + +// Scale X axis according to min and max values in series +void RadioAstronomyGUI::on_powerAutoscaleX_clicked() +{ + if (m_powerSeries && (m_powerSeries->count() > 0)) + { + QDateTime start = QDateTime::fromMSecsSinceEpoch(m_powerSeries->at(0).x()); + QDateTime end = QDateTime::fromMSecsSinceEpoch(m_powerSeries->at(m_powerSeries->count()-1).x()); + ui->powerStartTime->setDateTime(start); + ui->powerEndTime->setDateTime(end); + } +} + +void RadioAstronomyGUI::on_powerReference_valueChanged(double value) +{ + m_settings.m_powerReference = value; + if (m_powerYAxis) { + m_powerYAxis->setRange(m_settings.m_powerReference - m_settings.m_powerRange, m_settings.m_powerReference); + } + applySettings(); +} + +void RadioAstronomyGUI::on_powerRange_valueChanged(double value) +{ + m_settings.m_powerRange = value; + if (m_settings.m_powerRange <= 1.0) + { + ui->powerRange->setSingleStep(0.1); + ui->powerRange->setDecimals(2); + ui->powerReference->setDecimals(2); + } + else + { + ui->powerRange->setSingleStep(1.0); + ui->powerRange->setDecimals(1); + ui->powerReference->setDecimals(1); + } + if (m_powerYAxis) { + m_powerYAxis->setRange(m_settings.m_powerReference - m_settings.m_powerRange, m_settings.m_powerReference); + } + applySettings(); +} + +void RadioAstronomyGUI::on_powerStartTime_dateTimeChanged(QDateTime value) +{ + if (m_powerXAxis) { + m_powerXAxis->setMin(value); + } +} + +void RadioAstronomyGUI::on_powerEndTime_dateTimeChanged(QDateTime value) +{ + if (m_powerXAxis) { + m_powerXAxis->setMax(value); + } +} + +// Columns in table reordered +void RadioAstronomyGUI::powerTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_powerTableColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void RadioAstronomyGUI::powerTable_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_powerTableColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void RadioAstronomyGUI::powerTableColumnSelectMenu(QPoint pos) +{ + powerTableMenu->popup(ui->powerTable->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void RadioAstronomyGUI::powerTableColumnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->powerTable->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu item +QAction *RadioAstronomyGUI::createCheckableItem(QString &text, int idx, bool checked, const char *slot) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, slot); + return action; +} + +RadioAstronomyGUI* RadioAstronomyGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + RadioAstronomyGUI* gui = new RadioAstronomyGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void RadioAstronomyGUI::destroy() +{ + delete this; +} + +void RadioAstronomyGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray RadioAstronomyGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool RadioAstronomyGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void RadioAstronomyGUI::updatePipeList() +{ + QString currentText = ui->starTracker->currentText(); + ui->starTracker->blockSignals(true); + ui->starTracker->clear(); + QList::const_iterator it = m_availablePipes.begin(); + + for (int i = 0; it != m_availablePipes.end(); ++it, i++) { + ui->starTracker->addItem(it->getName()); + } + + if (currentText.isEmpty()) + { + if (m_availablePipes.size() > 0) { + ui->starTracker->setCurrentIndex(0); + } + } + else + { + ui->starTracker->setCurrentIndex(ui->starTracker->findText(currentText)); + } + ui->starTracker->blockSignals(false); + + QString newText = ui->starTracker->currentText(); + if (currentText != newText) + { + m_settings.m_starTracker = newText; + applySettings(); + } +} + +static bool withinBeam(float a, float b, float beamWidth) +{ + return abs(a-b) < beamWidth; +} + +bool RadioAstronomyGUI::handleMessage(const Message& message) +{ + if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + if (m_settings.m_tempGalLink) { + calcGalacticBackgroundTemp(); + } + updateTSys0(); + return true; + } + else if (PipeEndPoint::MsgReportPipes::match(message)) + { + PipeEndPoint::MsgReportPipes& report = (PipeEndPoint::MsgReportPipes&) message; + m_availablePipes = report.getAvailablePipes(); + updatePipeList(); + return true; + } + else if (MainCore::MsgStarTrackerTarget::match(message)) + { + MainCore::MsgStarTrackerTarget& msg = (MainCore::MsgStarTrackerTarget&)message; + SWGSDRangel::SWGStarTrackerTarget *target = msg.getSWGStarTrackerTarget(); + m_coordsValid = true; + m_ra = target->getRa(); + m_dec = target->getDec(); + m_azimuth = target->getAzimuth(); + m_elevation = target->getElevation(); + m_l = target->getL(); + m_b = target->getB(); + m_vBCRS = target->getEarthRotationVelocity() + target->getEarthOrbitVelocityBcrs(); + m_vLSR = target->getSunVelocityLsr() + m_vBCRS; + m_solarFlux = target->getSolarFlux(); + double airTemp = target->getAirTemperature(); + m_skyTemp = target->getSkyTemperature(); + m_beamWidth = target->getHpbw(); + + if (m_settings.m_elevationLink) { + ui->elevation->setValue(m_elevation); + } + if (m_settings.m_tempAirLink) { + ui->tempAir->setValue(airTemp); + } + SensorMeasurement* sm = new SensorMeasurement(QDateTime::currentDateTime(), airTemp); + m_airTemps.append(sm); + updateTSys0(); + updateOmegaA(); + + return true; + } + else if (RadioAstronomy::MsgConfigureRadioAstronomy::match(message)) + { + const RadioAstronomy::MsgConfigureRadioAstronomy& cfg = (RadioAstronomy::MsgConfigureRadioAstronomy&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (RadioAstronomy::MsgMeasurementProgress::match(message)) + { + RadioAstronomy::MsgMeasurementProgress& progress = (RadioAstronomy::MsgMeasurementProgress&) message; + ui->measurementProgress->setValue(progress.getPercentComplete()); + return true; + } + else if (RadioAstronomy::MsgSweepStatus::match(message)) + { + RadioAstronomy::MsgSweepStatus& status = (RadioAstronomy::MsgSweepStatus&) message; + ui->sweepStatus->setText(status.getStatus()); + return true; + } + else if (RadioAstronomy::MsgSweepComplete::match(message)) + { + ui->startStop->blockSignals(true); + ui->startStop->setChecked(false); + ui->startStop->blockSignals(false); + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + return true; + } + else if (RadioAstronomy::MsgCalComplete::match(message)) + { + RadioAstronomy::MsgCalComplete& measurement = (RadioAstronomy::MsgCalComplete&) message; + calCompletetReceived(measurement); + return true; + } + else if (RadioAstronomy::MsgFFTMeasurement::match(message)) + { + RadioAstronomy::MsgFFTMeasurement& measurement = (RadioAstronomy::MsgFFTMeasurement&) message; + fftMeasurementReceived(measurement); + if (m_settings.m_runMode == RadioAstronomySettings::SINGLE) + { + ui->startStop->blockSignals(true); + ui->startStop->setChecked(false); + ui->startStop->blockSignals(false); + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + } + return true; + } + else if (RadioAstronomy::MsgSensorMeasurement::match(message)) + { + RadioAstronomy::MsgSensorMeasurement& measurement = (RadioAstronomy::MsgSensorMeasurement&) message; + sensorMeasurementReceived(measurement); + return true; + } + + return false; +} + +void RadioAstronomyGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void RadioAstronomyGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void RadioAstronomyGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +// Calculate Tsys0 - i.e. receiver noise temperature when there's no source signal, just unwanted noise +void RadioAstronomyGUI::updateTSys0() +{ + double tSys0 = calcTSys0(); + ui->tSys0->setText(QString("%1").arg(round(tSys0))); + double sigmaT = calcSigmaT(tSys0); + double sigmaS = calcSigmaS(tSys0); + ui->sigmaTSys0->setText(QString("%1").arg(sigmaT, 0, 'f', 1)); + ui->sigmaSSys0->setText(QString("%1").arg(sigmaS, 0, 'f', 1)); +} + +// Estimate of system noise temperature due to all sources of unwanted noise, from user settings +double RadioAstronomyGUI::calcTSys0() const +{ + return m_settings.m_tempRX + m_settings.m_tempCMB + m_settings.m_tempGal + m_settings.m_tempSP + m_settings.m_tempAtm; +} + +// Calculate measurement time +double RadioAstronomyGUI::calcTau() const +{ + return m_settings.m_integration / (m_settings.m_sampleRate / (double)m_settings.m_fftSize); +} + +double RadioAstronomyGUI::calcTau(const FFTMeasurement* fft) const +{ + return fft->m_integration / (fft->m_sampleRate / (double)fft->m_fftSize); +} + +// Calculate variation in Tsys due to random noise fluctuations, including receiver gain variations +// Minimum temp we can reliably detect will be ~5x this +// Uses practical total-power radiometer equation: https://www.cv.nrao.edu/~sransom/web/Ch3.html#E158 + +double RadioAstronomyGUI::calcSigmaT(double tSys) const +{ + double tau = calcTau(); + return tSys * sqrt(1.0/(m_settings.m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation); +} + +double RadioAstronomyGUI::calcSigmaT(const FFTMeasurement* fft) const +{ + double tau = calcTau(fft); + return fft->m_tSys * sqrt(1.0/(fft->m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation); +} + +// Calculate variations in flux due to random noise fluctuations, including receiver gain variations +// Minimum flux we can reliably detect will be ~5x this + +double RadioAstronomyGUI::calcSigmaS(double tSys) const +{ + double omegaA = hpbwToSteradians(m_beamWidth); + double lambda = Astronomy::m_speedOfLight / (double)m_centerFrequency; + double flux = 2.0 * Astronomy::m_boltzmann * tSys * omegaA / (lambda * lambda); // Should we use Aeff here instead? + double tau = calcTau(); + double sigma = flux * sqrt(1.0/(m_settings.m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation); + return Units::wattsPerMetrePerHertzToJansky(sigma); +} + +double RadioAstronomyGUI::calcSigmaS(const FFTMeasurement* fft) const +{ + double omegaA = fft->m_omegaA; + double lambda = Astronomy::m_speedOfLight / (double)fft->m_centerFrequency; + double flux = 2.0 * Astronomy::m_boltzmann * fft->m_tSys * omegaA / (lambda * lambda); // Should we use Aeff here instead? + double tau = calcTau(fft); + double sigma = flux * sqrt(1.0/(fft->m_rfBandwidth * tau) + m_settings.m_gainVariation * m_settings.m_gainVariation); + return Units::wattsPerMetrePerHertzToJansky(sigma); +} + +// Calculate and display how long a single measurement will take +void RadioAstronomyGUI::updateIntegrationTime() +{ + double secs = calcTau(); + if (secs >= 60) { + ui->integrationTime->setText(QString("%1m").arg(secs/60, 0, 'f', 1)); + } else { + ui->integrationTime->setText(QString("%1s").arg(secs, 0, 'f', 1)); + } + updateTSys0(); +} + +// Limit bandwidth to be less than sample rate +void RadioAstronomyGUI::updateBWLimits() +{ + qint64 sr = (qint64) m_settings.m_sampleRate; + int digits = ceil(log10(sr+1)); + ui->rfBW->setValueRange(true, digits, 1000, sr); +} + +void RadioAstronomyGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void RadioAstronomyGUI::on_sampleRate_changed(qint64 value) +{ + float sr = value; + m_settings.m_sampleRate = sr; + updateBWLimits(); + updateIntegrationTime(); + applySettings(); +} + +void RadioAstronomyGUI::on_rfBW_changed(qint64 value) +{ + float bw = value; + m_channelMarker.setBandwidth(bw); + m_settings.m_rfBandwidth = bw; + applySettings(); +} + +void RadioAstronomyGUI::on_integration_changed(qint64 value) +{ + m_settings.m_integration = value; + updateIntegrationTime(); + applySettings(); +} + +void RadioAstronomyGUI::on_recalibrate_toggled(bool checked) +{ + m_settings.m_recalibrate = checked; + applySettings(); + if (checked) { + recalibrate(); + } +} + +void RadioAstronomyGUI::on_showCalSettings_clicked() +{ + RadioAstronomyCalibrationDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) { + applySettings(); + } +} + +// Start hot calibration +void RadioAstronomyGUI::on_startCalHot_clicked() +{ + if (ui->startStop->isChecked()) { + ui->startStop->click(); + } + m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartCal::create(true)); + ui->startCalHot->setStyleSheet("QToolButton { background-color : green; }"); +} + +// Start cold calibration +void RadioAstronomyGUI::on_startCalCold_clicked() +{ + if (ui->startStop->isChecked()) { + ui->startStop->click(); + } + m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartCal::create(false)); + ui->startCalCold->setStyleSheet("QToolButton { background-color : green; }"); +} + +// Clear all measurements (but not calibration data) +void RadioAstronomyGUI::clearData() +{ + ui->powerTable->setRowCount(0); + m_powerSeries->clear(); + m_powerPeakSeries->clear(); + m_powerMarkerSeries->clear(); + m_powerTsys0Series->clear(); + m_airTemps.clear(); + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_sensors[i].clear(); + } + for (int row = 0; row < POWER_MARKER_ROWS; row++) + { + for (int col = POWER_MARKER_COL_DATE; col <= POWER_MARKER_COL_DELTA_TO; col++) + { + ui->powerMarkerTable->item(row, col)->setText(""); + } + } + m_powerM1Valid = false; + m_powerM2Valid = false; + + qDeleteAll(m_fftMeasurements); + m_fftMeasurements.clear(); + m_fftSeries->clear(); + m_fftPeakSeries->clear(); + m_fftMarkerSeries->clear(); + for (int row = 0; row < SPECTRUM_MARKER_ROWS; row++) + { + for (int col = SPECTRUM_MARKER_COL_FREQ; col <= SPECTRUM_MARKER_COL_D; col++) + { + ui->spectrumMarkerTable->item(row, col)->setText(""); + } + } + m_spectrumM1Valid = false; + m_spectrumM2Valid = false; + clearLoSMarker("Max"); + clearLoSMarker("M1"); + clearLoSMarker("M2"); + + ui->spectrumIndex->setRange(0, 0); + ui->spectrumDateTime->setDateTime(QDateTime::currentDateTime()); + ui->powerMean->setText(""); + ui->powerRMS->setText(""); + ui->powerSD->setText(""); + plotPowerVsTimeChart(); // To ensure min/max/peaks are reset + + create2DImage(); + plotPowerChart(); + + ui->measurementProgress->setValue(0); + ui->sweepStatus->setText(""); +} + +// Clear calibration data +void RadioAstronomyGUI::clearCalData() +{ + delete m_calHot; + delete m_calCold; + delete m_calG; + m_calHot = nullptr; + m_calCold = nullptr; + m_calG = nullptr; + m_calHotSeries->clear(); + m_calColdSeries->clear(); + ui->calAvgDiff->setText(""); +} + +// deleteRowsComplete should be called after all rows are deleted +// Returns if the row being deleted is the currently displayed FFT +bool RadioAstronomyGUI::deleteRow(int row) +{ + ui->powerTable->removeRow(row); + delete m_fftMeasurements[row]; + m_fftMeasurements.removeAt(row); + return row == ui->spectrumIndex->value(); +} + +// Updates GUI after rows have been deleted +void RadioAstronomyGUI::deleteRowsComplete(bool deletedCurrent, int next) +{ + if (m_fftMeasurements.size() == 0) + { + clearData(); + } + else + { + if (deletedCurrent) { + ui->spectrumIndex->setValue(next); + } + plotPowerChart(); + powerAutoscale(); + } +} + +// Calculate average difference in hot and cold cal data - so we can easily validate results +void RadioAstronomyGUI::calcCalAvgDiff() +{ + if ((m_calHot && m_calCold) && (m_calHot->m_fftSize == m_calCold->m_fftSize)) + { + Real sum = 0.0f; + for (int i = 0; i < m_calHot->m_fftSize; i++) { + sum += CalcDb::dbPower(m_calHot->m_fftData[i]) - CalcDb::dbPower(m_calCold->m_fftData[i]); + } + Real avg = sum / m_calHot->m_fftSize; + ui->calAvgDiff->setText(QString::number(avg, 'f', 1)); + } + else + { + ui->calAvgDiff->setText(""); + } +} + +void RadioAstronomyGUI::calcCalibrationScaleFactors() +{ + if (m_calHot) + { + delete[] m_calG; + m_calG = new double[m_calHot->m_fftSize]; + // Calculate scaling factors from FFT mag to temperature + // FIXME: This assumes cal hot is fixed reference temp - E.g. 50Ohm term + for (int i = 0; i < m_calHot->m_fftSize; i++) { + m_calG[i] = (m_settings.m_tCalHot + m_settings.m_tempRX) / m_calHot->m_fftData[i]; + } + } +} + +void RadioAstronomyGUI::calibrate() +{ + if (m_calHotSeries) + { + calcCalibrationScaleFactors(); + calcCalTrx(); + calcCalTsp(); + + if (m_settings.m_recalibrate) + { + // Apply new calibration to existing measurements + recalibrate(); + } + } +} + +// Apply calibration to all existing measurements +void RadioAstronomyGUI::recalibrate() +{ + for (int i = 0; i < m_fftMeasurements.size(); i++) + { + FFTMeasurement* fft = m_fftMeasurements[i]; + // Recalibrate + calcFFTTemperatures(fft); + calcFFTTotalTemperature(fft); + // Update table + if (fft->m_tSys != 0.0f) { + ui->powerTable->item(i, POWER_COL_POWER_DBM)->setData(Qt::DisplayRole, fft->m_totalPowerdBm); + } + if (fft->m_temp) { + updatePowerColumns(i, fft); + } + } + // Update charts + plotFFTMeasurement(); + plotPowerChart(); +} + +// Calculate Trx using Y-factor method +void RadioAstronomyGUI::calcCalTrx() +{ + if ((m_calHot && m_calCold) && (m_calHot->m_fftSize == m_calCold->m_fftSize)) + { + // y=Ph/Pc + double sumH = 0.0; + double sumC = 0.0; + for (int i = 0; i < m_calHot->m_fftSize; i++) + { + sumH += m_calHot->m_fftData[i]; + sumC += m_calCold->m_fftData[i]; + } + double y = sumH/sumC; + // Use y to calculate Trx, which should be the same for both calibration points + double Trx = (m_settings.m_tCalHot - (m_settings.m_tCalCold * y)) / (y - 1.0); + ui->calYFactor->setText(QString::number(y, 'f', 2)); + ui->calTrx->setText(QString::number(Trx, 'f', 1)); + } + else + { + ui->calYFactor->setText(""); + ui->calTrx->setText(""); + } +} + +// Estimate spillover temperature (This is typically very Az/El depenedent as ground noise will vary) +void RadioAstronomyGUI::calcCalTsp() +{ + if (!ui->calTrx->text().isEmpty() && !ui->calTsky->text().isEmpty() && !ui->calYFactor->text().isEmpty()) + { + double Trx = ui->calTrx->text().toDouble(); + double Tsky = ui->calTsky->text().toDouble(); + double y = ui->calYFactor->text().toDouble(); + double atmosphericAbsorbtion = std::exp(-m_settings.m_zenithOpacity/cos(Units::degreesToRadians(90.0f - m_settings.m_elevation))); + + double Tsp = (m_settings.m_tCalHot + Trx) / y - (Tsky*atmosphericAbsorbtion) - m_settings.m_tempAtm - Trx; + + ui->calTsp->setText(QString::number(Tsp, 'f', 1)); + } + else + { + ui->calTsp->setText(""); + } +} + +void RadioAstronomyGUI::on_clearData_clicked() +{ + clearData(); +} + +void RadioAstronomyGUI::on_clearCal_clicked() +{ + clearCalData(); +} + +// Save power data in table to a CSV file +void RadioAstronomyGUI::on_savePowerData_clicked() +{ + // Get filename to save to + QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + QFile file(fileNames[0]); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0])); + return; + } + QTextStream out(&file); + + // Create a CSV file from the values in the table + for (int i = 0; i < ui->powerTable->horizontalHeader()->count(); i++) + { + QString text = ui->powerTable->horizontalHeaderItem(i)->text(); + out << text << ","; + } + out << "\n"; + for (int i = 0; i < ui->powerTable->rowCount(); i++) + { + for (int j = 0; j < ui->powerTable->horizontalHeader()->count(); j++) + { + out << ui->powerTable->item(i,j)->data(Qt::DisplayRole).toString() << ","; + } + out << "\n"; + } + } + } +} + +// Create a hash mapping from column name to array index +QHash RadioAstronomyGUI::csvHeadersToHash(QStringList cols) +{ + QHash hash; + for (int i = 0; i < cols.size(); i++) { + hash.insert(cols[i], i); + } + return hash; +} + +// Get data from column with given name, if available +QString RadioAstronomyGUI::csvData(QHash hash, QStringList cols, QString col) +{ + QString s; + if (hash.contains(col)) + { + int idx = hash[col]; + if (idx < cols.size()) { + s = cols[idx]; + } + } + return s; +} + +bool RadioAstronomyGUI::hasNeededFFTData(QHash hash) +{ + return hash.contains("FFT Size") && hash.contains("Data"); +} + +// Write FFTMeasurement to a stream +void RadioAstronomyGUI::saveFFT(QTextStream& out, const FFTMeasurement* fft) +{ + out << fft->m_dateTime.toString(); + out << ","; + out << fft->m_centerFrequency; + out << ","; + out << fft->m_sampleRate; + out << ","; + out << fft->m_integration; + out << ","; + out << fft->m_rfBandwidth; + out << ","; + out << fft->m_omegaA; + out << ","; + out << fft->m_omegaS; + out << ","; + out << fft->m_totalPower; + out << ","; + out << fft->m_totalPowerdBFS; + out << ","; + out << fft->m_totalPowerdBm; + out << ","; + out << fft->m_totalPowerWatts; + out << ","; + out << fft->m_tSys; + out << ","; + out << fft->m_tSys0; + out << ","; + out << fft->m_tSource; + out << ","; + out << fft->m_flux; + out << ","; + out << fft->m_sigmaT; + out << ","; + out << fft->m_sigmaS; + out << ","; + out << fft->m_tempMin; + out << ","; + out << fft->m_baseline; + out << ","; + out << fft->m_ra; + out << ","; + out << fft->m_dec; + out << ","; + out << fft->m_azimuth; + out << ","; + out << fft->m_elevation; + out << ","; + out << fft->m_l; + out << ","; + out << fft->m_b; + out << ","; + out << fft->m_vBCRS; + out << ","; + out << fft->m_vLSR; + out << ","; + out << fft->m_solarFlux; + out << ","; + out << fft->m_airTemp; + out << ","; + out << fft->m_skyTemp; + out << ","; + out << fft->m_sensor[0]; + out << ","; + out << fft->m_sensor[1]; + out << ","; + out << fft->m_fftSize; + out << ","; + for (int j = 0; j < fft->m_fftSize; j++) + { + out << fft->m_fftData[j]; + out << ","; + } + if (fft->m_snr) + { + for (int j = 0; j < fft->m_fftSize; j++) + { + out << fft->m_snr[j]; + out << ","; + } + } + if (fft->m_temp) + { + for (int j = 0; j < fft->m_fftSize; j++) + { + out << fft->m_temp[j]; + out << ","; + } + } + out << "\n"; +} + +// Create a FFTMeasurement from data read from CSV file +RadioAstronomyGUI::FFTMeasurement* RadioAstronomyGUI::loadFFT(QHash hash, QStringList cols) +{ + int fftSize = csvData(hash, cols, "FFT Size").toInt(); + int fftDataIdx = hash["Data"]; + if ((fftSize > 0) && (cols.size() >= fftDataIdx + fftSize)) + { + FFTMeasurement* fft = new FFTMeasurement(); + fft->m_dateTime = QDateTime::fromString(csvData(hash, cols, "Date Time")); + fft->m_centerFrequency = csvData(hash, cols, "Centre Freq").toLongLong(); + fft->m_sampleRate = csvData(hash, cols, "Sample Rate").toInt(); + fft->m_integration = csvData(hash, cols, "Integration").toInt(); + fft->m_rfBandwidth = csvData(hash, cols, "Bandwidth").toInt(); + fft->m_omegaA = csvData(hash, cols, "OmegaA").toFloat(); + fft->m_omegaS = csvData(hash, cols, "OmegaS").toFloat(); + + fft->m_fftSize = fftSize; + fft->m_fftData = new Real[fftSize]; + fft->m_db = new Real[fftSize]; + for (int i = 0; i < fftSize; i++) + { + fft->m_fftData[i] = cols[fftDataIdx+i].toFloat(); + fft->m_db[i] = (Real)CalcDb::dbPower(fft->m_fftData[i]); + } + if (cols.size() >= fftDataIdx + 2*fftSize) + { + fft->m_snr = new Real[fftSize]; + for (int i = 0; i < fftSize; i++) { + fft->m_snr[i] = cols[fftDataIdx+fftSize+i].toFloat(); + } + if (cols.size() >= fftDataIdx + 3*fftSize) + { + fft->m_temp = new Real[fftSize]; + for (int i = 0; i < fftSize; i++) { + fft->m_temp[i] = cols[fftDataIdx+2*fftSize+i].toFloat(); + } + } + } + fft->m_totalPower = csvData(hash, cols, "Power (FFT)").toFloat(); + fft->m_totalPowerdBFS = csvData(hash, cols, "Power (dBFS)").toFloat(); + fft->m_totalPowerdBm = csvData(hash, cols, "Power (dBm)").toFloat(); + fft->m_totalPowerWatts = csvData(hash, cols, "Power (Watts)").toFloat(); + fft->m_tSys = csvData(hash, cols, "Tsys").toFloat(); + fft->m_tSys0 = csvData(hash, cols, "Tsys0").toFloat(); + fft->m_tSource = csvData(hash, cols, "Tsource").toFloat(); + fft->m_flux = csvData(hash, cols, "Sv").toFloat(); + fft->m_sigmaT = csvData(hash, cols, "SigmaTsys").toFloat(); + fft->m_sigmaS = csvData(hash, cols, "SigmaSsys").toFloat(); + fft->m_tempMin = csvData(hash, cols, "Min Temp").toFloat(); + fft->m_baseline = (RadioAstronomySettings::SpectrumBaseline)csvData(hash, cols, "Baseline").toInt(); + + + fft->m_ra = csvData(hash, cols, "RA").toFloat(); + fft->m_dec = csvData(hash, cols, "Dec").toFloat(); + fft->m_azimuth = csvData(hash, cols, "Azimuth").toFloat(); + fft->m_elevation = csvData(hash, cols, "Elevation").toFloat(); + fft->m_l = csvData(hash, cols, "l").toFloat(); + fft->m_b = csvData(hash, cols, "b").toFloat(); + if ((fft->m_ra != 0.0) || (fft->m_dec != 0.0) || (fft->m_azimuth != 0.0) || (fft->m_elevation != 0.0) || (fft->m_l != 0.0) || (fft->m_b != 0.0)) { + fft->m_coordsValid = true; + } + fft->m_vBCRS = csvData(hash, cols, "vBCRS").toFloat(); + fft->m_vLSR = csvData(hash, cols, "vLSR").toFloat(); + fft->m_solarFlux = csvData(hash, cols, "Solar Flux").toFloat(); + fft->m_airTemp = csvData(hash, cols, "Air Temp").toFloat(); + fft->m_skyTemp = csvData(hash, cols, "Sky Temp").toFloat(); + fft->m_sensor[0] = csvData(hash, cols, "Sensor 1").toFloat(); + fft->m_sensor[1] = csvData(hash, cols, "Sensor 2").toFloat(); + + if (fft->m_rfBandwidth == 0) + { + fft->m_rfBandwidth = 0.9 * fft->m_sampleRate; // Older files don't have this column and we need a value for min + calcFFTTotalPower(fft); + /*calcFFTMinTemperature(fft); + calcFFTTotalTemperature(fft);*/ + } + return fft; + } + else + { + return nullptr; + } +} + +void RadioAstronomyGUI::on_saveSpectrumData_clicked() +{ + // Get filename to save to + QFileDialog fileDialog(nullptr, "Select file to save data to", "", "*.csv"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + QFile file(fileNames[0]); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0])); + return; + } + QTextStream out(&file); + + if (ui->spectrumChartSelect->currentIndex() == 0) + { + // Create a CSV file for all the spectrum data + out << "Date Time,Centre Freq,Sample Rate,Integration,Bandwidth,OmegaA,OmegaS,Power (FFT),Power (dBFS),Power (dBm),Power (Watts),Tsys,Tsys0,Tsource,Sv,SigmaTsys,SigmaSsys,Min Temp,Baseline,RA,Dec,Azimuth,Elevation,l,b,vBCRS,vLSR,Solar Flux,Air Temp,Sky Temp,Sensor 1,Sensor 2,FFT Size,Data\n"; + for (int i = 0; i < m_fftMeasurements.size(); i++) { + saveFFT(out, m_fftMeasurements[i]); + } + } + else + { + // Create a CSV file for calibration data + out << "Cal,Cal Temp,Date Time,Centre Freq,Sample Rate,Integration,Bandwidth,OmegaA,OmegaS,Power (FFT),Power (dBFS),Power (dBm),Power (Watts),Tsys,Tsys0,Tsource,Sv,SigmaTsys,SigmaSsys,Min Temp,Baseline,RA,Dec,Azimuth,Elevation,l,b,vBCRS,vLSR,Solar Flux,Air Temp,Sky Temp,Sensor 1,Sensor 2,FFT Size,Data\n"; + if (m_calHot) + { + out << "Hot,"; + out << m_settings.m_tCalHot; + out << ","; + saveFFT(out, m_calHot); + } + if (m_calCold) + { + out << "Cold,"; + out << m_settings.m_tCalCold; + out << ","; + saveFFT(out, m_calCold); + } + } + } + } +} + +void RadioAstronomyGUI::on_loadSpectrumData_clicked() +{ + // Get filename to load from + QFileDialog fileDialog(nullptr, "Select file to load data from", "", "*.csv"); + fileDialog.setAcceptMode(QFileDialog::AcceptOpen); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + QFile file(fileNames[0]); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to open file %1").arg(fileNames[0])); + return; + } + + // Get column names + QTextStream in(&file); + QString header = in.readLine(); + QStringList colNames = header.split(","); + QHash hash = csvHeadersToHash(colNames); + + if (ui->spectrumChartSelect->currentIndex() == 0) + { + // Load data from CSV file + if (hasNeededFFTData(hash)) + { + // Remove old data - we could support multiple series for comparison + clearData(); + // Read in FFT data from file + ui->spectrumIndex->blockSignals(true); // Prevent every spectrum from being displayed + while (!in.atEnd()) + { + QString row = in.readLine(); + QStringList cols = row.split(","); + FFTMeasurement* fft = loadFFT(hash, cols); + if (fft) { + addFFT(fft, true); + } + } + ui->spectrumIndex->blockSignals(false); + // Add data from FFT to sensor measurements + for (int i = 0; i < m_fftMeasurements.size(); i++) + { + SensorMeasurement* sm; + sm = new SensorMeasurement(m_fftMeasurements[i]->m_dateTime, m_fftMeasurements[i]->m_airTemp); + m_airTemps.append(sm); + for (int j = 0; j < RADIOASTRONOMY_SENSORS; j++) + { + sm = new SensorMeasurement(m_fftMeasurements[i]->m_dateTime, m_fftMeasurements[i]->m_sensor[j]); + m_sensors[j].append(sm); + } + } + // If we're loading data from scratch, autoscale both axis + if ((ui->spectrumCenterFreq->value() == 0.0) || m_settings.m_spectrumAutoscale) + { + on_spectrumAutoscaleY_clicked(); + on_spectrumAutoscaleX_clicked(); + } + // Ensure both charts are redrawn fully, as we've disabled some updates/calcs during load + on_spectrumIndex_valueChanged(m_fftMeasurements.size() - 1); // Don't call setValue, as it already has this value + plotPowerChart(); + // As signals were blocked above, power axis may not match up with GUI. Manually update + // Just calling autoscale will not work, as the GUI values may not change + on_powerStartTime_dateTimeChanged(ui->powerStartTime->dateTime()); + on_powerEndTime_dateTimeChanged(ui->powerEndTime->dateTime()); + on_powerRange_valueChanged(m_settings.m_powerRange); + on_powerReference_valueChanged(m_settings.m_powerReference); + } + } + else + { + // Load calibration data from CSV file + if (hasNeededFFTData(hash) && hash.contains("Cal")) + { + while (!in.atEnd()) + { + QString row = in.readLine(); + QStringList cols = row.split(","); + + QString calName = csvData(hash, cols, "Cal"); + + FFTMeasurement** calp = nullptr; + FFTMeasurement* cal = nullptr; + if (calName == "Hot") { + calp = &m_calHot; + } else if (calName == "Cold") { + calp = &m_calCold; + } else { + qDebug() << "RadioAstronomyGUI::on_loadSpectrumData_clicked: Skipping unknown calibration " << calName; + } + if (calp) + { + cal = loadFFT(hash, cols); + if (cal) + { + delete *calp; + *calp = cal; + qDebug() << "RadioAstronomyGUI::on_loadSpectrumData_clicked: Loaded calibration " << calName; + if (calName == "Cold") { + ui->calTsky->setText(QString::number(cal->m_skyTemp, 'f', 1)); + } + QString calTempString = csvData(hash, cols, "Cal Temp"); + bool ok; + double calTemp = calTempString.toDouble(&ok); + if (ok) + { + if (calName == "Cold") + { + ui->tCalColdSelect->setCurrentIndex(0); + ui->tCalCold->setValue(calTemp); + } + else + { + ui->tCalHotSelect->setCurrentIndex(0); + ui->tCalHot->setValue(calTemp); + } + } + } + } + } + calcCalAvgDiff(); + calibrate(); + plotCalMeasurements(); + } + else + { + QMessageBox::critical(this, "Radio Astronomy", QString("Missing required columns in file %1").arg(fileNames[0])); + return; + } + } + } + } +} + +void RadioAstronomyGUI::on_powerTable_cellDoubleClicked(int row, int column) +{ + if ((column >= POWER_COL_RA) && (column >= POWER_COL_EL)) + { + // Display target in Star Tracker + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *messageQueues = messagePipes.getMessageQueues(m_radioAstronomy, "startracker.display"); + if (messageQueues) + { + QList::iterator it = messageQueues->begin(); + + for (; it != messageQueues->end(); ++it) + { + SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplaySettings(); + QDateTime dt(ui->powerTable->item(row, POWER_COL_DATE)->data(Qt::DisplayRole).toDate(), + ui->powerTable->item(row, POWER_COL_TIME)->data(Qt::DisplayRole).toTime()); + swgSettings->setDateTime(new QString(dt.toString(Qt::ISODateWithMs))); + swgSettings->setAzimuth(ui->powerTable->item(row, POWER_COL_AZ)->data(Qt::DisplayRole).toFloat()); + swgSettings->setElevation(ui->powerTable->item(row, POWER_COL_EL)->data(Qt::DisplayRole).toFloat()); + (*it)->push(MainCore::MsgStarTrackerDisplaySettings::create(m_radioAstronomy, swgSettings)); + } + } + } + else + { + // Display in Spectrometer + ui->spectrumIndex->setValue(row); + } +} + +void RadioAstronomyGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void RadioAstronomyGUI::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_radioAstronomy->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(); +} + +RadioAstronomyGUI::RadioAstronomyGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::RadioAstronomyGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_basebandSampleRate(0), + m_centerFrequency(0), + m_tickCount(0), + m_powerChart(nullptr), + m_powerSeries(nullptr), + m_powerXAxis(nullptr), + m_powerYAxis(nullptr), + m_powerPeakSeries(nullptr), + m_powerMarkerSeries(nullptr), + m_powerTsys0Series(nullptr), + m_powerGaussianSeries(nullptr), + m_2DChart(nullptr), + m_2DXAxis(nullptr), + m_2DYAxis(nullptr), + m_2DMapIntensity(nullptr), + m_sweepIndex(0), + m_calChart(nullptr), + m_calXAxis(nullptr), + m_calYAxis(nullptr), + m_calHotSeries(nullptr), + m_calColdSeries(nullptr), + m_calHot(nullptr), + m_calCold(nullptr), + m_calG(nullptr), + m_fftChart(nullptr), + m_fftSeries(nullptr), + m_fftHlineSeries(nullptr), + m_fftPeakSeries(nullptr), + m_fftMarkerSeries(nullptr), + m_fftGaussianSeries(nullptr), + m_fftLABSeries(nullptr), + m_fftXAxis(nullptr), + m_fftYAxis(nullptr), + m_fftDopplerAxis(nullptr), + m_powerPeakValid(false), + m_powerM1Valid(false), + m_powerM2Valid(false), + m_spectrumM1Valid(false), + m_spectrumM2Valid(false), + m_coordsValid(false), + m_ra(0.0f), + m_dec(0.0f), + m_azimuth(0.0f), + m_elevation(0.0f), + m_l(0.0f), + m_b(0.0f), + m_vBCRS(0.0f), + m_vLSR(0.0f), + m_solarFlux(0.0f), + m_beamWidth(5.6f), + m_lLAB(0.0f), + m_bLAB(0.0f), + m_downloadingLAB(false) +{ + 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_radioAstronomy = reinterpret_cast(rxChannel); + m_radioAstronomy->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &RadioAstronomyGUI::downloadFinished); + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + + // Need to setValue before calling setValueRange, otherwise valueChanged is called + // overwriting the default settings (could also blockSignals) + // Also, set bandwidth before sampleRate + ui->rfBW->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->rfBW->setValue(m_settings.m_rfBandwidth); + ui->rfBW->setValueRange(true, 8, 1000, 99999999); + ui->sampleRate->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->sampleRate->setValue(m_settings.m_sampleRate); + ui->sampleRate->setValueRange(true, 8, 100000, 99999999); + ui->integration->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->integration->setValue(m_settings.m_integration); + ui->integration->setValueRange(true, 7, 1, 99999999); + + 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("Radio Astronomy"); + 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 + resizePowerTable(); + // Allow user to reorder columns + ui->powerTable->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->powerTable->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + powerTableMenu = new QMenu(ui->powerTable); + for (int i = 0; i < ui->powerTable->horizontalHeader()->count(); i++) + { + QString text = ui->powerTable->horizontalHeaderItem(i)->text(); + powerTableMenu->addAction(createCheckableItem(text, i, true, SLOT(powerTableColumnSelectMenuChecked()))); + } + ui->powerTable->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->powerTable->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(powerTableColumnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->powerTable->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(powerTable_sectionMoved(int, int, int))); + connect(ui->powerTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(powerTable_sectionResized(int, int, int))); + ui->powerTable->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->powerTable, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint))); + + ui->powerTable->setItemDelegateForColumn(POWER_COL_TIME, new TimeDelegate()); + //ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER, new DecimalDelegate(6)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER_DB, new DecimalDelegate(1)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_POWER_DBM, new DecimalDelegate(1)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_TSYS, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_TSYS0, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_TSOURCE, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_TB, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_TSKY, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_FLUX, new DecimalDelegate(2)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_SIGMA_T, new DecimalDelegate(2)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_SIGMA_S, new DecimalDelegate(1)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_RA, new HMSDelegate()); + ui->powerTable->setItemDelegateForColumn(POWER_COL_DEC, new DMSDelegate()); + ui->powerTable->setItemDelegateForColumn(POWER_COL_GAL_LAT, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_GAL_LON, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_AZ, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_EL, new DecimalDelegate(0)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_VBCRS, new DecimalDelegate(1)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_VLSR, new DecimalDelegate(1)); + ui->powerTable->setItemDelegateForColumn(POWER_COL_AIR_TEMP, new DecimalDelegate(1)); + + resizeSpectrumMarkerTable(); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_FREQ, new DecimalDelegate(6)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_VALUE, new DecimalDelegate(1)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_DELTA_X, new DecimalDelegate(6)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_DELTA_Y, new DecimalDelegate(1)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_VR, new DecimalDelegate(2)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_R, new DecimalDelegate(1)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_D, new DecimalDelegate(1)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_R_MIN, new DecimalDelegate(1)); + ui->spectrumMarkerTable->setItemDelegateForColumn(SPECTRUM_MARKER_COL_V, new DecimalDelegate(1)); + + // Create blank marker table + ui->spectrumMarkerTable->setRowCount(SPECTRUM_MARKER_ROWS); // 1 peak and two markers + for (int row = 0; row < SPECTRUM_MARKER_ROWS; row++) + { + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_NAME, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_FREQ, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VALUE, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_X, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_Y, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_DELTA_TO, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_VR, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_D, new QTableWidgetItem()); + // It seems clearing Qt::ItemIsUserCheckable doesn't remove the checkbox, so once set, we always have it + QTableWidgetItem* item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_PLOT_MAX, item); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_R_MIN, new QTableWidgetItem()); + ui->spectrumMarkerTable->setItem(row, SPECTRUM_MARKER_COL_V, new QTableWidgetItem()); + } + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_NAME)->setText("Max"); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_NAME)->setText("M1"); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_NAME)->setText("M2"); + connect(ui->spectrumMarkerTable, &QTableWidget::itemChanged, this, &RadioAstronomyGUI::spectrumMarkerTableItemChanged); + + resizePowerMarkerTable(); + ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_TIME, new TimeDelegate()); + ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_VALUE, new DecimalDelegate(1)); + ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_DELTA_X, new TimeDeltaDelegate()); + ui->powerMarkerTable->setItemDelegateForColumn(POWER_MARKER_COL_DELTA_Y, new DecimalDelegate(1)); + + // Create blank marker table + ui->powerMarkerTable->setRowCount(POWER_MARKER_ROWS); // 1 peak and two markers + for (int row = 0; row < POWER_MARKER_ROWS; row++) + { + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_NAME, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DATE, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_TIME, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_VALUE, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_X, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_Y, new QTableWidgetItem()); + ui->powerMarkerTable->setItem(row, POWER_MARKER_COL_DELTA_TO, new QTableWidgetItem()); + } + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MAX, POWER_MARKER_COL_NAME)->setText("Max"); + ui->powerMarkerTable->item(POWER_MARKER_ROW_PEAK_MIN, POWER_MARKER_COL_NAME)->setText("Min"); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_NAME)->setText("M1"); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_NAME)->setText("M2"); + + ui->sweepStartDateTime->setMinimumDateTime(QDateTime::currentDateTime()); + ui->spectrumDateTime->setDateTime(QDateTime::currentDateTime()); + + updateRotatorList(); + + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + + displaySettings(); + applySettings(true); + + create2DImage(); + + plotCalSpectrum(); + plotSpectrum(); + plotPowerChart(); +} + +void RadioAstronomyGUI::customContextMenuRequested(QPoint pos) +{ + QTableWidgetItem *item = ui->powerTable->itemAt(pos); + if (item) + { + QMenu* tableContextMenu = new QMenu(ui->powerTable); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + + // Copy cell contents to clipboard + QAction* copyAction = new QAction("Copy cell", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + + // Delete selected rows + QAction* delAction = new QAction("Delete rows", tableContextMenu); + connect(delAction, &QAction::triggered, this, [this]()->void { + QModelIndexList rowIndexes = ui->powerTable->selectionModel()->selectedRows(); + if (rowIndexes.size() > 0) + { + // Delete in reverse row order + std::vector rows; + foreach (auto rowIndex, rowIndexes) { + rows.push_back(rowIndex.row()); + } + std::sort(rows.begin(), rows.end(), std::greater()); + bool deletedCurrent = false; + int next; + foreach (auto row, rows) { + next = row - 1; + if (deleteRow(row)) { + deletedCurrent = true; + } + } + deleteRowsComplete(deletedCurrent, next); + } + }); + tableContextMenu->addAction(delAction); + + // Update rows with new Tsys0 and baseline + QAction* updateTSysAction = new QAction(QString("Update Tsys0 / baseline / %1").arg(QChar(937)), tableContextMenu); + connect(updateTSysAction, &QAction::triggered, this, [this]()->void { + QModelIndexList rowIndexes = ui->powerTable->selectionModel()->selectedRows(); + if (rowIndexes.size() > 0) + { + foreach (auto rowIndex, rowIndexes) + { + int row = rowIndex.row(); + m_fftMeasurements[row]->m_tSys0 = calcTSys0(); + m_fftMeasurements[row]->m_baseline = m_settings.m_spectrumBaseline; + m_fftMeasurements[row]->m_omegaA = calcOmegaA(); + m_fftMeasurements[row]->m_omegaS = calcOmegaS(); + calcFFTTotalTemperature(m_fftMeasurements[row]); + updatePowerColumns(row, m_fftMeasurements[row]); + } + plotFFTMeasurement(); + } + }); + tableContextMenu->addAction(updateTSysAction); + + tableContextMenu->popup(ui->powerTable->viewport()->mapToGlobal(pos)); + } +} + +RadioAstronomyGUI::~RadioAstronomyGUI() +{ + delete ui; + delete m_calHot; + delete m_calCold; + qDeleteAll(m_dataLAB); + m_dataLAB.clear(); + delete[] m_2DMapIntensity; +} + +void RadioAstronomyGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void RadioAstronomyGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + RadioAstronomy::MsgConfigureRadioAstronomy* message = RadioAstronomy::MsgConfigureRadioAstronomy::create( m_settings, force); + m_radioAstronomy->getInputMessageQueue()->push(message); + } +} + +int RadioAstronomyGUI::fftSizeToIndex(int size) +{ + switch (size) + { + case 16: + return 0; + case 32: + return 1; + case 64: + return 2; + case 128: + return 3; + case 256: + return 4; + case 512: + return 5; + case 1024: + return 6; + case 2048: + return 7; + case 4096: + return 8; + } + return 0; +} + +void RadioAstronomyGUI::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()); + float rfBW = m_settings.m_rfBandwidth; // Save value, as it may be corrupted when setting sampleRate + ui->sampleRate->setValue(m_settings.m_sampleRate); + ui->rfBW->setValue(rfBW); + updateBWLimits(); + + ui->integration->setValue(m_settings.m_integration); + ui->fftSize->setCurrentIndex(fftSizeToIndex(m_settings.m_fftSize)); + ui->fftWindow->setCurrentIndex((int)m_settings.m_fftWindow); + ui->filterFreqs->setText(m_settings.m_filterFreqs); + + int idx = ui->starTracker->findText(m_settings.m_starTracker); + if (idx != -1) { + ui->starTracker->setCurrentIndex(idx); + } + idx = ui->rotator->findText(m_settings.m_rotator); + if (idx != -1) { + ui->rotator->setCurrentIndex(idx); + } + + ui->tempRXSelect->setCurrentIndex(0); + ui->tempRX->setValue(m_settings.m_tempRX); + ui->tempRXUnitsLabel->setText("K"); + ui->tempCMB->setValue(m_settings.m_tempCMB); + ui->tempGal->setValue(m_settings.m_tempGal); + ui->tempGal->setEnabled(!m_settings.m_tempGalLink); + ui->tempGalLink->setChecked(m_settings.m_tempGalLink); + ui->tempSP->setValue(m_settings.m_tempSP); + ui->tempAtm->setValue(m_settings.m_tempAtm); + ui->tempAtm->setEnabled(!m_settings.m_tempAtmLink); + ui->tempAtmLink->setChecked(m_settings.m_tempAtmLink); + ui->tempAir->setValue(m_settings.m_tempAir); + ui->tempAir->setEnabled(!m_settings.m_tempAirLink); + ui->tempAirLink->setChecked(m_settings.m_tempAirLink); + ui->zenithOpacity->setValue(m_settings.m_zenithOpacity); + ui->elevation->setValue(m_settings.m_elevation); + ui->elevation->setEnabled(!m_settings.m_elevationLink); + ui->elevationLink->setChecked(m_settings.m_elevationLink); + + ui->gainVariation->setValue(m_settings.m_gainVariation); + ui->sourceType->setCurrentIndex((int)m_settings.m_sourceType); + ui->omegaS->setValue(m_settings.m_omegaS); + ui->omegaSUnits->setCurrentIndex((int)m_settings.m_omegaSUnits); + ui->omegaAUnits->setCurrentIndex((int)m_settings.m_omegaAUnits); + + ui->recalibrate->setChecked(m_settings.m_recalibrate); + ui->tCalHot->setValue(m_settings.m_tCalHot); + ui->tCalCold->setValue(m_settings.m_tCalCold); + + ui->spectrumAutoscale->setChecked(m_settings.m_spectrumAutoscale); + ui->spectrumReference->setValue(m_settings.m_spectrumReference); + ui->spectrumRange->setValue(m_settings.m_spectrumRange); + FFTMeasurement* fft = currentFFT(); + if (fft) { + ui->spectrumCenterFreq->setValue(fft->m_centerFrequency/1e6 + m_settings.m_spectrumCenterFreqOffset); + } else { + ui->spectrumCenterFreq->setValue(m_centerFrequency/1e6 + m_settings.m_spectrumCenterFreqOffset); + } + ui->spectrumSpan->setValue(m_settings.m_spectrumSpan); + ui->spectrumYUnits->setCurrentIndex((int)m_settings.m_spectrumYScale); + ui->spectrumBaseline->setCurrentIndex((int)m_settings.m_spectrumBaseline); + ui->spectrumAutoscaleX->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumAutoscaleY->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumReference->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumRange->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumCenterFreq->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumSpan->setEnabled(!m_settings.m_spectrumAutoscale); + + ui->powerAutoscale->setChecked(m_settings.m_powerAutoscale); + ui->powerReference->setValue(m_settings.m_powerReference); + ui->powerRange->setValue(m_settings.m_powerRange); + ui->powerShowPeak->setChecked(m_settings.m_powerPeaks); + if (m_powerPeakSeries) + { + m_powerPeakSeries->setVisible(m_settings.m_powerPeaks); + m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false); + } + ui->powerShowMarker->setChecked(m_settings.m_powerMarkers); + if (m_powerMarkerSeries) + { + m_powerMarkerSeries->setVisible(m_settings.m_powerMarkers); + m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false); + } + ui->powerShowAvg->setChecked(m_settings.m_powerAvg); + ui->powerChartAvgWidgets->setVisible(m_settings.m_powerAvg); + ui->powerShowGaussian->setChecked(m_settings.m_powerShowGaussian); + ui->powerGaussianWidgets->setVisible(m_settings.m_powerShowGaussian); + if (m_powerGaussianSeries) { + m_powerGaussianSeries->setVisible(m_settings.m_powerShowGaussian); + } + ui->powerShowLegend->setChecked(m_settings.m_powerLegend); + if (m_powerChart) { + m_powerChart->legend()->setVisible(m_settings.m_powerLegend); + } + ui->powerChartSelect->setCurrentIndex((int)m_settings.m_powerYData); + ui->powerYUnits->setCurrentIndex(powerYUnitsToIndex(m_settings.m_powerYUnits)); + ui->powerShowTsys0->setChecked(m_settings.m_powerShowTsys0); + ui->powerShowAirTemp->setChecked(m_settings.m_powerShowAirTemp); + m_airTemps.clicked(m_settings.m_powerShowAirTemp); + ui->powerShowSensor1->setChecked(m_settings.m_sensorVisible[0]); + m_sensors[0].setName(m_settings.m_sensorName[0]); + m_sensors[0].clicked(m_settings.m_sensorVisible[0]); + ui->powerShowSensor2->setChecked(m_settings.m_sensorVisible[1]); + m_sensors[1].setName(m_settings.m_sensorName[1]); + m_sensors[1].clicked(m_settings.m_sensorVisible[1]); + + ui->power2DLinkSweep->setChecked(m_settings.m_power2DLinkSweep); + ui->power2DSweepType->setCurrentIndex((int)m_settings.m_power2DSweepType); + ui->power2DWidth->setValue(m_settings.m_power2DWidth); + ui->power2DHeight->setValue(m_settings.m_power2DHeight); + ui->power2DXMin->setValue(m_settings.m_power2DXMin); + ui->power2DXMax->setValue(m_settings.m_power2DXMax); + ui->power2DYMin->setValue(m_settings.m_power2DYMin); + ui->power2DYMax->setValue(m_settings.m_power2DYMax); + ui->powerColourAutoscale->setChecked(m_settings.m_powerColourAutoscale); + ui->powerColourScaleMin->setValue(m_settings.m_powerColourScaleMin); + ui->powerColourScaleMin->setEnabled(!m_settings.m_powerColourAutoscale); + ui->powerColourScaleMax->setValue(m_settings.m_powerColourScaleMax); + ui->powerColourScaleMax->setEnabled(!m_settings.m_powerColourAutoscale); + ui->powerColourPalette->setCurrentIndex(ui->powerColourPalette->findText(m_settings.m_powerColourPalette)); + + ui->spectrumReverseXAxis->setChecked(m_settings.m_spectrumReverseXAxis); + ui->spectrumPeak->setChecked(m_settings.m_spectrumPeaks); + ui->spectrumMarker->setChecked(m_settings.m_spectrumMarkers); + ui->spectrumTemp->setChecked(m_settings.m_spectrumTemp); + if (m_fftGaussianSeries) { + m_fftGaussianSeries->setVisible(m_settings.m_spectrumTemp); + } + ui->spectrumShowRefLine->setChecked(m_settings.m_spectrumRefLine); + if (m_fftHlineSeries) + { + m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine); + m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine); + } + ui->spectrumShowLAB->setChecked(m_settings.m_spectrumLAB); + if (m_fftLABSeries) { + m_fftLABSeries->setVisible(m_settings.m_spectrumLAB); + } + ui->spectrumShowDistance->setChecked(m_settings.m_spectrumDistance); + updateDistanceColumns(); + ui->spectrumShowLegend->setChecked(m_settings.m_spectrumLegend); + if (m_fftChart) { + m_fftChart->legend()->setVisible(m_settings.m_spectrumLegend); + } + if (m_calChart) { + m_calChart->legend()->setVisible(m_settings.m_spectrumLegend); + } + + + ui->refFrame->setCurrentIndex((int)m_settings.m_refFrame); + ui->spectrumLine->setCurrentIndex((int)m_settings.m_line); + ui->sunDistanceToGC->setValue(m_settings.m_sunDistanceToGC); + ui->sunOrbitalVelocity->setValue(m_settings.m_sunOrbitalVelocity); + displaySpectrumLineFrequency(); + updateSpectrumSelect(); + updatePowerSelect(); + + // Updates visibility of widgets + updateSpectrumMarkerTableVisibility(); + updatePowerMarkerTableVisibility(); + updatePowerChartWidgetsVisibility(); + updateSpectrumChartWidgetsVisibility(); + + updateIntegrationTime(); + + ui->runMode->setCurrentIndex((int)m_settings.m_runMode); + ui->sweepStartAtTime->setCurrentIndex(m_settings.m_sweepStartAtTime ? 1 : 0); + ui->sweepStartDateTime->setDateTime(m_settings.m_sweepStartDateTime); + ui->sweepStartDateTime->setVisible(m_settings.m_sweepStartAtTime); + ui->sweepType->setCurrentIndex((int)m_settings.m_sweepType); + ui->sweep1Start->setValue(m_settings.m_sweep1Start); + ui->sweep1Stop->setValue(m_settings.m_sweep1Stop); + ui->sweep1Step->setValue(m_settings.m_sweep1Step); + ui->sweep1Delay->setValue(m_settings.m_sweep1Delay); + ui->sweep2Start->setValue(m_settings.m_sweep2Start); + ui->sweep2Stop->setValue(m_settings.m_sweep2Stop); + ui->sweep2Step->setValue(m_settings.m_sweep2Step); + ui->sweep2Delay->setValue(m_settings.m_sweep2Delay); + displayRunModeSettings(); + + displayStreamIndex(); + + // Order and size columns + QHeaderView *header = ui->powerTable->horizontalHeader(); + for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) + { + bool hidden = m_settings.m_powerTableColumnSizes[i] == 0; + header->setSectionHidden(i, hidden); + powerTableMenu->actions().at(i)->setChecked(!hidden); + if (m_settings.m_powerTableColumnSizes[i] > 0) + ui->powerTable->setColumnWidth(i, m_settings.m_powerTableColumnSizes[i]); + header->moveSection(header->visualIndex(i), m_settings.m_powerTableColumnIndexes[i]); + } + + blockApplySettings(false); + arrangeRollups(); +} + +void RadioAstronomyGUI::displayStreamIndex() +{ + if (m_deviceUISet->m_deviceMIMOEngine) { + setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex)); + } else { + setStreamIndicator("S"); // single channel indicator + } +} + +void RadioAstronomyGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void RadioAstronomyGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void RadioAstronomyGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_radioAstronomy->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); + } + + if (m_tickCount % 20 == 0) { // 1s + updateRotatorList(); + } + + m_tickCount++; +} + +void RadioAstronomyGUI::updateRotatorList() +{ + // Update list of rotators + std::vector featureSets = MainCore::instance()->getFeatureeSets(); + for (int i = 0; i < featureSets.size(); i++) + { + FeatureSet* featureSet = featureSets[i]; + for (int j = 0; j < featureSet->getNumberOfFeatures(); j++) + { + const Feature* feature = featureSet->getFeatureAt(j); + if (feature->getURI() == "sdrangel.feature.gs232controller") + { + // Add if it doesn't already exist + QString name = QString("F%1:%2 GS232Controller").arg(i).arg(j); + if (ui->rotator->findText(name) == -1) + { + ui->rotator->addItem(name); + // Rotator feature can be created after this plugin, so select it + // if the chosen rotator appears + if (name == m_settings.m_rotator) { + ui->rotator->setCurrentIndex(ui->rotator->findText(name)); + } + } + } + else + { + // Try to remove + QString prefix = QString("F%1:%2").arg(i).arg(j); + for (int k = 0; k < ui->rotator->count(); k++) + { + if (ui->rotator->itemText(k).startsWith(prefix)) + { + ui->rotator->removeItem(k); + break; + } + } + } + } + } +} + +void RadioAstronomyGUI::on_fftSize_currentIndexChanged(int index) +{ + m_settings.m_fftSize = 1 << (4+index); + applySettings(); + updateIntegrationTime(); +} + +void RadioAstronomyGUI::on_fftWindow_currentIndexChanged(int index) +{ + m_settings.m_fftWindow = (RadioAstronomySettings::FFTWindow)index; + applySettings(); +} + +void RadioAstronomyGUI::on_filterFreqs_editingFinished() +{ + m_settings.m_filterFreqs = ui->filterFreqs->text(); + applySettings(); +} + +void RadioAstronomyGUI::on_gainVariation_valueChanged(double value) +{ + m_settings.m_gainVariation = value; + applySettings(); + updateTSys0(); +} + +// Can we allow user to enter text that is automatically looked up on SIMBAD? +void RadioAstronomyGUI::on_sourceType_currentIndexChanged(int index) +{ + m_settings.m_sourceType = (RadioAstronomySettings::SourceType)index; + applySettings(); + if (m_settings.m_sourceType == RadioAstronomySettings::SUN) + { + // Mean diameter of Sun in degrees from Earth + ui->omegaS->setValue(0.53); + ui->omegaSUnits->setCurrentIndex(0); + } + else if (m_settings.m_sourceType == RadioAstronomySettings::CAS_A) + { + // Diameter of Cas A in degrees http://simbad.u-strasbg.fr/simbad/sim-id?Ident=Cassiopeia+A + ui->omegaS->setValue(0.08333); + ui->omegaSUnits->setCurrentIndex(0); + } + bool visible = index == 1 || index >= 3; + ui->omegaS->setVisible(visible); + ui->omegaSUnits->setVisible(visible); +} + +void RadioAstronomyGUI::on_omegaS_valueChanged(double value) +{ + m_settings.m_omegaS = value; + if ((m_settings.m_sourceType == RadioAstronomySettings::SUN) && (value != 0.53)) { + ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT); + } else if ((m_settings.m_sourceType == RadioAstronomySettings::CAS_A) && (value != 0.08333)) { + ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT); + } + applySettings(); +} + +void RadioAstronomyGUI::updateOmegaA() +{ + if (m_settings.m_omegaAUnits == RadioAstronomySettings::DEGREES) { + ui->omegaA->setText(QString("%1").arg(m_beamWidth, 0, 'f', 1)); + } else { + ui->omegaA->setText(QString("%1").arg(hpbwToSteradians(m_beamWidth), 0, 'f', 4)); + } +} + +void RadioAstronomyGUI::on_omegaAUnits_currentIndexChanged(int index) +{ + m_settings.m_omegaAUnits = (RadioAstronomySettings::AngleUnits)index; + updateOmegaA(); + if (m_settings.m_omegaAUnits == RadioAstronomySettings::DEGREES) { + ui->omegaALabel->setText("HPBW"); + } else { + ui->omegaALabel->setText(QString("%1A").arg(QChar(937))); + } + applySettings(); +} + +void RadioAstronomyGUI::on_omegaSUnits_currentIndexChanged(int index) +{ + m_settings.m_omegaSUnits = (RadioAstronomySettings::AngleUnits)index; + if ( ( (m_settings.m_sourceType == RadioAstronomySettings::SUN) + || (m_settings.m_sourceType == RadioAstronomySettings::CAS_A) + ) + && (m_settings.m_omegaSUnits != RadioAstronomySettings::DEGREES) + ) + { + ui->sourceType->setCurrentIndex((int)RadioAstronomySettings::COMPACT); + } + applySettings(); +} + +void RadioAstronomyGUI::on_starTracker_currentTextChanged(const QString& text) +{ + m_settings.m_starTracker = text; + applySettings(); +} + +void RadioAstronomyGUI::on_rotator_currentTextChanged(const QString& text) +{ + m_settings.m_rotator = text; + applySettings(); +} + +void RadioAstronomyGUI::on_showSensors_clicked() +{ + RadioAstronomySensorDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) + { + m_sensors[0].setName(m_settings.m_sensorName[0]); + m_sensors[1].setName(m_settings.m_sensorName[1]); + applySettings(); + } +} + +void RadioAstronomyGUI::sensorMeasurementReceived(const RadioAstronomy::MsgSensorMeasurement& measurement) +{ + int sensor = measurement.getSensor(); + double value = measurement.getValue(); + QDateTime dateTime = measurement.getDateTime(); + SensorMeasurement* sm = new SensorMeasurement(dateTime, value); + m_sensors[sensor].append(sm); +} + +void RadioAstronomyGUI::on_powerChartSelect_currentIndexChanged(int index) +{ + m_settings.m_powerYData = (RadioAstronomySettings::PowerYData)index; + ui->powerYUnits->clear(); + switch (m_settings.m_powerYData) + { + case RadioAstronomySettings::PY_POWER: + ui->powerYUnits->addItem("dBFS"); + ui->powerYUnits->addItem("dBm"); + ui->powerYUnits->addItem("Watts"); + break; + case RadioAstronomySettings::PY_TSYS: + case RadioAstronomySettings::PY_TSOURCE: + ui->powerYUnits->addItem("K"); + break; + case RadioAstronomySettings::PY_FLUX: + ui->powerYUnits->addItem("SFU"); + ui->powerYUnits->addItem("Jy"); + break; + case RadioAstronomySettings::PY_2D_MAP: + ui->powerYUnits->addItem("dBFS"); + ui->powerYUnits->addItem("dBm"); + //ui->powerYUnits->addItem("Watts"); // No watts for now, as range spin boxes can't handle scientific notation + ui->powerYUnits->addItem("K"); + break; + } + updatePowerMarkerTableVisibility(); + updatePowerChartWidgetsVisibility(); + plotPowerChart(); + applySettings(); +} + +void RadioAstronomyGUI::updatePowerChartWidgetsVisibility() +{ + bool powerChart; + if (m_settings.m_powerYData != RadioAstronomySettings::PY_2D_MAP) { + powerChart = true; + } else { + powerChart = false; + } + ui->powerShowLegend->setVisible(powerChart); + ui->powerShowSensor2->setVisible(powerChart); + ui->powerShowSensor1->setVisible(powerChart); + ui->powerShowAirTemp->setVisible(powerChart); + ui->powerShowTsys0->setVisible(powerChart); + ui->powerShowAvg->setVisible(powerChart); + ui->powerShowGaussian->setVisible(powerChart); + ui->powerShowMarker->setVisible(powerChart); + ui->powerShowPeak->setVisible(powerChart); + ui->powerScaleWidgets->setVisible(powerChart); + ui->powerGaussianWidgets->setVisible(powerChart && m_settings.m_powerShowGaussian); + ui->powerMarkerTableWidgets->setVisible(powerChart && (m_settings.m_powerPeaks || m_settings.m_powerMarkers)); + ui->power2DScaleWidgets->setVisible(!powerChart); + ui->power2DColourScaleWidgets->setVisible(!powerChart); + arrangeRollups(); +} + +int RadioAstronomyGUI::powerYUnitsToIndex(RadioAstronomySettings::PowerYUnits units) +{ + switch (units) + { + case RadioAstronomySettings::PY_DBFS: + return 0; + case RadioAstronomySettings::PY_DBM: + return 1; + case RadioAstronomySettings::PY_WATTS: + return 2; + case RadioAstronomySettings::PY_KELVIN: + return 0; + case RadioAstronomySettings::PY_SFU: + return 0; + case RadioAstronomySettings::PY_JANSKY: + return 1; + } + return -1; +} + +void RadioAstronomyGUI::on_powerYUnits_currentIndexChanged(int index) +{ + QString text = ui->powerYUnits->currentText(); + if (text == "dBFS") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_DBFS; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (dBFS)"); + ui->powerColourScaleMin->setDecimals(2); + ui->powerColourScaleMax->setDecimals(2); + } + else if (text == "dBm") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_DBM; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (dBm)"); + ui->powerColourScaleMin->setDecimals(2); + ui->powerColourScaleMax->setDecimals(2); + } + else if (text == "Watts") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_WATTS; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Power (W)"); + } + else if (text == "K") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_KELVIN; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Temp (K)"); + ui->powerColourScaleMin->setDecimals(0); + ui->powerColourScaleMax->setDecimals(0); + } + else if (text == "SFU") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_SFU; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Flux (SFU)"); + } + else if (text == "Jy") + { + m_settings.m_powerYUnits = RadioAstronomySettings::PY_JANSKY; + ui->powerMarkerTable->horizontalHeaderItem(POWER_MARKER_COL_VALUE)->setText("Flux (Jy)"); + } + if (text == "dBFS") + { + ui->powerColourScaleMinUnits->setText("dB"); + ui->powerColourScaleMaxUnits->setText("dB"); + } + else + { + ui->powerColourScaleMinUnits->setText(text); + ui->powerColourScaleMaxUnits->setText(text); + } + if (m_settings.m_powerColourAutoscale && (ui->powerChartSelect->currentIndex() == 4)) { + powerColourAutoscale(); + } + applySettings(); + plotPowerChart(); +} + +void RadioAstronomyGUI::on_spectrumChartSelect_currentIndexChanged(int index) +{ + updateSpectrumMarkerTableVisibility(); // If this follows updateSpectrumChartWidgetsVisibility, widgets are not redrawn properly. + updateSpectrumChartWidgetsVisibility(); // when switching from cal to spectrum if table is visible + if (index == 0) + { + if (m_fftChart) { + ui->spectrumChart->setChart(m_fftChart); + } + } + else + { + if (m_calChart) { + ui->spectrumChart->setChart(m_calChart); + } + } +} + +void RadioAstronomyGUI::updateSpectrumChartWidgetsVisibility() +{ + bool fft = ui->spectrumChartSelect->currentIndex() == 0; + ui->spectrumYUnits->setVisible(fft); + ui->spectrumScaleWidgets->setVisible(fft); + ui->spectrumSelectWidgets->setVisible(fft); + ui->spectrumRefLineWidgets->setVisible(fft && m_settings.m_spectrumRefLine); + + ui->spectrumGaussianWidgets->setVisible(fft && m_settings.m_spectrumTemp); + ui->calWidgets->setVisible(!fft); + ui->recalibrate->setVisible(!fft); + ui->startCalHot->setVisible(!fft); + ui->startCalCold->setVisible(!fft); + ui->clearCal->setVisible(!fft); + ui->showCalSettings->setVisible(!fft); + ui->spectrumShowRefLine->setVisible(fft); + ui->spectrumShowDistance->setVisible(fft); + ui->spectrumShowLAB->setVisible(fft); + ui->spectrumTemp->setVisible(fft); + ui->spectrumMarker->setVisible(fft); + ui->spectrumPeak->setVisible(fft); + ui->saveSpectrumChartImages->setVisible(fft); + + arrangeRollups(); +} + +// Calulate mean, RMS and standard deviation +// Currently this is for all data - but could make it only for visible data +void RadioAstronomyGUI::calcAverages() +{ + qreal sum = 0.0; + qreal sumSq = 0.0; + QVector points = m_powerSeries->pointsVector(); + for (int i = 0; i < points.size(); i++) + { + QPointF point = points.at(i); + qreal y = point.y(); + sum += y; + sumSq += y * y; + } + qreal mean = sum / points.size(); + qreal rms = std::sqrt(sumSq / points.size()); + qreal sumSqDiff = 0.0; + for (int i = 0; i < points.size(); i++) + { + QPointF point = points.at(i); + qreal y = point.y(); + qreal diff = y - mean; + sumSqDiff += diff * diff; + } + qreal sigma = std::sqrt(sumSqDiff / points.size()); + + ui->powerMean->setText(QString::number(mean)); + ui->powerRMS->setText(QString::number(rms)); + ui->powerSD->setText(QString::number(sigma)); +} + +QRgb RadioAstronomyGUI::intensityToColor(float intensity) +{ + QRgb c1, c2; + float scale; + + if (isnan(intensity)) { + return qRgb(0, 0, 0); + } + // Get in range 0-1 + intensity = (intensity - m_settings.m_powerColourScaleMin) / (m_settings.m_powerColourScaleMax - m_settings.m_powerColourScaleMin); + intensity = std::min(intensity, 1.0f); + intensity = std::max(intensity, 0.0f); + + if (m_settings.m_powerColourPalette[0] == 'C') + { + // Colour heat map gradient + if (intensity <= 0.25f) + { + c1 = qRgb(0, 0, 0x80); // Navy + c2 = qRgb(0, 0, 0xff); // Blue + scale = intensity * 4.0f; + } + else if (intensity <= 0.5f) + { + c1 = qRgb(0, 0, 0xff); // Blue + c2 = qRgb(0, 0xff, 0); // Green + scale = (intensity - 0.25f) * 4.0f; + } + else if (intensity <= 0.75f) + { + c1 = qRgb(0, 0xff, 0); // Green + c2 = qRgb(0xff, 0xff, 0); // Yellow + scale = (intensity - 0.5f) * 4.0f; + } + else + { + c1 = qRgb(0xff, 0xff, 0); // Yellow + c2 = qRgb(0xff, 0, 0); // Red + scale = (intensity - 0.75f) * 4.0f; + } + + int r, g, b; + r = (qRed(c2)-qRed(c1))*scale+qRed(c1); + g = (qGreen(c2)-qGreen(c1))*scale+qGreen(c1); + b = (qBlue(c2)-qBlue(c1))*scale+qBlue(c1); + + return qRgb(r, g, b); + } + else + { + // Greyscale + int g = 255 * intensity; + return qRgb(g, g, g); + } +} + +void RadioAstronomyGUI::plot2DChart() +{ + // Only plot if visible + if (ui->powerChartSelect->currentIndex() == 4) + { + QChart *oldChart = m_2DChart; + + m_2DChart = new QChart(); + + m_2DChart->layout()->setContentsMargins(0, 0, 0, 0); + m_2DChart->setMargins(QMargins(1, 1, 1, 1)); + m_2DChart->setTheme(QChart::ChartThemeDark); + m_2DChart->setTitle(""); + + m_2DXAxis = new QValueAxis(); + m_2DYAxis = new QValueAxis(); + + m_2DXAxis->setGridLineVisible(false); + m_2DYAxis->setGridLineVisible(false); + set2DAxisTitles(); + m_2DXAxis->setRange(m_settings.m_power2DXMin, m_settings.m_power2DXMax); + m_2DYAxis->setRange(m_settings.m_power2DYMin, m_settings.m_power2DYMax); + + m_2DChart->addAxis(m_2DXAxis, Qt::AlignBottom); + m_2DChart->addAxis(m_2DYAxis, Qt::AlignLeft); + + m_2DMap.fill(qRgb(0, 0, 0)); + for (int i = 0; i < m_fftMeasurements.size(); i++) { + update2DImage(m_fftMeasurements[i], i < m_fftMeasurements.size() - 1); + } + + connect(m_2DChart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF))); + + ui->powerChart->setChart(m_2DChart); + + delete oldChart; + } +} + +void RadioAstronomyGUI::set2DAxisTitles() +{ + if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB) + { + m_2DXAxis->setTitleText(QString("Galactic longitude (%1)").arg(QChar(0xb0))); + m_2DYAxis->setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0))); + } + else + { + m_2DXAxis->setTitleText(QString("Azimuth (%1)").arg(QChar(0xb0))); + m_2DYAxis->setTitleText(QString("Elevation (%1)").arg(QChar(0xb0))); + } +} + +void RadioAstronomyGUI::update2DSettingsFromSweep() +{ + if (m_settings.m_runMode == RadioAstronomySettings::SWEEP) + { + ui->power2DSweepType->setCurrentIndex((int)m_settings.m_sweepType); + + // Calculate width and height of image - 1 pixel per sweep measurement + float sweep1Start, sweep1Stop; + sweep1Start = m_settings.m_sweep1Start; + sweep1Stop = m_settings.m_sweep1Stop; + // Handle azimuth/l sweep through 0. E.g. 340deg -> 20deg with +vs step, or 20deg -> 340deg with -ve step + if ((m_settings.m_sweep1Stop < m_settings.m_sweep1Start) && (m_settings.m_sweep1Step > 0)) { + sweep1Stop = m_settings.m_sweep1Stop + 360.0; + } else if ((m_settings.m_sweep1Stop > m_settings.m_sweep1Start) && (m_settings.m_sweep1Step < 0)) { + sweep1Start += 360.0; + } + int width = abs((sweep1Stop - sweep1Start) / m_settings.m_sweep1Step) + 1; + int height = abs((m_settings.m_sweep2Stop - m_settings.m_sweep2Start) / m_settings.m_sweep2Step) + 1; + ui->power2DWidth->setValue(width); + ui->power2DHeight->setValue(height); + + // Subtract/add half a step so that pixels are centred on coordinates + int start1 = m_settings.m_sweep1Start - m_settings.m_sweep1Step / 2; + int stop1 = m_settings.m_sweep1Stop + m_settings.m_sweep1Step / 2; + if (start1 < stop1) + { + ui->power2DXMin->setValue(start1); + ui->power2DXMax->setValue(stop1); + } + else + { + ui->power2DXMin->setValue(stop1); + ui->power2DXMax->setValue(start1); + } + + int start2 = m_settings.m_sweep2Start - m_settings.m_sweep2Step / 2; + int stop2 = m_settings.m_sweep2Stop + m_settings.m_sweep2Step / 2; + if (start2 < stop2) + { + ui->power2DYMin->setValue(start2); + ui->power2DYMax->setValue(stop2); + } + else + { + ui->power2DYMin->setValue(stop2); + ui->power2DYMax->setValue(start2); + } + + m_sweepIndex = 0; + } +} + +void RadioAstronomyGUI::create2DImage() +{ + // Intensity array holds power/temperature values which are then colourised in to a QImage + delete m_2DMapIntensity; + int size = m_settings.m_power2DWidth * m_settings.m_power2DHeight; + if (size > 0) + { + m_2DMapIntensity = new float[size]; + for (int i = 0; i < size; i++) { + m_2DMapIntensity[i] = NAN; + } + m_2DMapMin = std::numeric_limits::max(); + m_2DMapMax = -std::numeric_limits::max(); + QImage map(m_settings.m_power2DWidth, m_settings.m_power2DHeight, QImage::Format_ARGB32); + map.fill(qRgb(0, 0, 0)); + m_2DMap = map; + } + else + { + m_2DMapIntensity = nullptr; + m_2DMap = QImage(); + } +} + +void RadioAstronomyGUI::update2DImage(FFTMeasurement* fft, bool skipCalcs) +{ + if (m_2DMap.width() > 0) + { + int x, y; + if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_OFFSET) + { + y = fft->m_sweepIndex / m_2DMap.width(); + x = fft->m_sweepIndex % m_2DMap.width(); + + if (m_settings.m_sweep2Step >= 0) { + y = m_2DMap.height() - 1 - y; + } + if (m_settings.m_sweep1Step < 0) { + x = m_2DMap.width() - 1 - x; + } + } + else + { + if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB) + { + x = (int)round(fft->m_l); + y = (int)round(fft->m_b); + } + else + { + x = (int)round(fft->m_azimuth); + y = (int)round(fft->m_elevation); + } + + // Map coordinates to pixels + float xRange = m_settings.m_power2DXMax - m_settings.m_power2DXMin; + float yRange = m_settings.m_power2DYMax - m_settings.m_power2DYMin; + + x = (x - m_settings.m_power2DXMin) * m_settings.m_power2DWidth / xRange; + y = (y - m_settings.m_power2DYMin) * m_settings.m_power2DHeight / yRange; + + if (yRange >= 0) { + y = m_2DMap.height() - 1 - y; + } + if (xRange < 0) { + x = m_2DMap.width() - 1 - x; + } + } + + if ((x >= 0) && (x < m_2DMap.width()) && (y >= 0) && (y < m_2DMap.height())) + { + float intensity; + switch (m_settings.m_powerYUnits) + { + case RadioAstronomySettings::PY_DBFS: + intensity = fft->m_totalPowerdBFS; + break; + case RadioAstronomySettings::PY_DBM: + intensity = fft->m_totalPowerdBm; + break; + case RadioAstronomySettings::PY_WATTS: + intensity = fft->m_totalPowerWatts; + break; + case RadioAstronomySettings::PY_KELVIN: + intensity = fft->m_tSys; + break; + } + + float newMin = std::min(m_2DMapMin, intensity); + float newMax = std::max(m_2DMapMax, intensity); + bool rescale = false; + if ((newMin != m_2DMapMin) || (newMax != m_2DMapMax)) { + rescale = m_settings.m_powerColourAutoscale; + } + m_2DMapMin = newMin; + m_2DMapMax = newMax; + m_2DMapIntensity[y*m_2DMap.width()+x] = intensity; + m_2DMap.setPixel(x, y, intensityToColor(intensity)); + if (rescale && !skipCalcs) { + powerColourAutoscale(); + } + if (m_2DChart && !skipCalcs) { + plotAreaChanged(m_2DChart->plotArea()); + } + } + } +} + +void RadioAstronomyGUI::recolour2DImage() +{ + for (int y = 0; y < m_2DMap.height(); y++) + { + for (int x = 0; x < m_2DMap.width(); x++) { + m_2DMap.setPixel(x, y, intensityToColor(m_2DMapIntensity[y*m_2DMap.width()+x])); + } + } + if (m_2DChart) { + plotAreaChanged(m_2DChart->plotArea()); + } +} + +void RadioAstronomyGUI::plotAreaChanged(const QRectF& plotArea) +{ + if (ui->powerChartSelect->currentIndex() == 4) + { + int width = static_cast(plotArea.width()); + int height = static_cast(plotArea.height()); + int viewW = static_cast(ui->powerChart->width()); + int viewH = static_cast(ui->powerChart->height()); + + // Scale the image to fit plot area + QImage image = m_2DMap.scaled(QSize(width, height), Qt::IgnoreAspectRatio); + QImage translated(viewW, viewH, QImage::Format_ARGB32); + translated.fill(Qt::black); + QPainter painter(&translated); + painter.drawImage(plotArea.topLeft(), image); + + m_2DChart->setPlotAreaBackgroundBrush(translated); + m_2DChart->setPlotAreaBackgroundVisible(true); + } +} + +// Find min and max coordinates from existing data +void RadioAstronomyGUI::power2DAutoscale() +{ + if (m_fftMeasurements.size() > 0) + { + float minX = std::numeric_limits::max(); + float maxX = -std::numeric_limits::max(); + float minY = std::numeric_limits::max(); + float maxY = -std::numeric_limits::max(); + + for (int i = 0; i < m_fftMeasurements.size(); i++) + { + FFTMeasurement* fft = m_fftMeasurements[i]; + float x, y; + if (m_settings.m_power2DSweepType == RadioAstronomySettings::SWP_LB) + { + x = fft->m_l; + y = fft->m_b; + } + else + { + x = fft->m_azimuth; + y = fft->m_elevation; + } + minX = std::min(minX, x); + maxX = std::max(maxX, x); + minY = std::min(minY, y); + maxY = std::max(maxY, y); + } + + // Adjust so pixels are centered + float xAdjust = (maxX - minX) / m_2DMap.width() / 2; + float yAdjust = (maxY - minY) / m_2DMap.height() / 2; + + ui->power2DXMin->setValue(minX - xAdjust); + ui->power2DXMax->setValue(maxX + xAdjust); + ui->power2DYMin->setValue(minY - yAdjust); + ui->power2DYMax->setValue(maxY + xAdjust); + } +} + +void RadioAstronomyGUI::on_power2DAutoscale_clicked() +{ + power2DAutoscale(); + plot2DChart(); +} + +void RadioAstronomyGUI::on_power2DLinkSweep_toggled(bool checked) +{ + m_settings.m_power2DLinkSweep = checked; + applySettings(); +} + +void RadioAstronomyGUI::on_power2DSweepType_currentIndexChanged(int index) +{ + m_settings.m_power2DSweepType = (RadioAstronomySettings::SweepType)index; + applySettings(); + plot2DChart(); +} + +void RadioAstronomyGUI::on_power2DWidth_valueChanged(int value) +{ + m_settings.m_power2DWidth = value; + applySettings(); + create2DImage(); + plot2DChart(); +} + +void RadioAstronomyGUI::on_power2DHeight_valueChanged(int value) +{ + m_settings.m_power2DHeight = value; + applySettings(); + create2DImage(); + plot2DChart(); +} + +void RadioAstronomyGUI::on_power2DXMin_valueChanged(double value) +{ + m_settings.m_power2DXMin = value; + applySettings(); + if (m_2DXAxis) + { + m_2DXAxis->setMin(m_settings.m_power2DXMin); + plot2DChart(); + } +} + +void RadioAstronomyGUI::on_power2DXMax_valueChanged(double value) +{ + m_settings.m_power2DXMax = value; + applySettings(); + if (m_2DXAxis) + { + m_2DXAxis->setMax(m_settings.m_power2DXMax); + plot2DChart(); + } +} + +void RadioAstronomyGUI::on_power2DYMin_valueChanged(double value) +{ + m_settings.m_power2DYMin = value; + applySettings(); + if (m_2DYAxis) + { + m_2DYAxis->setMin(m_settings.m_power2DYMin); + plot2DChart(); + } +} + +void RadioAstronomyGUI::on_power2DYMax_valueChanged(double value) +{ + m_settings.m_power2DYMax = value; + applySettings(); + if (m_2DYAxis) + { + m_2DYAxis->setMax(m_settings.m_power2DYMax); + plot2DChart(); + } +} + +void RadioAstronomyGUI::powerColourAutoscale() +{ + int width = m_2DMap.width(); + int height = m_2DMap.height(); + float newMin = std::numeric_limits::max(); + float newMax = -std::numeric_limits::max(); + for (int i = 0; i < width * height; i++) + { + if (!isnan(m_2DMapIntensity[i])) + { + newMin = std::min(newMin, m_2DMapIntensity[i]); + newMax = std::max(newMax, m_2DMapIntensity[i]); + } + } + + if ((newMin != ui->powerColourScaleMin->value()) || (newMax != ui->powerColourScaleMax->value())) + { + ui->powerColourScaleMin->setValue(std::floor(newMin * 10.0) / 10.0); + ui->powerColourScaleMax->setValue(std::ceil(newMax * 10.0) / 10.0); + } +} + +void RadioAstronomyGUI::on_powerColourAutoscale_toggled(bool checked) +{ + m_settings.m_powerColourAutoscale = checked; + applySettings(); + if (m_settings.m_powerColourAutoscale) { + powerColourAutoscale(); + } + ui->powerColourScaleMin->setEnabled(!m_settings.m_powerColourAutoscale); + ui->powerColourScaleMax->setEnabled(!m_settings.m_powerColourAutoscale); +} + +void RadioAstronomyGUI::updatePowerColourScaleStep() +{ + float diff = abs(m_settings.m_powerColourScaleMax - m_settings.m_powerColourScaleMin); + double step = (diff <= 1.0f) ? 0.1 : 1.0f; + ui->powerColourScaleMin->setSingleStep(step); + ui->powerColourScaleMax->setSingleStep(step); +} + +void RadioAstronomyGUI::on_powerColourScaleMin_valueChanged(double value) +{ + m_settings.m_powerColourScaleMin = value; + updatePowerColourScaleStep(); + applySettings(); + recolour2DImage(); +} + +void RadioAstronomyGUI::on_powerColourScaleMax_valueChanged(double value) +{ + m_settings.m_powerColourScaleMax = value; + updatePowerColourScaleStep(); + applySettings(); + recolour2DImage(); +} + +void RadioAstronomyGUI::on_powerColourPalette_currentIndexChanged(int index) +{ + (void) index; + m_settings.m_powerColourPalette = ui->powerColourPalette->currentText(); + applySettings(); + recolour2DImage(); +} + +void RadioAstronomyGUI::plotPowerChart() +{ + if (ui->powerChartSelect->currentIndex() == 4) { + plot2DChart(); + } else { + plotPowerVsTimeChart(); + } +} + +void RadioAstronomyGUI::plotPowerVsTimeChart() +{ + QChart *oldChart = m_powerChart; + + m_powerChart = new QChart(); + + m_powerChart->layout()->setContentsMargins(0, 0, 0, 0); + m_powerChart->setMargins(QMargins(1, 1, 1, 1)); + m_powerChart->setTheme(QChart::ChartThemeDark); + + m_powerChart->legend()->setAlignment(Qt::AlignRight); + m_powerChart->legend()->setVisible(m_settings.m_powerLegend); + + // Create measurement data series + m_powerSeries = new QLineSeries(); + connect(m_powerSeries, &QXYSeries::clicked, this, &RadioAstronomyGUI::powerSeries_clicked); + + // Plot peak info + m_powerPeakSeries = new QScatterSeries(); + m_powerPeakSeries->setName("Peak"); + m_powerPeakSeries->setPointLabelsVisible(true); + m_powerPeakSeries->setPointLabelsFormat("@yPoint"); // Qt can't display dates, so omit @xPoint + m_powerPeakSeries->setMarkerSize(5); + m_powerPeakSeries->setVisible(m_settings.m_powerPeaks); + + // Markers + m_powerMarkerSeries = new QScatterSeries(); + m_powerMarkerSeries->setName("Marker"); + m_powerMarkerSeries->setPointLabelsVisible(true); + m_powerMarkerSeries->setPointLabelsFormat("@yPoint"); + m_powerMarkerSeries->setMarkerSize(5); + m_powerMarkerSeries->setVisible(m_settings.m_powerMarkers); + + // Noise + m_powerTsys0Series = new QLineSeries(); + m_powerTsys0Series->setName("Tsys0"); + m_powerTsys0Series->setVisible(m_settings.m_powerShowTsys0); + + // Air temperature + m_airTemps.init("Air temp", m_settings.m_powerShowAirTemp); + + // Gaussian + m_powerGaussianSeries = new QLineSeries(); + m_powerGaussianSeries->setName("Gaussian fit"); + m_powerGaussianSeries->setVisible(m_settings.m_powerShowGaussian); + + // Sensors + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_sensors[i].init(m_settings.m_sensorName[i], m_settings.m_sensorVisible[i]); + } + + // Reset min/max and peaks + m_powerMin = std::numeric_limits::max(); + m_powerMax = -std::numeric_limits::max(); + m_powerPeakValid = false; + + // Create X axis + m_powerXAxis = new QDateTimeAxis(); + int rows = ui->powerTable->rowCount(); + QString dateTimeFormat = "hh:mm:ss"; + m_powerXAxisSameDay = true; + if (rows > 1) + { + QDate start = ui->powerTable->item(0, POWER_COL_DATE)->data(Qt::DisplayRole).toDate(); + QDate end = ui->powerTable->item(rows-1, POWER_COL_DATE)->data(Qt::DisplayRole).toDate(); + if (start != end) + { + dateTimeFormat = QString("%1 hh:mm").arg(QLocale::system().dateFormat(QLocale::ShortFormat)); + m_powerXAxisSameDay = false; + } + } + m_powerXAxis->setFormat(dateTimeFormat); + m_powerXAxis->setRange(ui->powerStartTime->dateTime(), ui->powerEndTime->dateTime()); + ui->powerStartTime->setDisplayFormat(dateTimeFormat); + ui->powerEndTime->setDisplayFormat(dateTimeFormat); + + // Create Y axis + m_powerYAxis = new QValueAxis(); + + m_powerXAxis->setTitleText("Time"); + calcPowerChartTickCount(size().width()); + switch (m_settings.m_powerYData) + { + case RadioAstronomySettings::PY_POWER: + m_powerSeries->setName("Measurement"); + switch (m_settings.m_powerYUnits) + { + case RadioAstronomySettings::PY_DBFS: + m_powerYAxis->setTitleText("Power (dBFS)"); + break; + case RadioAstronomySettings::PY_DBM: + m_powerYAxis->setTitleText("Power (dBm)"); + break; + case RadioAstronomySettings::PY_WATTS: + m_powerYAxis->setTitleText("Power (Watts)"); + break; + } + break; + case RadioAstronomySettings::PY_TSYS: + m_powerSeries->setName("Tsys"); + m_powerYAxis->setTitleText("Tsys (K)"); + break; + case RadioAstronomySettings::PY_TSOURCE: + m_powerSeries->setName("Tsource"); + m_powerYAxis->setTitleText("Tsource (K)"); + break; + case RadioAstronomySettings::PY_FLUX: + m_powerSeries->setName("Flux density"); + switch (m_settings.m_powerYUnits) + { + case RadioAstronomySettings::PY_SFU: + m_powerYAxis->setTitleText("Flux density (SFU)"); + break; + case RadioAstronomySettings::PY_JANSKY: + m_powerYAxis->setTitleText("Flux density (Jy)"); + break; + } + break; + } + + m_powerChart->addAxis(m_powerXAxis, Qt::AlignBottom); + m_powerChart->addAxis(m_powerYAxis, Qt::AlignLeft); + m_powerChart->addAxis(m_airTemps.yAxis(), Qt::AlignRight); + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_powerChart->addAxis(m_sensors[i].yAxis(), Qt::AlignRight); + } + + // Add data to series and calculate peaks + for (int i = 0; i < m_fftMeasurements.size(); i++) { + addToPowerSeries(m_fftMeasurements[i], i < (m_fftMeasurements.size() - 1)); + } + m_airTemps.addAllToSeries(); + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_sensors[i].addAllToSeries(); + } + + m_powerChart->addSeries(m_powerSeries); + m_powerSeries->attachAxis(m_powerXAxis); + m_powerSeries->attachAxis(m_powerYAxis); + + m_powerChart->addSeries(m_powerTsys0Series); + m_powerTsys0Series->attachAxis(m_powerXAxis); + m_powerTsys0Series->attachAxis(m_powerYAxis); + + m_powerChart->addSeries(m_powerGaussianSeries); + m_powerGaussianSeries->attachAxis(m_powerXAxis); + m_powerGaussianSeries->attachAxis(m_powerYAxis); + + m_airTemps.addToChart(m_powerChart, m_powerXAxis); + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_sensors[i].addToChart(m_powerChart, m_powerXAxis); + } + + m_powerChart->addSeries(m_powerPeakSeries); + m_powerPeakSeries->attachAxis(m_powerXAxis); + m_powerPeakSeries->attachAxis(m_powerYAxis); + + m_powerChart->addSeries(m_powerMarkerSeries); + m_powerMarkerSeries->attachAxis(m_powerXAxis); + m_powerMarkerSeries->attachAxis(m_powerYAxis); + + // Dark theme only has 5 colours for series, so use an extra unique colour (purple) + QPen pen(QColor(qRgb(146, 65, 146)), 2, Qt::SolidLine); + m_sensors[1].setPen(pen); + + // Don't have peaks and markers in legend + m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false); + m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false); + + ui->powerChart->setChart(m_powerChart); + + delete oldChart; +} + +void RadioAstronomyGUI::calCompletetReceived(const RadioAstronomy::MsgCalComplete& measurement) +{ + bool hot = measurement.getHot(); + int size = measurement.getSize(); + Real* data = measurement.getCal(); + + FFTMeasurement* fft = new FFTMeasurement(); + if (hot) + { + delete m_calHot; + m_calHot = fft; + ui->startCalHot->setStyleSheet("QToolButton { background: none; }"); + } + else + { + delete m_calCold; + m_calCold = fft; + ui->startCalCold->setStyleSheet("QToolButton { background: none; }"); + } + fft->m_fftData = data; + fft->m_fftSize = size; + fft->m_dateTime = measurement.getDateTime(); + fft->m_centerFrequency = m_centerFrequency; + fft->m_sampleRate = m_settings.m_sampleRate; + fft->m_integration = m_settings.m_integration; + fft->m_rfBandwidth = m_settings.m_rfBandwidth; + fft->m_omegaA = calcOmegaA(); + fft->m_omegaS = calcOmegaS(); + fft->m_coordsValid = m_coordsValid; + fft->m_ra = m_ra; + fft->m_dec = m_dec; + fft->m_azimuth = m_azimuth; + fft->m_elevation = m_elevation; + fft->m_l = m_l; + fft->m_b = m_b; + fft->m_vBCRS = m_vBCRS; + fft->m_vLSR = m_vLSR; + fft->m_solarFlux = m_solarFlux; + fft->m_airTemp = m_airTemps.lastValue(); + fft->m_skyTemp = m_skyTemp; + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + fft->m_sensor[i] = m_sensors[i].lastValue(); + } + fft->m_tSys0 = calcTSys0(); + fft->m_baseline = m_settings.m_spectrumBaseline; + + if (!hot) { + ui->calTsky->setText(QString::number(m_skyTemp, 'f', 1)); + } + + calcFFTTotalPower(fft); + calcCalAvgDiff(); + calibrate(); + calcFFTTemperatures(fft); + calcFFTTotalTemperature(fft); + plotCalMeasurements(); +} + +void RadioAstronomyGUI::plotCalMeasurements() +{ + m_calHotSeries->clear(); + m_calColdSeries->clear(); + + if (m_calHot || m_calCold) + { + float minVal = std::numeric_limits::max(); + float maxVal = -std::numeric_limits::max(); + + double size; + double sampleRate; + double centerFrequency; + if (m_calCold && m_calHot) + { + size = (double)std::min(m_calCold->m_fftSize, m_calHot->m_fftSize); + sampleRate = (double)m_calCold->m_sampleRate; + centerFrequency = (double)m_calCold->m_centerFrequency; + } + else if (m_calCold) + { + size = (double)m_calCold->m_fftSize; + sampleRate = (double)m_calCold->m_sampleRate; + centerFrequency = (double)m_calCold->m_centerFrequency; + } + else + { + size = (double)m_calHot->m_fftSize; + sampleRate = (double)m_calHot->m_sampleRate; + centerFrequency = (double)m_calHot->m_centerFrequency; + } + + double binRes = sampleRate / size; + double startFreq = centerFrequency - sampleRate / 2.0; + double freq = startFreq; + for (int i = 0; i < size; i++) + { + float value; + bool hotValid = m_calHot && (i < m_calHot->m_fftSize); + bool coldValid = m_calCold && (i < m_calCold->m_fftSize); + + if (hotValid) + { + value = CalcDb::dbPower(m_calHot->m_fftData[i]); + m_calHotSeries->append(freq / 1e6, value); + minVal = std::min(minVal, value); + maxVal = std::max(maxVal, value); + } + if (coldValid) + { + value = CalcDb::dbPower(m_calCold->m_fftData[i]); + m_calColdSeries->append(freq / 1e6, value); + minVal = std::min(minVal, value); + maxVal = std::max(maxVal, value); + } + freq += binRes; + } + + m_calYAxis->setRange(minVal, maxVal); + + double startFreqMHz = centerFrequency/1e6 - sampleRate/ 1e6 / 2.0; + double endFreqMHz = centerFrequency/1e6 + sampleRate/ 1e6 / 2.0; + m_calXAxis->setRange(startFreqMHz, endFreqMHz); + m_calXAxis->setReverse(m_settings.m_spectrumReverseXAxis); + } +} + +void RadioAstronomyGUI::plotCalSpectrum() +{ + QChart *oldChart = m_calChart; + + m_calChart = new QChart(); + + m_calChart->layout()->setContentsMargins(0, 0, 0, 0); + m_calChart->setMargins(QMargins(1, 1, 1, 1)); + m_calChart->setTheme(QChart::ChartThemeDark); + + m_calChart->legend()->setAlignment(Qt::AlignRight); + m_calChart->legend()->setVisible(m_settings.m_spectrumLegend); + + m_calHotSeries = new QLineSeries(); + m_calHotSeries->setName("Hot"); + m_calColdSeries = new QLineSeries(); + m_calColdSeries->setName("Cold"); + + m_calXAxis = new QValueAxis(); + m_calYAxis = new QValueAxis(); + + m_calChart->addAxis(m_calXAxis, Qt::AlignBottom); + m_calChart->addAxis(m_calYAxis, Qt::AlignLeft); + + m_calXAxis->setTitleText("Frequency (MHz)"); + calcSpectrumChartTickCount(m_calXAxis, size().width()); + m_calYAxis->setTitleText("Power (dBFS)"); + + m_calChart->addSeries(m_calHotSeries); + m_calHotSeries->attachAxis(m_calXAxis); + m_calHotSeries->attachAxis(m_calYAxis); + + m_calChart->addSeries(m_calColdSeries); + m_calColdSeries->attachAxis(m_calXAxis); + m_calColdSeries->attachAxis(m_calYAxis); + + plotCalMeasurements(); + + ui->spectrumChart->setChart(m_calChart); + + delete oldChart; +} + +void RadioAstronomyGUI::on_spectrumReference_valueChanged(double value) +{ + m_settings.m_spectrumReference = value; + spectrumUpdateYRange(); + if (!m_settings.m_spectrumAutoscale) { + applySettings(); + } +} + +void RadioAstronomyGUI::on_spectrumRange_valueChanged(double value) +{ + m_settings.m_spectrumRange = value; + if (m_settings.m_spectrumRange <= 1.0) + { + ui->spectrumRange->setSingleStep(0.1); + ui->spectrumRange->setDecimals(2); + ui->spectrumReference->setDecimals(2); + } + else + { + ui->spectrumRange->setSingleStep(1.0); + ui->spectrumRange->setDecimals(1); + ui->spectrumReference->setDecimals(1); + } + spectrumUpdateYRange(); + if (!m_settings.m_spectrumAutoscale) { + applySettings(); + } +} + +void RadioAstronomyGUI::on_spectrumSpan_valueChanged(double value) +{ + m_settings.m_spectrumSpan = value; + spectrumUpdateXRange(); + applySettings(); +} + +void RadioAstronomyGUI::on_spectrumCenterFreq_valueChanged(double value) +{ + double offset; + FFTMeasurement* fft = currentFFT(); + if (fft) { + offset = value - fft->m_centerFrequency/1e6; + } else { + offset = value - m_centerFrequency/1e6; + } + m_settings.m_spectrumCenterFreqOffset = offset; + spectrumUpdateXRange(); + applySettings(); +} + +void RadioAstronomyGUI::spectrumUpdateXRange(FFTMeasurement* fft) +{ + if (!fft) { + fft = currentFFT(); + } + if (m_fftXAxis && fft) + { + double startFreqMHz = fft->m_centerFrequency/1e6 - m_settings.m_spectrumSpan / 2.0; + double endFreqMHz = fft->m_centerFrequency/1e6 + m_settings.m_spectrumSpan / 2.0; + m_fftXAxis->setRange(startFreqMHz + m_settings.m_spectrumCenterFreqOffset, endFreqMHz + m_settings.m_spectrumCenterFreqOffset); + double lineFreqMHz = ui->spectrumLineFrequency->value(); + double lineFreq = lineFreqMHz * 1e6; + double startFreq = fft->m_centerFrequency + m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6; + double endFreq = fft->m_centerFrequency - m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6; + m_fftDopplerAxis->setRange(dopplerToVelocity(lineFreq, startFreq, fft), + dopplerToVelocity(lineFreq, endFreq, fft)); + } +} + +void RadioAstronomyGUI::spectrumUpdateYRange(FFTMeasurement* fft) +{ + if (!fft) { + fft = currentFFT(); + } + if (m_fftYAxis && fft) { + m_fftYAxis->setRange(m_settings.m_spectrumReference - m_settings.m_spectrumRange, m_settings.m_spectrumReference); + } +} + +void RadioAstronomyGUI::spectrumAutoscale() +{ + if (m_settings.m_spectrumAutoscale) + { + on_spectrumAutoscaleX_clicked(); + on_spectrumAutoscaleY_clicked(); + } +} + +// Scale Y axis according to min and max values +void RadioAstronomyGUI::on_spectrumAutoscale_toggled(bool checked) +{ + m_settings.m_spectrumAutoscale = checked; + ui->spectrumAutoscaleX->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumAutoscaleY->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumReference->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumRange->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumCenterFreq->setEnabled(!m_settings.m_spectrumAutoscale); + ui->spectrumSpan->setEnabled(!m_settings.m_spectrumAutoscale); + spectrumAutoscale(); + applySettings(); +} + +// Get minimum and maximum values in a series +// Assumes minVal and maxVal are initialised +static bool seriesMinAndMax(QLineSeries* series, qreal& minVal, qreal& maxVal) +{ + QVector points = series->pointsVector(); + for (int i = 0; i < points.size(); i++) + { + qreal power = points[i].y(); + minVal = std::min(minVal, power); + maxVal = std::max(maxVal, power); + } + return points.size() > 0; +} + +// Scale Y axis according to min and max values +void RadioAstronomyGUI::on_spectrumAutoscaleY_clicked() +{ + double minVal = std::numeric_limits::max(); + double maxVal = -std::numeric_limits::max(); + bool v0 = false, v1 = false; + if (m_fftSeries) { + v0 = seriesMinAndMax(m_fftSeries, minVal, maxVal); + } + if (m_fftLABSeries && m_settings.m_spectrumLAB) { + v1 = seriesMinAndMax(m_fftLABSeries, minVal, maxVal); + } + if (v0 || v1) + { + double range = (maxVal - minVal) * 1.2; // 20% wider than signal range, for space for markers + range = std::max(0.1, range); // Don't be smaller than minimum value we can set in GUI + ui->spectrumRange->setValue(range); // Call before setting reference, so number of decimals are adjusted + ui->spectrumReference->setValue(maxVal + range * 0.15); + } +} + +// Scale X axis according to min and max values +void RadioAstronomyGUI::on_spectrumAutoscaleX_clicked() +{ + FFTMeasurement* fft = currentFFT(); + if (fft) + { + ui->spectrumSpan->setValue(fft->m_sampleRate/1e6); + ui->spectrumCenterFreq->setValue(fft->m_centerFrequency/1e6); + } + else + { + ui->spectrumSpan->setValue(m_basebandSampleRate/1e6); + ui->spectrumCenterFreq->setValue(m_centerFrequency/1e6); + } +} + +RadioAstronomyGUI::FFTMeasurement* RadioAstronomyGUI::currentFFT() +{ + int index = ui->spectrumIndex->value(); + if ((index >= 0) && (index < m_fftMeasurements.size())) { + return m_fftMeasurements[index]; + } else { + return nullptr; + } +} + +void RadioAstronomyGUI::on_spectrumYUnits_currentIndexChanged(int index) +{ + QString text = ui->spectrumYUnits->currentText(); + if (text == "dBFS") + { + m_settings.m_spectrumYScale = RadioAstronomySettings::SY_DBFS; + ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Power (dBFS)"); + } + else if (text == "SNR") + { + m_settings.m_spectrumYScale = RadioAstronomySettings::SY_SNR; + ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("SNR"); + } + else if (text == "dBm") + { + m_settings.m_spectrumYScale = RadioAstronomySettings::SY_DBM; + ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Power (dBm)"); + } + else if (text == "Tsys K") + { + m_settings.m_spectrumYScale = RadioAstronomySettings::SY_TSYS; + ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Tsys (K)"); + } + else + { + m_settings.m_spectrumYScale = RadioAstronomySettings::SY_TSOURCE; + ui->spectrumMarkerTable->horizontalHeaderItem(SPECTRUM_MARKER_COL_VALUE)->setText("Tsource (K)"); + } + plotFFTMeasurement(); + applySettings(); +} + +void RadioAstronomyGUI::on_spectrumBaseline_currentIndexChanged(int index) +{ + m_settings.m_spectrumBaseline = (RadioAstronomySettings::SpectrumBaseline)index; + plotFFTMeasurement(); + if ((m_settings.m_powerYData == RadioAstronomySettings::PY_TSOURCE) || (m_settings.m_powerYData == RadioAstronomySettings::PY_FLUX)) { + plotPowerChart(); + } + applySettings(); +} + +// Convert frequency shift to velocity in km/s (+ve approaching) +static double lineDopplerVelocity(double centre, double f) +{ + return Astronomy::dopplerToVelocity(f, centre) / 1000.0f; +} + +// Convert frequency shift to velocity (+ve receeding - which seems to be the astronomical convention) +double RadioAstronomyGUI::dopplerToVelocity(double centre, double f, FFTMeasurement *fft) +{ + double v = lineDopplerVelocity(centre, f); + // Adjust in to selected reference frame + switch (m_settings.m_refFrame) + { + case RadioAstronomySettings::BCRS: + v -= fft->m_vBCRS; + break; + case RadioAstronomySettings::LSR: + v -= fft->m_vLSR; + break; + } + // Make +ve receeding + return -v; +} + +// Replot current FFT +void RadioAstronomyGUI::plotFFTMeasurement() +{ + plotFFTMeasurement(ui->spectrumIndex->value()); +} + +// Plot FFT with specified index +void RadioAstronomyGUI::plotFFTMeasurement(int index) +{ + if (index < m_fftMeasurements.size()) + { + FFTMeasurement *fft = m_fftMeasurements[index]; + + m_fftSeries->clear(); + m_fftHlineSeries->clear(); + m_fftGaussianSeries->clear(); + m_fftLABSeries->clear(); + m_fftPeakSeries->clear(); + + double binRes = fft->m_sampleRate / (double)fft->m_fftSize; + double startFreq = fft->m_centerFrequency - fft->m_sampleRate / 2.0; + + // Plot reference spectral line and Doppler axis + plotRefLine(fft); + + // Plot Gaussian for temp estimation + plotTempGaussian(startFreq, binRes, fft->m_fftSize); + + // Plot LAB reference data + if ( fft->m_coordsValid && m_settings.m_spectrumLAB + && ( (m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSYS) + || (m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSOURCE) + ) + ) + { + plotLAB(fft->m_l, fft->m_b, m_beamWidth); + } + + if ( ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_SNR) && !fft->m_snr) + || ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_DBM) && !fft->m_temp) + || ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSYS) && !fft->m_temp) + || ((m_settings.m_spectrumYScale == RadioAstronomySettings::SY_TSOURCE) && !fft->m_temp) + ) + { + m_fftChart->setTitle("No cal data: Run calibration or set units to dBFS."); + } + else + { + m_fftChart->setTitle(""); + + if (fft->m_coordsValid) + { + m_fftChart->setTitle(QString("RA: %1 Dec: %2 l: %3%7 b: %4%7 Az: %5%7 El: %6%7") + .arg(Units::decimalHoursToHoursMinutesAndSeconds(fft->m_ra)) + .arg(Units::decimalDegreesToDegreeMinutesAndSeconds(fft->m_dec)) + .arg(QString::number(fft->m_l, 'f', 1)) + .arg(QString::number(fft->m_b, 'f', 1)) + .arg(QString::number(fft->m_azimuth, 'f', 0)) + .arg(QString::number(fft->m_elevation, 'f', 0)) + .arg(QChar(0xb0))); + } + + int peakIdx = 0; + double peakValue = -std::numeric_limits::max(); + + double freq = startFreq; // Main spectrum seems to use bin midpoint - this uses lowest frequency, so we're tone at centre freq appears in centre of plot + + // Plot power/temp + for (int i = 0; i < fft->m_fftSize; i++) + { + qreal value; + switch (m_settings.m_spectrumYScale) + { + case RadioAstronomySettings::SY_DBFS: + value = fft->m_db[i]; + break; + case RadioAstronomySettings::SY_SNR: + value = fft->m_snr[i]; + break; + case RadioAstronomySettings::SY_DBM: + value = Astronomy::noisePowerdBm(fft->m_temp[i], fft->m_sampleRate/(double)fft->m_fftSize); + break; + case RadioAstronomySettings::SY_TSYS: + value = fft->m_temp[i]; + break; + case RadioAstronomySettings::SY_TSOURCE: + switch (m_settings.m_spectrumBaseline) + { + case RadioAstronomySettings::SBL_TSYS0: + value = fft->m_temp[i] - fft->m_tSys0; + break; + case RadioAstronomySettings::SBL_TMIN: + value = fft->m_temp[i] - fft->m_tempMin; + break; + case RadioAstronomySettings::SBL_CAL_COLD: + if (m_calCold) { + value = m_calG[i] * (fft->m_fftData[i] - m_calCold->m_fftData[i]); + } else { + value = 0.0; + } + break; + } + break; + } + if (value > peakValue) + { + peakValue = value; + peakIdx = i; + } + + m_fftSeries->append(freq / 1e6, value); + freq += binRes; + } + + double startFreqMHz = fft->m_centerFrequency/1e6 - m_settings.m_spectrumSpan / 2.0; + double endFreqMHz = fft->m_centerFrequency/1e6 + m_settings.m_spectrumSpan / 2.0; + + spectrumUpdateXRange(fft); + spectrumUpdateYRange(fft); + m_fftXAxis->setReverse(m_settings.m_spectrumReverseXAxis); + + switch (m_settings.m_spectrumYScale) + { + case RadioAstronomySettings::SY_DBFS: + m_fftYAxis->setTitleText("Power (dBFS)"); + break; + case RadioAstronomySettings::SY_SNR: + m_fftYAxis->setTitleText("SNR"); + break; + case RadioAstronomySettings::SY_DBM: + m_fftYAxis->setTitleText("Power (dBm)"); + break; + case RadioAstronomySettings::SY_TSYS: + m_fftYAxis->setTitleText("Tsys (K)"); + break; + case RadioAstronomySettings::SY_TSOURCE: + m_fftYAxis->setTitleText("Tsource (K)"); + break; + } + + // Plot peaks + if (m_settings.m_spectrumPeaks) + { + double peakFreqMHz = (startFreq + peakIdx * binRes) / 1e6; + double peakFreq = peakFreqMHz * 1e6; + + m_fftPeakSeries->append(peakFreqMHz, peakValue); + + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, peakFreqMHz); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, peakValue); + + calcVrAndDistanceToPeak(peakFreq, fft, SPECTRUM_MARKER_ROW_PEAK); + } + + // Update markers to track current data + if (m_spectrumM1Valid) + { + m_fftMarkerSeries->clear(); + int idx; + idx = (m_spectrumM1X - startFreqMHz) / (binRes/1e6); + if ((idx >= 0) && (idx < m_fftSeries->count())) + { + m_spectrumM1Y = m_fftSeries->at(idx).y(); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM1Y); + m_fftMarkerSeries->append(m_spectrumM1X, m_spectrumM1Y); + calcVrAndDistanceToPeak(m_spectrumM1X*1e6, fft, SPECTRUM_MARKER_ROW_M1); + } + if (m_spectrumM2Valid) + { + idx = (m_spectrumM2X - startFreqMHz) / (binRes/1e6); + if (idx < m_fftSeries->count()) + { + m_spectrumM2Y = m_fftSeries->at(idx).y(); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM2Y); + m_fftMarkerSeries->append(m_spectrumM2X, m_spectrumM2Y); + calcVrAndDistanceToPeak(m_spectrumM2X*1e6, fft, SPECTRUM_MARKER_ROW_M2); + calcSpectrumMarkerDelta(); + } + } + } + + spectrumAutoscale(); + } + } +} + +bool RadioAstronomyGUI::losMarkerEnabled(const QString& name) +{ + if (m_settings.m_spectrumDistance && m_settings.m_spectrumRefLine) + { + if (name == "Max") { + return m_settings.m_spectrumPeaks; + } else if (name == "M1") { + return m_settings.m_spectrumMarkers; + } else { + return m_settings.m_spectrumMarkers; + } + } + return false; +} + +void RadioAstronomyGUI::showLoSMarker(const QString& name) +{ + if (losMarkerEnabled(name)) + { + if (name == "Max") { + showLoSMarker(SPECTRUM_MARKER_ROW_PEAK); + } else if (name == "M1") { + showLoSMarker(SPECTRUM_MARKER_ROW_M1); + } else { + showLoSMarker(SPECTRUM_MARKER_ROW_M2); + } + } +} + +void RadioAstronomyGUI::showLoSMarker(int row) +{ + bool ok; + float d = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->data(Qt::DisplayRole).toFloat(&ok); + if (ok) + { + FFTMeasurement *fft = currentFFT(); + if (fft) + { + QString name = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_NAME)->text(); + updateLoSMarker(name, fft->m_l, fft->m_b, d); + } + } +} + +void RadioAstronomyGUI::updateLoSMarker(const QString& name, float l, float b, float d) +{ + // Send to Star Tracker + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *messageQueues = messagePipes.getMessageQueues(m_radioAstronomy, "startracker.display"); + if (messageQueues) + { + QList::iterator it = messageQueues->begin(); + + for (; it != messageQueues->end(); ++it) + { + SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplayLoSSettings(); + swgSettings->setName(new QString(name)); + swgSettings->setL(l); + swgSettings->setB(b); + swgSettings->setD(d); + (*it)->push(MainCore::MsgStarTrackerDisplayLoSSettings::create(m_radioAstronomy, swgSettings)); + } + } +} + +void RadioAstronomyGUI::clearLoSMarker(const QString& name) +{ + // Set d to 0 to clear + updateLoSMarker(name, 0.0f, 0.0f, 0.0f); +} + +void RadioAstronomyGUI::calcVrAndDistanceToPeak(double freq, FFTMeasurement *fft, int row) +{ + double lineFreq = ui->spectrumLineFrequency->value() * 1e6; + + // Calculate radial velocity (along line-of-sight) from Doppler shift + double vR = dopplerToVelocity(lineFreq, freq, fft); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_VR)->setData(Qt::DisplayRole, vR); + + // Tangent point method only valid for Galactic quadrants I (0-90) and IV (270-360) + if ((fft->m_l < 90.0) || (fft->m_l > 270.0)) + { + // Calculate minimum distance to Galactic centre along line of sight (tangential radius) + double rMin = m_settings.m_sunDistanceToGC * sin(Units::degreesToRadians(fft->m_l)); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R_MIN)->setData(Qt::DisplayRole, rMin); + + // Calculate orbital velocity at tangent/minimum point + // This is the velocity to plot for the rotation curve + double w0 = m_settings.m_sunOrbitalVelocity / m_settings.m_sunDistanceToGC; + double vOrb = vR + rMin * w0; + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_V)->setData(Qt::DisplayRole, vOrb); + } + else + { + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R_MIN)->setText(""); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_V)->setText(""); + } + + // Calculate distance of HI cloud (as indicated by a peak) to Sun and Galactic center + double r, d1, d2; + int solutions = calcDistanceToPeak(vR, fft->m_l, fft->m_b, r, d1, d2); + if (solutions == 0) + { + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setText(""); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setText(""); + } + else if (solutions == 1) + { + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setData(Qt::DisplayRole, r); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setData(Qt::DisplayRole, d1); + } + else + { + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_R)->setData(Qt::DisplayRole, r); + ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_D)->setText(QString("%1/%2").arg(QString::number(d1, 'f', 1)).arg(QString::number(d2, 'f', 1))); + } + + // Send to Star Tracker + QString name = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_NAME)->text(); + if (losMarkerEnabled(name)) + { + if ((solutions == 0) || isnan(d1)) + { + updateLoSMarker(name, fft->m_l, fft->m_b, 0.0f); + } + else if (solutions == 1) + { + updateLoSMarker(name, fft->m_l, fft->m_b, d1); + } + else + { + bool plotMax = ui->spectrumMarkerTable->item(row, SPECTRUM_MARKER_COL_PLOT_MAX)->checkState() == Qt::Checked; + if ((plotMax && (d1 > d2)) || (!plotMax && (d1 < d2))) { + updateLoSMarker(name, fft->m_l, fft->m_b, d1); + } else { + updateLoSMarker(name, fft->m_l, fft->m_b, d2); + } + } + } +} + +int RadioAstronomyGUI::calcDistanceToPeak(double vr, float l, float b, double& r, double &d1, double &d2) +{ + // Radio Astronomy 4th edition - Burke - p343 + double r0 = m_settings.m_sunDistanceToGC; // Distance of Sun to Galactic centre in kpc + double v0 = m_settings.m_sunOrbitalVelocity; // Orbital velocity of the Sun around Galactic centre in km/s + double w0 = v0/r0; // Angular velocity of the Sun + double gl = Units::degreesToRadians(l); + double gb = Units::degreesToRadians(b); + double w = vr/(r0*sin(gl)*cos(gb))+w0; + r = v0/w; // Assume constant v, regardless of distance from GC - Dark matter magic - Not valid <1kpc from GC + if (r < 0) { + return 0; + } + // https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_non-included_angle_given_(SSA) + double beta = gl; + double d = sin(beta)*r0/r; + if (d > 1.0) { + // No solutions + return 0; + } + if ((r <= r0) && (beta >= M_PI/2.0)) { + return 0; + } + double gamma = asin(d); + double alpha1 = M_PI - beta - gamma; + d1 = r * sin(alpha1)/sin(beta); // Distance from Sun to peak + if (r < r0) + { + double alpha2 = M_PI - beta - (M_PI-gamma); + d2 = r * sin(alpha2)/sin(beta); // Distance from Sun to peak + return 2; + } + else + { + return 1; + } +} + +void RadioAstronomyGUI::spectrumMarkerTableItemChanged(QTableWidgetItem *item) +{ + if (item->column() == SPECTRUM_MARKER_COL_PLOT_MAX) { + // Plot max checkbox clicked + calcDistances(); + } +} + +void RadioAstronomyGUI::calcDistances() +{ + double freq; + bool ok; + FFTMeasurement* fft = currentFFT(); + if (fft) + { + freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_PEAK, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok); + if (ok) { + calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_PEAK); + } + freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok); + if (ok) { + calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_M1); + } + freq = ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_FREQ)->data(Qt::DisplayRole).toDouble(&ok); + if (ok) { + calcVrAndDistanceToPeak(freq*1e6, fft, SPECTRUM_MARKER_ROW_M2); + } + } +} + +void RadioAstronomyGUI::plotRefLine(FFTMeasurement *fft) +{ + double lineFreqMHz = ui->spectrumLineFrequency->value(); + double lineFreq = lineFreqMHz * 1e6; + QString refFrame[] = {"Topocentric", "Barycentric", "LSR"}; + m_fftDopplerAxis->setTitleText(QString("%1 radial velocity (km/s - +ve receeding)").arg(refFrame[m_settings.m_refFrame])); + m_fftHlineSeries->setName(QString("%1 line").arg(ui->spectrumLine->currentText())); + m_fftHlineSeries->append(0.0f, -200.0f); // For dB + m_fftHlineSeries->append(0.0f, 10000.0f); // For temp can be >1e6? + double startFreq = fft->m_centerFrequency + m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6; + double endFreq = fft->m_centerFrequency - m_settings.m_spectrumSpan*1e6 / 2.0 + m_settings.m_spectrumCenterFreqOffset*1e6; + m_fftDopplerAxis->setRange(dopplerToVelocity(lineFreq, startFreq, fft), + dopplerToVelocity(lineFreq, endFreq, fft)); + m_fftDopplerAxis->setReverse(!m_settings.m_spectrumReverseXAxis); + m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine); +} + +void RadioAstronomyGUI::plotTempGaussian(double startFreq, double freqStep, int steps) +{ + m_fftGaussianSeries->clear(); + double f0 = ui->spectrumGaussianFreq->value() * 1e6; + double a = ui->spectrumGaussianAmp->value(); + double floor = ui->spectrumGaussianFloor->value(); + double fwhm = ui->spectrumGaussianFWHM->value(); + double fwhm_sq = fwhm*fwhm; + double freq = startFreq; + for (int i = 0; i < steps; i++) + { + double fd = freq - f0; + double g = a * std::exp(-4.0*M_LN2*fd*fd/fwhm_sq) + floor; + m_fftGaussianSeries->append(freq/1e6, g); + freq += freqStep; + } +} + +void RadioAstronomyGUI::calcFFTPower(FFTMeasurement* fft) +{ + // Convert linear to dB + for (int i = 0; i < fft->m_fftSize; i++) { + fft->m_db[i] = CalcDb::dbPower(fft->m_fftData[i]); + } +} + +void RadioAstronomyGUI::calcFFTTotalPower(FFTMeasurement* fft) +{ + double total = 0.0; + for (int i = 0; i < fft->m_fftSize; i++) { + total += fft->m_fftData[i]; + } + fft->m_totalPower = total; + fft->m_totalPowerdBFS = CalcDb::dbPower(total); +} + +void RadioAstronomyGUI::calcFFTTemperatures(FFTMeasurement* fft) +{ + if (m_calCold && !fft->m_snr) { + fft->m_snr = new Real[fft->m_fftSize]; + } + if (m_calG && !fft->m_temp) { + fft->m_temp = new Real[fft->m_fftSize]; + } + for (int i = 0; i < fft->m_fftSize; i++) + { + if (fft->m_snr && m_calCold) + { + // Calculate SNR (relative to cold cal) + fft->m_snr[i] = fft->m_fftData[i] / m_calCold->m_fftData[i]; + } + if (m_calG) + { + // Calculate temperature using scaling from hot cal + fft->m_temp[i] = m_calG[i] * fft->m_fftData[i]; + // Calculate temperature using linear interpolation from hot/cold cal + //fft->m_temp[i] = m_calG[i] * (fft->m_fftData[i] - m_calCold->m_fftData[i]) + m_settings.m_tCalCold; + //fft->m_temp[i] = std::max(fft->m_temp[i], 0.0f); // Can't have negative temperatures + } + } + calcFFTMinTemperature(fft); +} + +void RadioAstronomyGUI::calcFFTMinTemperature(FFTMeasurement* fft) +{ + fft->m_tempMin = 0; + if (fft->m_temp) + { + // Select minimum from within band. 95% of that to account for a little bit of inband rolloff + float tempMin = std::numeric_limits::max(); + double pc = 0.95 * fft->m_rfBandwidth / (double)fft->m_sampleRate; + int count = (int)(pc * fft->m_fftSize); + int start = (fft->m_fftSize - count) / 2; + for (int i = 0; i < count; i++) + { + int idx = i + start; + tempMin = std::min(tempMin, fft->m_temp[idx]); + } + if (tempMin != std::numeric_limits::max()) { + fft->m_tempMin = tempMin; + } + } +} + +// Estimate Tsource by subtracting selected baseline +double RadioAstronomyGUI::calcTSource(FFTMeasurement *fft) const +{ + switch (fft->m_baseline) + { + case RadioAstronomySettings::SBL_TSYS0: + return fft->m_tSys - fft->m_tSys0; + case RadioAstronomySettings::SBL_TMIN: + return fft->m_tSys - fft->m_tempMin; + case RadioAstronomySettings::SBL_CAL_COLD: + if (m_calCold) { + return fft->m_tSys - m_calCold->m_tSys; + } + break; + } + return fft->m_tSys; +} + +// Calculate spectral flux density of source +double RadioAstronomyGUI::calcFlux(double Ta, const FFTMeasurement *fft) const +{ + // Factor of 2 here assumes single polarization + // See equation 5.13 and 5.20 in Radio Astronomy 4th edition - Burke + double lambda = Astronomy::m_speedOfLight / (double)fft->m_centerFrequency; + return 2.0 * Astronomy::m_boltzmann * Ta * fft->m_omegaA / (lambda * lambda); +} + +void RadioAstronomyGUI::calcFFTTotalTemperature(FFTMeasurement* fft) +{ + if (fft->m_temp) + { + double tempSum = 0.0; + for (int i = 0; i < fft->m_fftSize; i++) { + tempSum += fft->m_temp[i]; + } + + // Convert from temperature to power in Watts and dBm + Real bw = fft->m_sampleRate/(Real)fft->m_fftSize; + fft->m_totalPowerWatts = Astronomy::m_boltzmann * tempSum * bw; + fft->m_totalPowerdBm = Astronomy::noisePowerdBm(tempSum, bw); + fft->m_tSys = tempSum/fft->m_fftSize; + + // Esimate source temperature + fft->m_tSource = calcTSource(fft); + + // Calculate error due to thermal noise and gain variation + fft->m_sigmaT = calcSigmaT(fft); + fft->m_sigmaS = calcSigmaS(fft); + + // Calculate spectral flux density of source + fft->m_flux = calcFlux(fft->m_tSource, fft); + } +} + +void RadioAstronomyGUI::updatePowerColumns(int row, FFTMeasurement* fft) +{ + ui->powerTable->item(row, POWER_COL_TSYS)->setData(Qt::DisplayRole, fft->m_tSys); + ui->powerTable->item(row, POWER_COL_TSYS0)->setData(Qt::DisplayRole, fft->m_tSys0); + ui->powerTable->item(row, POWER_COL_TSOURCE)->setData(Qt::DisplayRole, fft->m_tSource); + if (m_settings.m_sourceType != RadioAstronomySettings::UNKNOWN) { + ui->powerTable->item(row, POWER_COL_TB)->setData(Qt::DisplayRole, fft->m_tSource/beamFillingFactor()); + } else { + ui->powerTable->item(row, POWER_COL_TB)->setText(""); + } + ui->powerTable->item(row, POWER_COL_FLUX)->setData(Qt::DisplayRole, Units::wattsPerMetrePerHertzToJansky(fft->m_flux)); + ui->powerTable->item(row, POWER_COL_SIGMA_T)->setData(Qt::DisplayRole, fft->m_sigmaT); + ui->powerTable->item(row, POWER_COL_SIGMA_S)->setData(Qt::DisplayRole, fft->m_sigmaS); + ui->powerTable->item(row, POWER_COL_OMEGA_A)->setData(Qt::DisplayRole, fft->m_omegaA); + ui->powerTable->item(row, POWER_COL_OMEGA_S)->setData(Qt::DisplayRole, fft->m_omegaS); +} + +void RadioAstronomyGUI::addFFT(FFTMeasurement *fft, bool skipCalcs) +{ + m_fftMeasurements.append(fft); + + powerMeasurementReceived(fft, skipCalcs); // Call before ui->spectrumIndex->setValue, so table row is valid + update2DImage(fft, skipCalcs); + + ui->spectrumIndex->setRange(0, m_fftMeasurements.size() - 1); + if ((ui->spectrumIndex->value() == m_fftMeasurements.size() - 2) || (m_fftMeasurements.size() == 1)) { + ui->spectrumIndex->setValue(m_fftMeasurements.size() - 1); + } + if ( m_fftMeasurements.size() == 1) + { + // Force drawing for first measurement + on_spectrumIndex_valueChanged(0); + } +} + +void RadioAstronomyGUI::fftMeasurementReceived(const RadioAstronomy::MsgFFTMeasurement& measurement) +{ + FFTMeasurement *fft = new FFTMeasurement(); + fft->m_fftData = measurement.getFFT(); + fft->m_fftSize = measurement.getSize(); + fft->m_dateTime = measurement.getDateTime(); + fft->m_centerFrequency = m_centerFrequency; + fft->m_sampleRate = m_settings.m_sampleRate; + fft->m_integration = m_settings.m_integration; + fft->m_rfBandwidth = m_settings.m_rfBandwidth; + fft->m_omegaA = calcOmegaA(); + fft->m_omegaS = calcOmegaS(); + fft->m_coordsValid = m_coordsValid; + fft->m_ra = m_ra; + fft->m_dec = m_dec; + fft->m_azimuth = m_azimuth; + fft->m_elevation = m_elevation; + fft->m_l = m_l; + fft->m_b = m_b; + fft->m_vBCRS = m_vBCRS; + fft->m_vLSR = m_vLSR; + fft->m_solarFlux = m_solarFlux; + fft->m_airTemp = m_airTemps.lastValue(); + fft->m_skyTemp = m_skyTemp; + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + fft->m_sensor[i] = m_sensors[i].lastValue(); + } + fft->m_db = new Real[fft->m_fftSize]; + fft->m_sweepIndex = m_sweepIndex++; + fft->m_tSys0 = calcTSys0(); + fft->m_baseline = m_settings.m_spectrumBaseline; + + calcFFTPower(fft); + calcFFTTotalPower(fft); + calcFFTTemperatures(fft); + calcFFTTotalTemperature(fft); + addFFT(fft); +} + +void RadioAstronomyGUI::on_spectrumIndex_valueChanged(int value) +{ + if (value < m_fftMeasurements.size()) + { + plotFFTMeasurement(value); + + // Highlight in table + ui->powerTable->selectRow(value); + ui->powerTable->scrollTo(ui->powerTable->model()->index(value, 0)); + ui->spectrumDateTime->setDateTime(m_fftMeasurements[value]->m_dateTime); + // Display target in Star Tracker + MessagePipes& messagePipes = MainCore::instance()->getMessagePipes(); + QList *messageQueues = messagePipes.getMessageQueues(m_radioAstronomy, "startracker.display"); + if (messageQueues) + { + QList::iterator it = messageQueues->begin(); + + for (; it != messageQueues->end(); ++it) + { + SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = new SWGSDRangel::SWGStarTrackerDisplaySettings(); + swgSettings->setDateTime(new QString(m_fftMeasurements[value]->m_dateTime.toString(Qt::ISODateWithMs))); + swgSettings->setAzimuth(m_fftMeasurements[value]->m_azimuth); + swgSettings->setElevation(m_fftMeasurements[value]->m_elevation); + (*it)->push(MainCore::MsgStarTrackerDisplaySettings::create(m_radioAstronomy, swgSettings)); + } + } + } +} + +void RadioAstronomyGUI::plotSpectrum() +{ + QChart *oldChart = m_fftChart; + + m_fftChart = new QChart(); + + m_fftChart->layout()->setContentsMargins(0, 0, 0, 0); + m_fftChart->setMargins(QMargins(1, 1, 1, 1)); + m_fftChart->setTheme(QChart::ChartThemeDark); + + m_fftChart->legend()->setAlignment(Qt::AlignRight); + m_fftChart->legend()->setVisible(m_settings.m_spectrumLegend); + + m_fftSeries = new QLineSeries(); + m_fftSeries->setName("Measurement"); + connect(m_fftSeries, &QXYSeries::clicked, this, &RadioAstronomyGUI::spectrumSeries_clicked); + + // Plot vertical reference spectral line + m_fftHlineSeries = new QLineSeries(); + m_fftHlineSeries->setName(QString("%1 line").arg(ui->spectrumLine->currentText())); + m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine); + + // Plot peak info + m_fftPeakSeries = new QScatterSeries(); + m_fftPeakSeries->setPointLabelsVisible(true); + m_fftPeakSeries->setMarkerSize(5); + m_fftPeakSeries->setName("Max"); + + // Markers + m_fftMarkerSeries = new QScatterSeries(); + m_fftMarkerSeries->setPointLabelsVisible(true); + m_fftMarkerSeries->setMarkerSize(5); + m_fftMarkerSeries->setName("Markers"); + + // Gaussian + m_fftGaussianSeries = new QLineSeries(); + m_fftGaussianSeries->setName("Gaussian fit"); + m_fftGaussianSeries->setVisible(m_settings.m_spectrumTemp); + + m_fftLABSeries = new QLineSeries(); + m_fftLABSeries->setName("LAB reference"); + m_fftLABSeries->setVisible(m_settings.m_spectrumLAB); + + m_fftXAxis = new QValueAxis(); + m_fftYAxis = new QValueAxis(); + m_fftDopplerAxis = new QValueAxis(); + + m_fftChart->addAxis(m_fftXAxis, Qt::AlignBottom); + m_fftChart->addAxis(m_fftYAxis, Qt::AlignLeft); + m_fftChart->addAxis(m_fftDopplerAxis, Qt::AlignTop); + + m_fftXAxis->setTitleText("Frequency (MHz)"); + calcSpectrumChartTickCount(m_fftXAxis, size().width()); + calcSpectrumChartTickCount(m_fftDopplerAxis, size().width()); + m_fftYAxis->setTitleText("Power"); + + m_fftChart->addSeries(m_fftSeries); + m_fftSeries->attachAxis(m_fftXAxis); + m_fftSeries->attachAxis(m_fftYAxis); + + m_fftChart->addSeries(m_fftHlineSeries); + //m_fftHlineSeries->attachAxis(m_fftXAxis); + m_fftHlineSeries->attachAxis(m_fftDopplerAxis); + m_fftHlineSeries->attachAxis(m_fftYAxis); + + m_fftChart->addSeries(m_fftGaussianSeries); + m_fftGaussianSeries->attachAxis(m_fftXAxis); + m_fftGaussianSeries->attachAxis(m_fftYAxis); + + m_fftChart->addSeries(m_fftLABSeries); + //m_fftLABSeries->attachAxis(m_fftXAxis); + m_fftLABSeries->attachAxis(m_fftDopplerAxis); + m_fftLABSeries->attachAxis(m_fftYAxis); + + m_fftChart->addSeries(m_fftPeakSeries); + m_fftPeakSeries->attachAxis(m_fftXAxis); + m_fftPeakSeries->attachAxis(m_fftYAxis); + + m_fftChart->addSeries(m_fftMarkerSeries); + m_fftMarkerSeries->attachAxis(m_fftXAxis); + m_fftMarkerSeries->attachAxis(m_fftYAxis); + + // Don't have peaks and markers in legend + m_fftChart->legend()->markers(m_fftPeakSeries)[0]->setVisible(false); + m_fftChart->legend()->markers(m_fftMarkerSeries)[0]->setVisible(false); + + ui->spectrumChart->setChart(m_fftChart); + + delete oldChart; +} + +// Calculate galactic background temperature based on center frequency +void RadioAstronomyGUI::calcGalacticBackgroundTemp() +{ + // https://arxiv.org/ftp/arxiv/papers/1912/1912.12699.pdf - page 6 + // 17.1, 25.2 and 54.8 K for the 10th, 50th and 90th percentile of the all-sky distribution + // See also ITU-R P.372-7 section 6 + // If this is used for cold calibration, we don't want to use the higher value + double temp = 25.2 * std::pow(m_centerFrequency/408000000.0, -2.75); + ui->tempGal->setValue(temp); +} + +// Calculate athmospheric noise temperature based on air temperature, zenith opacity and elevation +void RadioAstronomyGUI::calcAtmosphericTemp() +{ + float el = m_settings.m_elevation; + if (m_settings.m_elevation < 1.0f) { + el = 1.0f; // Avoid divide by 0 and limit max value to match ITU-R P.372-7 figure 5 + } + double temp = Units::celsiusToKelvin(m_settings.m_tempAir) * (1.0 - std::exp(-m_settings.m_zenithOpacity/cos(Units::degreesToRadians(90.0f - el)))); + ui->tempAtm->setValue(temp); +} + +void RadioAstronomyGUI::on_tempRXSelect_currentIndexChanged(int value) +{ + if (value == 0) + { + // T_RX + ui->tempRX->setValue(m_settings.m_tempRX); + ui->tempRXUnitsLabel->setText("K"); + } + else + { + // NF + ui->tempRX->setValue(Units::noiseTempToNoiseFigureTo(m_settings.m_tempRX)); + ui->tempRXUnitsLabel->setText("dB"); + } +} + +void RadioAstronomyGUI::on_tempRX_valueChanged(double value) +{ + if (ui->tempRXSelect->currentIndex() == 0) { + m_settings.m_tempRX = value; + } else { + m_settings.m_tempRX = Units::noiseFigureToNoiseTemp(value); + } + updateTSys0(); + applySettings(); +} + +void RadioAstronomyGUI::on_tempCMB_valueChanged(double value) +{ + m_settings.m_tempCMB = value; + updateTSys0(); + applySettings(); +} + +void RadioAstronomyGUI::on_tempGal_valueChanged(double value) +{ + m_settings.m_tempGal = value; + updateTSys0(); + applySettings(); +} + +void RadioAstronomyGUI::on_tempSP_valueChanged(double value) +{ + m_settings.m_tempSP = value; + updateTSys0(); + applySettings(); +} + +void RadioAstronomyGUI::on_tempAtm_valueChanged(double value) +{ + m_settings.m_tempAtm = value; + updateTSys0(); + applySettings(); +} + +void RadioAstronomyGUI::on_tempAir_valueChanged(double value) +{ + m_settings.m_tempAir = value; + if (m_settings.m_tempAtmLink) { + calcAtmosphericTemp(); + } + applySettings(); +} + +void RadioAstronomyGUI::on_zenithOpacity_valueChanged(double value) +{ + m_settings.m_zenithOpacity = value; + if (m_settings.m_tempAtmLink) { + calcAtmosphericTemp(); + } + applySettings(); +} + +void RadioAstronomyGUI::on_elevation_valueChanged(double value) +{ + m_settings.m_elevation = value; + if (m_settings.m_tempAtmLink) { + calcAtmosphericTemp(); + } + applySettings(); +} + +void RadioAstronomyGUI::on_elevationLink_toggled(bool checked) +{ + m_settings.m_elevationLink = checked; + ui->elevation->setValue(m_elevation); + ui->elevation->setEnabled(!m_settings.m_elevationLink); + applySettings(); +} + +void RadioAstronomyGUI::on_tempAtmLink_toggled(bool checked) +{ + m_settings.m_tempAtmLink = checked; + ui->tempAtm->setEnabled(!m_settings.m_tempAtmLink); + if (checked) { + calcAtmosphericTemp(); + } + applySettings(); +} + +void RadioAstronomyGUI::on_tempAirLink_toggled(bool checked) +{ + m_settings.m_tempAirLink = checked; + ui->tempAir->setEnabled(!m_settings.m_tempAirLink); + if (checked) + { + ui->tempAir->setValue(m_airTemps.lastValue()); + calcAtmosphericTemp(); + } + applySettings(); +} + +void RadioAstronomyGUI::on_tempGalLink_toggled(bool checked) +{ + m_settings.m_tempGalLink = checked; + if (checked) { + calcGalacticBackgroundTemp(); + } + ui->tempGal->setEnabled(!m_settings.m_tempGalLink); + applySettings(); +} + +void RadioAstronomyGUI::on_tCalHotSelect_currentIndexChanged(int value) +{ + if (value == 0) + { + // Thot + ui->tCalHot->setValue(m_settings.m_tCalHot); + ui->tCalHotUnitsLabel->setText("K"); + } + else + { + // Phot + double power = Astronomy::noisePowerdBm(m_settings.m_tCalHot, m_settings.m_sampleRate); + ui->tCalHot->setValue(power); + ui->tCalHotUnitsLabel->setText("dBm"); + } +} + +void RadioAstronomyGUI::on_tCalHot_valueChanged(double value) +{ + double temp; + if (ui->tCalHotSelect->currentIndex() == 0) { + temp = value; + } else { + temp = Astronomy::noiseTemp(value, m_settings.m_sampleRate); + } + m_settings.m_tCalHot = (float)temp; + calibrate(); + applySettings(); +} + +void RadioAstronomyGUI::on_tCalColdSelect_currentIndexChanged(int value) +{ + if (value == 0) + { + // Tcold + ui->tCalCold->setValue(m_settings.m_tCalCold); + ui->tCalColdUnitsLabel->setText("K"); + } + else + { + // Pcold + double power = Astronomy::noisePowerdBm(m_settings.m_tCalCold, m_settings.m_sampleRate); + ui->tCalCold->setValue(power); + ui->tCalColdUnitsLabel->setText("dBm"); + } +} + +void RadioAstronomyGUI::on_tCalCold_valueChanged(double value) +{ + double temp; + if (ui->tCalColdSelect->currentIndex() == 0) { + temp = value; + } else { + temp = Astronomy::noiseTemp(value, m_settings.m_sampleRate); + } + m_settings.m_tCalCold = (float)temp; + calibrate(); + applySettings(); +} + +void RadioAstronomyGUI::on_spectrumLine_currentIndexChanged(int value) +{ + m_settings.m_line = (RadioAstronomySettings::Line)value; + displaySpectrumLineFrequency(); + plotFFTMeasurement(); + applySettings(); +} + +void RadioAstronomyGUI::displaySpectrumLineFrequency() +{ + switch (m_settings.m_line) + { + case RadioAstronomySettings::HI: + ui->spectrumLineFrequency->setValue(Astronomy::m_hydrogenLineFrequency / 1e6); + ui->spectrumLineFrequency->setEnabled(false); + break; + case RadioAstronomySettings::OH: + ui->spectrumLineFrequency->setValue(Astronomy::m_hydroxylLineFrequency / 1e6); + ui->spectrumLineFrequency->setEnabled(false); + break; + case RadioAstronomySettings::DI: + ui->spectrumLineFrequency->setValue(Astronomy::m_deuteriumLineFrequency / 1e6); + ui->spectrumLineFrequency->setEnabled(false); + break; + case RadioAstronomySettings::CUSTOM_LINE: + ui->spectrumLineFrequency->setValue(m_settings.m_lineCustomFrequency / 1e6); + ui->spectrumLineFrequency->setEnabled(true); + break; + } +} + +void RadioAstronomyGUI::on_spectrumLineFrequency_valueChanged(double value) +{ + m_settings.m_lineCustomFrequency = value * 1e6; + plotFFTMeasurement(); + applySettings(); +} + +void RadioAstronomyGUI::on_refFrame_currentIndexChanged(int value) +{ + m_settings.m_refFrame = (RadioAstronomySettings::RefFrame)value; + plotFFTMeasurement(); + applySettings(); +} + +void RadioAstronomyGUI::on_sunDistanceToGC_valueChanged(double value) +{ + m_settings.m_sunDistanceToGC = value; + applySettings(); + calcDistances(); +} + +void RadioAstronomyGUI::on_sunOrbitalVelocity_valueChanged(double value) +{ + m_settings.m_sunOrbitalVelocity = value; + applySettings(); + calcDistances(); +} + +void RadioAstronomyGUI::on_savePowerChartImage_clicked() +{ + QFileDialog fileDialog(nullptr, "Select file to save image to", "", "*.png;*.jpg;*.jpeg;*.bmp;*.ppm;*.xbm;*.xpm"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + QImage image(ui->powerChart->size(), QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter painter(&image); + ui->powerChart->render(&painter); + if (!image.save(fileNames[0])) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to save image to %1").arg(fileNames[0])); + } + } + } +} + +void RadioAstronomyGUI::on_saveSpectrumChartImage_clicked() +{ + QFileDialog fileDialog(nullptr, "Select file to save image to", "", "*.png;*.jpg;*.jpeg;*.bmp;*.ppm;*.xbm;*.xpm"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + QImage image(ui->spectrumChart->size(), QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter painter(&image); + ui->spectrumChart->render(&painter); + if (!image.save(fileNames[0])) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to save image to %1").arg(fileNames[0])); + } + } + } +} + +void RadioAstronomyGUI::on_saveSpectrumChartImages_clicked() +{ + if (m_fftMeasurements.size() > 1) + { + // Get filename of animation file + QFileDialog fileDialog(nullptr, "Select file to save animation to", "", "*.png"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + // Create animation file + APNG apng(m_fftMeasurements.size()); + + // Plot each FFT to a temp .png file (in memory) then append to animation file + for (int i = 0; i < m_fftMeasurements.size(); i++) + { + plotFFTMeasurement(i); + QImage image(ui->spectrumChart->size(), QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter painter(&image); + ui->spectrumChart->render(&painter); + apng.addImage(image); + } + if (!apng.save(fileNames[0])) { + QMessageBox::critical(this, "Radio Astronomy", QString("Failed to write to file %1").arg(fileNames[0])); + } + } + } + } +} + +void RadioAstronomyGUI::on_spectrumReverseXAxis_toggled(bool checked) +{ + m_settings.m_spectrumReverseXAxis = checked; + applySettings(); + if (ui->spectrumChartSelect->currentIndex() == 0) { + plotFFTMeasurement(); + } else { + m_calXAxis->setReverse(m_settings.m_spectrumReverseXAxis); + } +} + +void RadioAstronomyGUI::on_powerShowPeak_toggled(bool checked) +{ + m_settings.m_powerPeaks = checked; + updatePowerMarkerTableVisibility(); + applySettings(); + if (m_powerPeakSeries) + { + m_powerPeakSeries->setVisible(checked); + if (checked) { + m_powerChart->legend()->markers(m_powerPeakSeries)[0]->setVisible(false); + } + } + arrangeRollups(); +} + +void RadioAstronomyGUI::on_spectrumPeak_toggled(bool checked) +{ + m_settings.m_spectrumPeaks = checked; + updateSpectrumMarkerTableVisibility(); + plotFFTMeasurement(); + applySettings(); + if (m_fftChart) + { + if (checked) { + m_fftChart->legend()->markers(m_fftPeakSeries)[0]->setVisible(false); + showLoSMarker("Max"); + } else { + clearLoSMarker("Max"); + } + } + arrangeRollups(); +} + +void RadioAstronomyGUI::on_powerShowMarker_toggled(bool checked) +{ + m_settings.m_powerMarkers = checked; + updatePowerMarkerTableVisibility(); + applySettings(); + if (m_powerMarkerSeries) + { + m_powerMarkerSeries->setVisible(checked); + if (checked) { + m_powerChart->legend()->markers(m_powerMarkerSeries)[0]->setVisible(false); + } + } + updatePowerSelect(); + arrangeRollups(); +} + +void RadioAstronomyGUI::on_powerShowAvg_toggled(bool checked) +{ + m_settings.m_powerAvg = checked; + applySettings(); + ui->powerChartAvgWidgets->setVisible(checked); + arrangeRollups(); + if (checked) { + calcAverages(); + } +} + +void RadioAstronomyGUI::on_powerShowLegend_toggled(bool checked) +{ + m_settings.m_powerLegend = checked; + applySettings(); + if (m_powerChart) + { + if (checked) { + m_powerChart->legend()->show(); + } else { + m_powerChart->legend()->hide(); + } + } +} + +void RadioAstronomyGUI::on_powerShowTsys0_toggled(bool checked) +{ + m_settings.m_powerShowTsys0 = checked; + applySettings(); + if (m_powerTsys0Series) { + m_powerTsys0Series->setVisible(checked); + } +} + +void RadioAstronomyGUI::on_powerShowAirTemp_toggled(bool checked) +{ + m_settings.m_powerShowAirTemp = checked; + applySettings(); + m_airTemps.clicked(checked); +} + +void RadioAstronomyGUI::on_powerShowSensor1_toggled(bool checked) +{ + m_settings.m_sensorVisible[0] = checked; + applySettings(); + m_sensors[0].clicked(checked); +} + +void RadioAstronomyGUI::on_powerShowSensor2_toggled(bool checked) +{ + m_settings.m_sensorVisible[1] = checked; + applySettings(); + m_sensors[1].clicked(checked); +} + +void RadioAstronomyGUI::updatePowerMarkerTableVisibility() +{ + ui->powerMarkerTableWidgets->setVisible(m_settings.m_powerPeaks || m_settings.m_powerMarkers); + if (m_settings.m_powerPeaks) + { + ui->powerMarkerTable->showRow(POWER_MARKER_ROW_PEAK_MAX); + ui->powerMarkerTable->showRow(POWER_MARKER_ROW_PEAK_MIN); + } + else + { + ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_PEAK_MAX); + ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_PEAK_MIN); + } + if (m_settings.m_powerMarkers) + { + ui->powerMarkerTable->showRow(POWER_MARKER_ROW_M1); + ui->powerMarkerTable->showRow(POWER_MARKER_ROW_M2); + } + else + { + ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_M1); + ui->powerMarkerTable->hideRow(POWER_MARKER_ROW_M2); + } + ui->powerMarkerTableWidgets->updateGeometry(); // Without this, widgets aren't resized properly +} + +void RadioAstronomyGUI::updateSpectrumMarkerTableVisibility() +{ + bool fft = ui->spectrumChartSelect->currentIndex() == 0; + ui->spectrumMarkerTableWidgets->setVisible(fft && (m_settings.m_spectrumPeaks || m_settings.m_spectrumMarkers)); + if (m_settings.m_spectrumPeaks) + { + ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_PEAK); + } + else + { + ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_PEAK); + } + if (m_settings.m_spectrumMarkers) + { + ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_M1); + ui->spectrumMarkerTable->showRow(SPECTRUM_MARKER_ROW_M2); + } + else + { + ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_M1); + ui->spectrumMarkerTable->hideRow(SPECTRUM_MARKER_ROW_M2); + } + ui->spectrumMarkerTableWidgets->updateGeometry(); // Without this, widgets aren't resized properly +} + +void RadioAstronomyGUI::on_spectrumMarker_toggled(bool checked) +{ + m_settings.m_spectrumMarkers = checked; + applySettings(); + updateSpectrumMarkerTableVisibility(); + m_fftMarkerSeries->setVisible(checked); + if (checked) + { + m_fftChart->legend()->markers(m_fftMarkerSeries)[0]->setVisible(false); + showLoSMarker("M1"); + showLoSMarker("M2"); + } + else + { + clearLoSMarker("M1"); + clearLoSMarker("M2"); + } + updateSpectrumSelect(); + arrangeRollups(); +} + +void RadioAstronomyGUI::on_spectrumTemp_toggled(bool checked) +{ + m_settings.m_spectrumTemp = checked; + applySettings(); + ui->spectrumGaussianWidgets->setVisible(checked); + m_fftGaussianSeries->setVisible(checked); + updateSpectrumSelect(); + arrangeRollups(); +} + +void RadioAstronomyGUI::on_spectrumShowLegend_toggled(bool checked) +{ + m_settings.m_spectrumLegend = checked; + applySettings(); + if (m_fftChart) + { + m_fftChart->legend()->setVisible(checked); + m_calChart->legend()->setVisible(checked); + } +} + +void RadioAstronomyGUI::on_spectrumShowRefLine_toggled(bool checked) +{ + m_settings.m_spectrumRefLine = checked; + applySettings(); + ui->spectrumRefLineWidgets->setVisible(checked); + if (m_fftHlineSeries) + { + m_fftHlineSeries->setVisible(m_settings.m_spectrumRefLine); + m_fftDopplerAxis->setVisible(m_settings.m_spectrumRefLine); + } + updateDistanceColumns(); + arrangeRollups(); +} + +void RadioAstronomyGUI::on_spectrumShowLAB_toggled(bool checked) +{ + m_settings.m_spectrumLAB = checked; + applySettings(); + m_fftLABSeries->setVisible(m_settings.m_spectrumLAB); + if (m_settings.m_spectrumLAB) { + plotLAB(); // Replot incase data needs to be downloaded + } + spectrumAutoscale(); +} + +void RadioAstronomyGUI::updateDistanceColumns() +{ + if (m_settings.m_spectrumDistance && m_settings.m_spectrumRefLine) + { + ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_R); + ui->spectrumMarkerTable->showColumn(SPECTRUM_MARKER_COL_D); + showLoSMarker("Max"); + showLoSMarker("M1"); + showLoSMarker("M2"); + ui->sunDistanceToGCLine->setVisible(true); + ui->sunDistanceToGCLabel->setVisible(true); + ui->sunDistanceToGC->setVisible(true); + ui->sunDistanceToGCUnits->setVisible(true); + ui->sunOrbitalVelocityLine->setVisible(true); + ui->sunOrbitalVelocityLabel->setVisible(true); + ui->sunOrbitalVelocity->setVisible(true); + ui->sunOrbitalVelocityUnits->setVisible(true); + } + else + { + ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_R); + ui->spectrumMarkerTable->hideColumn(SPECTRUM_MARKER_COL_D); + clearLoSMarker("Max"); + clearLoSMarker("M1"); + clearLoSMarker("M2"); + ui->sunDistanceToGCLine->setVisible(false); + ui->sunDistanceToGCLabel->setVisible(false); + ui->sunDistanceToGC->setVisible(false); + ui->sunDistanceToGCUnits->setVisible(false); + ui->sunOrbitalVelocityLine->setVisible(false); + ui->sunOrbitalVelocityLabel->setVisible(false); + ui->sunOrbitalVelocity->setVisible(false); + ui->sunOrbitalVelocityUnits->setVisible(false); + } +} + +void RadioAstronomyGUI::on_spectrumShowDistance_toggled(bool checked) +{ + m_settings.m_spectrumDistance = checked; + applySettings(); + if (m_settings.m_spectrumDistance && !m_settings.m_spectrumRefLine) { + ui->spectrumShowRefLine->setChecked(true); + } + updateDistanceColumns(); +} + +// point isn't necessarily a point in the series - may be interpolated +void RadioAstronomyGUI::powerSeries_clicked(const QPointF &point) +{ + QString selection = ui->powerSelect->currentText(); + if (selection.startsWith("M")) + { + if (selection == "M1") + { + // Place marker 1 + m_powerM1X = point.x(); + m_powerM1Y = point.y(); + if (m_powerM1Valid) { + m_powerMarkerSeries->replace(0, m_powerM1X, m_powerM1Y); + } else { + m_powerMarkerSeries->insert(0, QPointF(m_powerM1X, m_powerM1Y)); + } + m_powerM1Valid = true; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerM1X); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M1, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM1Y); + calcPowerMarkerDelta(); + } + else if (selection == "M2") + { + // Place marker 2 + m_powerM2X = point.x(); + m_powerM2Y = point.y(); + if (m_powerM2Valid) { + m_powerMarkerSeries->replace(1, m_powerM2X, m_powerM2Y); + } else { + m_powerMarkerSeries->insert(1, QPointF(m_powerM2X, m_powerM2Y)); + } + m_powerM2Valid = true; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(m_powerM2X); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_DATE)->setData(Qt::DisplayRole, dt.date()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_TIME)->setData(Qt::DisplayRole, dt.time()); + ui->powerMarkerTable->item(POWER_MARKER_ROW_M2, POWER_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_powerM2Y); + calcPowerMarkerDelta(); + } + } + else if (selection == "Gaussian") + { + // Fit a Gaussian assuming point clicked is the peak + ui->powerGaussianCenter->setDateTime(QDateTime::fromMSecsSinceEpoch(point.x())); + // Calculate noise floor - take average of lowest 10% + qreal floor = calcSeriesFloor(m_powerSeries); + ui->powerGaussianFloor->setValue(floor); + // Set amplitude to achieve selected point + ui->powerGaussianAmp->setValue(point.y() - floor); + } + else + { + if (m_fftMeasurements.size() > 1) + { + // Select row closest to clicked data + QDateTime dt = QDateTime::fromMSecsSinceEpoch(point.x()); + int i = 0; + while ((i < m_fftMeasurements.size()) && (dt > m_fftMeasurements[i]->m_dateTime)) { + i++; + } + if (i < m_fftMeasurements.size()) { + ui->spectrumIndex->setValue(i); + } + } + } +} + +qreal RadioAstronomyGUI::calcSeriesFloor(QXYSeries *series, int percent) +{ + QList minValues; + double count = series->count() * percent / 100.0; + for (int i = 0; i < series->count(); i++) + { + qreal y = series->at(i).y(); + if (minValues.size() < count) + { + minValues.append(y); + std::sort(minValues.begin(), minValues.end()); + } + else if (y < minValues.last()) + { + minValues.append(y); + std::sort(minValues.begin(), minValues.end()); + } + } + qreal sum = std::accumulate(minValues.begin(), minValues.end(), 0.0); + return sum / minValues.size(); +} + +void RadioAstronomyGUI::updateSpectrumSelect() +{ + ui->spectrumSelect->clear(); + if (m_settings.m_spectrumMarkers) + { + ui->spectrumSelect->addItem("M1"); + ui->spectrumSelect->addItem("M2"); + } + if (m_settings.m_spectrumTemp) + { + ui->spectrumSelect->addItem("Gaussian"); + } + bool visible = ui->spectrumSelect->count() != 0; + ui->spectrumSelectLabel->setVisible(visible); + ui->spectrumSelect->setVisible(visible); +} + +void RadioAstronomyGUI::updatePowerSelect() +{ + ui->powerSelect->clear(); + if (m_settings.m_powerMarkers || m_settings.m_powerShowGaussian) { + ui->powerSelect->addItem("Row"); + } + if (m_settings.m_powerMarkers) + { + ui->powerSelect->addItem("M1"); + ui->powerSelect->addItem("M2"); + } + if (m_settings.m_powerShowGaussian) { + ui->powerSelect->addItem("Gaussian"); + } + bool visible = ui->powerSelect->count() != 0; + ui->powerSelectLabel->setVisible(visible); + ui->powerSelect->setVisible(visible); +} + +void RadioAstronomyGUI::spectrumSeries_clicked(const QPointF &point) +{ + QString selection = ui->spectrumSelect->currentText(); + if (selection.startsWith("M")) + { + FFTMeasurement *fft = currentFFT(); + if (selection == "M1") + { + m_spectrumM1X = point.x(); + m_spectrumM1Y = point.y(); + m_spectrumM1Valid = true; + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, m_spectrumM1X); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M1, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM1Y); + calcVrAndDistanceToPeak(m_spectrumM1X*1e6, fft, SPECTRUM_MARKER_ROW_M1); + } + else if (selection == "M2") + { + m_spectrumM2X = point.x(); + m_spectrumM2Y = point.y(); + m_spectrumM2Valid = true; + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_FREQ)->setData(Qt::DisplayRole, m_spectrumM2X); + ui->spectrumMarkerTable->item(SPECTRUM_MARKER_ROW_M2, SPECTRUM_MARKER_COL_VALUE)->setData(Qt::DisplayRole, m_spectrumM2Y); + calcVrAndDistanceToPeak(m_spectrumM2X*1e6, fft, SPECTRUM_MARKER_ROW_M2); + } + calcSpectrumMarkerDelta(); + + m_fftMarkerSeries->clear(); + if (m_spectrumM1Valid) { + m_fftMarkerSeries->append(m_spectrumM1X, m_spectrumM1Y); + } + if (m_spectrumM2Valid) { + m_fftMarkerSeries->append(m_spectrumM2X, m_spectrumM2Y); + } + } + else if (selection == "Gaussian") + { + ui->spectrumGaussianFreq->setValue(point.x()); + // Calculate noise floor - take average of lowest 10% + qreal floor = calcSeriesFloor(m_fftSeries); + ui->spectrumGaussianFloor->setValue(floor); + // Set amplitude to achieve selected point + ui->spectrumGaussianAmp->setValue(point.y() - floor); + plotFFTMeasurement(); + } +} + +void RadioAstronomyGUI::on_spectrumGaussianFreq_valueChanged(double value) +{ + (void) value; + calcFWHM(); + plotFFTMeasurement(); +} + +void RadioAstronomyGUI::on_spectrumGaussianAmp_valueChanged(double value) +{ + (void) value; + plotFFTMeasurement(); + calcColumnDensity(); +} + +void RadioAstronomyGUI::on_spectrumGaussianFloor_valueChanged(double value) +{ + (void) value; + plotFFTMeasurement(); +} + +void RadioAstronomyGUI::on_spectrumGaussianFWHM_valueChanged(double fFWHM) +{ + double c = Astronomy::m_speedOfLight; + double k = Astronomy::m_boltzmann; + double m = Astronomy::m_hydrogenMass; + double f0 = ui->spectrumGaussianFreq->value() * 1e6; + double vTurb = ui->spectrumGaussianTurb->value() * 1e3; // RSM turbulent velocties - Convert to m/s + const double bf = 2.0*sqrt(M_LN2); // Convert from Doppler parameter to FWHM + + double fr = fFWHM * c / (bf*f0); + double frs = fr*fr; + + double T = m * (frs-vTurb*vTurb) / (2.0*k); + + ui->spectrumTemperature->blockSignals(true); + ui->spectrumTemperature->setValue(T); + ui->spectrumTemperature->blockSignals(false); + plotFFTMeasurement(); + calcColumnDensity(); +} + +void RadioAstronomyGUI::on_spectrumGaussianTurb_valueChanged(double value) +{ + (void) value; + calcFWHM(); + plotFFTMeasurement(); +} + +void RadioAstronomyGUI::on_spectrumTemperature_valueChanged(double value) +{ + (void) value; + calcFWHM(); + plotFFTMeasurement(); +} + +void RadioAstronomyGUI::calcFWHM() +{ + double c = Astronomy::m_speedOfLight; + double k = Astronomy::m_boltzmann; + double m = Astronomy::m_hydrogenMass; + double f0 = ui->spectrumGaussianFreq->value() * 1e6; + double vTurb = ui->spectrumGaussianTurb->value() * 1e3; // RSM turbulent velocties - Convert to m/s + double T = ui->spectrumTemperature->value(); + const double bf = 2.0*sqrt(M_LN2); // Convert from Doppler parameter to FWHM + + double fFWHM = bf * f0/c * sqrt((2*k*T)/m + vTurb * vTurb); + + ui->spectrumGaussianFWHM->blockSignals(true); + ui->spectrumGaussianFWHM->setValue(fFWHM); + ui->spectrumGaussianFWHM->blockSignals(false); + calcColumnDensity(); +} + +// Assumes optically thin +void RadioAstronomyGUI::calcColumnDensity() +{ + double f0 = ui->spectrumLineFrequency->value() * 1e6; + double f = f0 + ui->spectrumGaussianFWHM->value() / 2.0; + double v = lineDopplerVelocity(f0, f) * 2.0; + double a = ui->spectrumGaussianAmp->value(); + double integratedIntensity = v * a; + double columnDensity = 1.81e18 * integratedIntensity; + ui->columnDensity->setText(QString::number(columnDensity, 'g', 2)); +} + +void RadioAstronomyGUI::on_powerShowGaussian_clicked(bool checked) +{ + m_settings.m_powerShowGaussian = checked; + applySettings(); + ui->powerGaussianWidgets->setVisible(checked); + m_powerGaussianSeries->setVisible(checked); + updatePowerSelect(); + arrangeRollups(); +} + +void RadioAstronomyGUI::plotPowerGaussian() +{ + m_powerGaussianSeries->clear(); + double dt0 = ui->powerGaussianCenter->dateTime().toMSecsSinceEpoch(); + double a = ui->powerGaussianAmp->value(); + double floor = ui->powerGaussianFloor->value(); + double fwhm = ui->powerGaussianFWHM->value() * 1000; // Convert from s to ms + double fwhm_sq = fwhm*fwhm; + qint64 dt = m_powerXAxis->min().toMSecsSinceEpoch(); + qint64 end = m_powerXAxis->max().toMSecsSinceEpoch(); + int steps = 256; + qint64 step = (end - dt) / steps; + for (int i = 0; i < steps; i++) + { + double fd = dt - dt0; + double g = a * std::exp(-4.0*M_LN2*fd*fd/fwhm_sq) + floor; + m_powerGaussianSeries->append(dt, g); + dt += step; + } +} + +// Calculate antenna HPBW from Sun's FWHM time +void RadioAstronomyGUI::calcHPBWFromFWHM() +{ + double fwhmSeconds = ui->powerGaussianFWHM->value(); + double sunDegPerSecond = 360.0/(24.0*60.0*60.0); + double hpbwDeg = fwhmSeconds * sunDegPerSecond; + ui->powerGaussianHPBW->setValue(hpbwDeg); +} + +// Calculate Sun's FWHM time for anntena HPBW +void RadioAstronomyGUI::calcFHWMFromHPBW() +{ + double hpwmDeg = ui->powerGaussianHPBW->value(); + double sunDegPerSecond = 360.0/(24.0*60.0*60.0); + double fwhmSeconds = hpwmDeg / sunDegPerSecond; + ui->powerGaussianFWHM->setValue(fwhmSeconds); +} + +void RadioAstronomyGUI::on_powerGaussianCenter_dateTimeChanged(QDateTime dateTime) +{ + (void) dateTime; + plotPowerGaussian(); +} + +void RadioAstronomyGUI::on_powerGaussianAmp_valueChanged(double value) +{ + (void) value; + plotPowerGaussian(); +} + +void RadioAstronomyGUI::on_powerGaussianFloor_valueChanged(double value) +{ + (void) value; + plotPowerGaussian(); +} + +void RadioAstronomyGUI::on_powerGaussianFWHM_valueChanged(double value) +{ + (void) value; + plotPowerGaussian(); + ui->powerGaussianHPBW->blockSignals(true); + calcHPBWFromFWHM(); + ui->powerGaussianHPBW->blockSignals(false); +} + +void RadioAstronomyGUI::on_powerGaussianHPBW_valueChanged(double value) +{ + (void) value; + calcFHWMFromHPBW(); + ui->powerGaussianFWHM->blockSignals(true); + plotPowerGaussian(); + ui->powerGaussianFWHM->blockSignals(false); +} + +RadioAstronomyGUI::LABData* RadioAstronomyGUI::parseLAB(QFile* file, float l, float b) +{ + LABData *data = new LABData(); + data->read(file, l, b); + m_dataLAB.append(data); + return data; +} + +void RadioAstronomyGUI::plotLAB() +{ + int index = ui->spectrumIndex->value(); + if (index < m_fftMeasurements.size()) + { + FFTMeasurement *fft = m_fftMeasurements[index]; + plotLAB(fft->m_l, fft->m_b, m_beamWidth); + } +} + +void RadioAstronomyGUI::plotLAB(float l, float b, float beamWidth) +{ + // Assume a beamwidth >1deg + l = round(l); + b = round(b); + + // Check if we already have the data in memory + LABData* data = nullptr; + for (int i = 0; i < m_dataLAB.size(); i++) + { + if ((m_dataLAB[i]->m_l == l) && (m_dataLAB[i]->m_b == b)) + { + data = m_dataLAB[i]; + break; + } + } + + if (!data) + { + // Try to open previously downloaded data + m_filenameLAB = HttpDownloadManager::downloadDir() + "/" + QString("lab_l_%1_b_%2.txt").arg(l).arg(b); + QFile file(m_filenameLAB); + if (file.open(QIODevice::ReadOnly)) + { + qDebug() << "RadioAstronomyGUI::plotLAB: Using cached file: " << m_filenameLAB; + data = parseLAB(&file, l, b); + } + else + { + // Only download one file at a time, so we don't overload the server + if (!m_downloadingLAB) + { + m_downloadingLAB = true; + m_lLAB = l; + m_bLAB = b; + + // Request data be generated via web server + QNetworkRequest request(QUrl("https://www.astro.uni-bonn.de/hisurvey/euhou/LABprofile/index.php")); + request.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); + + QUrlQuery params; + params.addQueryItem("coordinates", "lb"); + params.addQueryItem("ral", QString::number(l)); + params.addQueryItem("decb", QString::number(b)); + params.addQueryItem("beam", QString::number(beamWidth)); + params.addQueryItem("vmin", "-100.0" ); + params.addQueryItem("vmax", "100.0" ); + params.addQueryItem("search", "Search data" ); + + m_networkManager->post(request, params.query(QUrl::FullyEncoded).toUtf8()); + } + } + } + + if (data) + { + data->toSeries(m_fftLABSeries); + spectrumAutoscale(); + } +} + +void RadioAstronomyGUI::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "RadioAstronomyGUI::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + m_downloadingLAB = false; + } + else + { + QString answer = reply->readAll(); + QRegExp re("a href=\\\"download.php([^\"]*)\""); + if (re.indexIn(answer) != -1) + { + QString filename = re.capturedTexts()[1]; + qDebug() << "RadioAstronomyGUI: Downloading LAB reference data: " << filename; + m_dlm.download(QUrl("https://www.astro.uni-bonn.de/hisurvey/euhou/LABprofile/download.php" + filename), m_filenameLAB); + } + else + { + qDebug() << "RadioAstronomyGUI::networkManagerFinished - No filename found: " << answer; + m_downloadingLAB = false; + } + } + + reply->deleteLater(); +} + +void RadioAstronomyGUI::downloadFinished(const QString& filename, bool success) +{ + if (success) + { + QFile file(filename); + if (file.open(QIODevice::ReadOnly)) + { + LABData *data = parseLAB(&file, m_lLAB, m_bLAB); + // Check if the data we've downloaded is for the current FFT being displayed + int index = ui->spectrumIndex->value(); + if (index < m_fftMeasurements.size()) + { + FFTMeasurement *fft = m_fftMeasurements[index]; + if (m_lLAB == fft->m_l && m_bLAB == fft->m_b) + { + data->toSeries(m_fftLABSeries); + spectrumAutoscale(); + } + else + { + // Try ploting for current FFT (as we only allow one download at a time, so may have been skipped) + m_downloadingLAB = false; + plotLAB(fft->m_l, fft->m_b, m_beamWidth); + } + } + } else { + qDebug() << "RadioAstronomyGUI::downloadFinished: Failed to open downloaded file: " << filename; + } + } + m_downloadingLAB = false; +} + +void RadioAstronomyGUI::displayRunModeSettings() +{ + bool sweep = m_settings.m_runMode == RadioAstronomySettings::SWEEP; + ui->sweep1CoordLabel->setVisible(sweep); + ui->sweepType->setVisible(sweep); + ui->sweep1StartLabel->setVisible(sweep); + ui->sweep1Start->setVisible(sweep); + ui->sweep1StopLabel->setVisible(sweep); + ui->sweep1Stop->setVisible(sweep); + ui->sweep1StepLabel->setVisible(sweep); + ui->sweep1Step->setVisible(sweep); + ui->sweep1DelayLabel->setVisible(sweep); + ui->sweep1Delay->setVisible(sweep); + ui->sweep2CoordLabel->setVisible(sweep); + ui->sweep2StartLabel->setVisible(sweep); + ui->sweep2Start->setVisible(sweep); + ui->sweep2StopLabel->setVisible(sweep); + ui->sweep2Stop->setVisible(sweep); + ui->sweep2StepLabel->setVisible(sweep); + ui->sweep2Step->setVisible(sweep); + ui->sweep2DelayLabel->setVisible(sweep); + ui->sweep2Delay->setVisible(sweep); + ui->sweepStatus->setVisible(sweep); + ui->runLayout->activate(); // Needed otherwise height of rollup doesn't seem to be reduced + ui->statusLayout->activate(); // going from sweep to single/continuous + arrangeRollups(); +} + +void RadioAstronomyGUI::on_runMode_currentIndexChanged(int index) +{ + m_settings.m_runMode = (RadioAstronomySettings::RunMode)index; + applySettings(); + displayRunModeSettings(); +} + +void RadioAstronomyGUI::on_sweepType_currentIndexChanged(int index) +{ + m_settings.m_sweepType = (RadioAstronomySettings::SweepType)index; + if ((index == 0) || (index == 2)) + { + ui->sweep1CoordLabel->setText("Az"); + ui->sweep2CoordLabel->setText("El"); + } + else if (index == 1) + { + ui->sweep1CoordLabel->setText("l"); + ui->sweep2CoordLabel->setText("b"); + } +} + +void RadioAstronomyGUI::on_sweep1Start_valueChanged(double value) +{ + m_settings.m_sweep1Start = ui->sweep1Start->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep1Stop_valueChanged(double value) +{ + m_settings.m_sweep1Stop = ui->sweep1Stop->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep1Step_valueChanged(double value) +{ + m_settings.m_sweep1Step = ui->sweep1Step->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep1Delay_valueChanged(double value) +{ + m_settings.m_sweep1Delay = ui->sweep1Delay->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep2Start_valueChanged(double value) +{ + m_settings.m_sweep2Start = ui->sweep2Start->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep2Stop_valueChanged(double value) +{ + m_settings.m_sweep2Stop = ui->sweep2Stop->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep2Step_valueChanged(double value) +{ + m_settings.m_sweep2Step = ui->sweep2Step->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweep2Delay_valueChanged(double value) +{ + m_settings.m_sweep2Delay = ui->sweep2Delay->value(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweepStartAtTime_currentIndexChanged(int index) +{ + m_settings.m_sweepStartAtTime = ui->sweepStartAtTime->currentIndex() == 1; + ui->sweepStartDateTime->setVisible(index == 1); + arrangeRollups(); + applySettings(); +} + +void RadioAstronomyGUI::on_sweepStartDateTime_dateTimeChanged(const QDateTime& dateTime) +{ + m_settings.m_sweepStartDateTime = dateTime; + applySettings(); +} + +void RadioAstronomyGUI::on_startStop_clicked(bool checked) +{ + if (checked) + { + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + applySettings(); + if (m_settings.m_power2DLinkSweep) + { + update2DSettingsFromSweep(); + create2DImage(); + } + m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStartSweep::create()); + } + else + { + m_radioAstronomy->getInputMessageQueue()->push(RadioAstronomy::MsgStopSweep::create()); + if (m_settings.m_runMode != RadioAstronomySettings::SWEEP) { + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + } + } +} + +void RadioAstronomyGUI::calcPowerChartTickCount(int width) +{ + // These values should probably be dependent on the font used + if (m_powerXAxis) { + if (m_powerXAxisSameDay) { + m_powerXAxis->setTickCount(width > 700 ? 10 : 5); + } else { + m_powerXAxis->setTickCount(width > 1200 ? 10 : 5); + } + } +} + +void RadioAstronomyGUI::calcSpectrumChartTickCount(QValueAxis *axis, int width) +{ + if (axis) { + axis->setTickCount(width > 700 ? 10 : 5); + } +} + +void RadioAstronomyGUI::resizeEvent(QResizeEvent* size) +{ + int width = size->size().width(); + calcPowerChartTickCount(width); + calcSpectrumChartTickCount(m_fftXAxis, width); + calcSpectrumChartTickCount(m_fftDopplerAxis, width); + calcSpectrumChartTickCount(m_calXAxis, width); + ChannelGUI::resizeEvent(size); +} diff --git a/plugins/channelrx/radioastronomy/radioastronomygui.h b/plugins/channelrx/radioastronomy/radioastronomygui.h new file mode 100644 index 000000000..84ce11ed4 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomygui.h @@ -0,0 +1,672 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMYGUI_H +#define INCLUDE_RADIOASTRONOMYGUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "util/messagequeue.h" +#include "util/httpdownloadmanager.h" +#include "pipes/pipeendpoint.h" + +#include "radioastronomysettings.h" +#include "radioastronomy.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +class RadioAstronomy; +class RadioAstronomyGUI; + +namespace Ui { + class RadioAstronomyGUI; +} +class RadioAstronomyGUI; + +using namespace QtCharts; + +class RadioAstronomyGUI : public ChannelGUI { + Q_OBJECT + + struct FFTMeasurement { + QDateTime m_dateTime; + qint64 m_centerFrequency; + int m_sampleRate; + int m_integration; + int m_rfBandwidth; + + int m_fftSize; + Real* m_fftData; + Real* m_db; // dBFS + Real* m_snr; // SNR (noise is cold cal data) + Real* m_temp; // Temp in Kelvin base on hot/cold cal data + Real m_totalPower; // Total power based on sum of fftData (i.e unknown units) + Real m_totalPowerdBFS; // m_totalPower in dB + Real m_totalPowerdBm; // Total power in dBm + Real m_totalPowerWatts; // Total power in Watts + Real m_tSys; // Total temp in K + Real m_tSys0; // Total unwanted noise (E.g. Trx+Tgal..) in K + Real m_tSource; // Estimated source temp (tSys-tSys0) in K + Real m_flux; // Average spectral flux density of source in W m^-2 Hz^-1 + Real m_sigmaT; // Temperature variation + Real m_sigmaS; // Flux variation + Real m_tempMin; // Minimum value in m_temp array + RadioAstronomySettings::SpectrumBaseline m_baseline; + + float m_omegaA; + float m_omegaS; + + bool m_coordsValid; //!< Whether follow variables are valid + float m_ra; + float m_dec; + float m_azimuth; + float m_elevation; + float m_l; + float m_b; + float m_vBCRS; + float m_vLSR; + float m_solarFlux; + float m_airTemp; + float m_skyTemp; + float m_sensor[RADIOASTRONOMY_SENSORS]; + + int m_sweepIndex; + + FFTMeasurement() : + m_fftSize(0), + m_fftData(nullptr), + m_db(nullptr), + m_snr(nullptr), + m_temp(nullptr), + m_totalPower(0.0f), + m_totalPowerdBFS(0.0f), + m_totalPowerdBm(0.0f), + m_totalPowerWatts(0.0f), + m_tSys(0.0f), + m_tSys0(0.0f), + m_tSource(0.0f), + m_flux(0.0f), + m_sigmaT(0.0f), + m_sigmaS(0.0f), + m_tempMin(0.0f), + m_baseline(RadioAstronomySettings::SBL_TSYS0), + m_coordsValid(false), + m_airTemp(0.0), + m_skyTemp(0.0), + m_sweepIndex(0) + { + } + + ~FFTMeasurement() + { + delete[] m_fftData; + delete[] m_db; + delete[] m_snr; + delete[] m_temp; + } + }; + + struct LABData { + + float m_l; + float m_b; + QList m_vlsr; + QList m_temp; + + LABData() : + m_l(0.0f), + m_b(0.0f) + { + } + + void read(QFile* file, float l, float b); + void toSeries(QLineSeries *series); + }; + + struct SensorMeasurement { + + QDateTime m_dateTime; + double m_value; + + SensorMeasurement(QDateTime dateTime, double value) : + m_dateTime(dateTime), + m_value(value) + { + } + }; + + class SensorMeasurements { + + QLineSeries* m_series; + QValueAxis* m_yAxis; + double m_max; + double m_min; + QList m_measurements; + + public: + + SensorMeasurements() : + m_series(nullptr), + m_yAxis(nullptr) + { + } + + void init(const QString& name, bool visible); + void setName(const QString& name); + void clicked(bool checked); + void append(SensorMeasurement *measurement); + void addToSeries(SensorMeasurement *measurement); + void addAllToSeries(); + void clear(); + void addToChart(QChart* chart, QDateTimeAxis* xAxis); + void setPen(const QPen& pen); + QValueAxis* yAxis() const; + qreal lastValue(); + }; + +public: + static RadioAstronomyGUI* 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::RadioAstronomyGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + RadioAstronomySettings m_settings; + bool m_doApplySettings; + QList m_availablePipes; + + int m_basebandSampleRate; + quint64 m_centerFrequency; + + RadioAstronomy* m_radioAstronomy; + uint32_t m_tickCount; + MessageQueue m_inputMessageQueue; + + QMenu *powerTableMenu; // Column select context menu + QMenu *copyMenu; + + QChart *m_powerChart; + QLineSeries *m_powerSeries; + QDateTimeAxis *m_powerXAxis; + bool m_powerXAxisSameDay; + QValueAxis *m_powerYAxis; + QScatterSeries *m_powerPeakSeries; + QScatterSeries *m_powerMarkerSeries; + QLineSeries *m_powerTsys0Series; + QLineSeries *m_powerGaussianSeries; + double m_powerMin; // For axis autoscale + double m_powerMax; + bool m_powerPeakValid; + qreal m_powerMinX; // For min peak + qreal m_powerMinY; + qreal m_powerMaxX; // For max peak + qreal m_powerMaxY; + + QChart *m_2DChart; + QValueAxis *m_2DXAxis; + QValueAxis *m_2DYAxis; + float *m_2DMapIntensity; + float m_2DMapMax; + float m_2DMapMin; + QImage m_2DMap; + int m_sweepIndex; + + SensorMeasurements m_airTemps; + SensorMeasurements m_sensors[RADIOASTRONOMY_SENSORS]; + + QChart *m_calChart; + QValueAxis *m_calXAxis; + QValueAxis *m_calYAxis; + + QLineSeries *m_calHotSeries; + QLineSeries *m_calColdSeries; + FFTMeasurement* m_calHot; + FFTMeasurement* m_calCold; + double *m_calG; + + QChart *m_fftChart; + QLineSeries *m_fftSeries; + QLineSeries *m_fftHlineSeries; + QScatterSeries *m_fftPeakSeries; + QScatterSeries *m_fftMarkerSeries; + QLineSeries *m_fftGaussianSeries; + QLineSeries *m_fftLABSeries; + QValueAxis *m_fftXAxis; + QValueAxis *m_fftYAxis; + QValueAxis *m_fftDopplerAxis; + QList m_fftMeasurements; + + // Markers + bool m_powerM1Valid; + bool m_powerM2Valid; + qreal m_powerM1X; + qreal m_powerM1Y; + qreal m_powerM2X; + qreal m_powerM2Y; + bool m_spectrumM1Valid; + bool m_spectrumM2Valid; + qreal m_spectrumM1X; + qreal m_spectrumM1Y; + qreal m_spectrumM2X; + qreal m_spectrumM2Y; + + // Target received from Star Tracker + bool m_coordsValid; + float m_ra; + float m_dec; + float m_azimuth; + float m_elevation; + float m_l; + float m_b; + float m_vBCRS; + float m_vLSR; + float m_solarFlux; + float m_skyTemp; + float m_beamWidth; + + float m_lLAB; // Galactic coords for current LAB data + float m_bLAB; + QString m_filenameLAB; + bool m_downloadingLAB; + QList m_dataLAB; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + HttpDownloadManager m_dlm; + + explicit RadioAstronomyGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~RadioAstronomyGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void displayStreamIndex(); + void displaySpectrumLineFrequency(); + void displayRunModeSettings(); + void updatePipeList(); + void updateRotatorList(); + bool handleMessage(const Message& message); + double degreesToSteradian(double deg) const; + double hpbwToSteradians(double hpbw) const; + double calcOmegaA() const; + double calcOmegaS() const; + double beamFillingFactor() const; + void updateOmegaA(); + void powerMeasurementReceived(FFTMeasurement *fft, bool skipCalcs); + void calCompletetReceived(const RadioAstronomy::MsgCalComplete& measurement); + void calcFFTPower(FFTMeasurement* fft); + void calcFFTTotalPower(FFTMeasurement* fft); + void calcFFTTemperatures(FFTMeasurement* fft); + void calcFFTTotalTemperature(FFTMeasurement* fft); + void calcFFTMinTemperature(FFTMeasurement* fft); + void addFFT(FFTMeasurement *fft, bool skipCalcs=false); + void fftMeasurementReceived(const RadioAstronomy::MsgFFTMeasurement& measurement); + void addToPowerSeries(FFTMeasurement *fft, bool skipCalcs=false); + void plotPowerGaussian(); + void plotSpectrum(); + void plotCalSpectrum(); + void plotCalMeasurements(); + void plotFFTMeasurement(int index); + void plotFFTMeasurement(); + void plotTempGaussian(double startFreq, double freqStep, int steps); + void plotRefLine(FFTMeasurement *fft); + void plotLAB(); + void plotLAB(float l, float b, float beamWidth); + LABData* parseLAB(QFile* file, float l, float b); + int fftSizeToIndex(int size); + double dopplerToVelocity(double centre, double f, FFTMeasurement *fft); + QHash csvHeadersToHash(QStringList cols); + QString csvData(QHash hash, QStringList cols, QString col); + bool hasNeededFFTData(QHash hash); + void saveFFT(QTextStream& out, const FFTMeasurement* fft); + FFTMeasurement* loadFFT(QHash hash, QStringList cols); + void clearData(); + void clearCalData(); + bool deleteRow(int row); + void deleteRowsComplete(bool deletedCurrent, int next); + void calcCalibrationScaleFactors(); + void calibrate(); + void recalibrate(); + void calcGalacticBackgroundTemp(); + void calcAtmosphericTemp(); + void calcCalAvgDiff(); + void calcCalTrx(); + void calcCalTsp(); + void calcAverages(); + void calcFWHM(); + void calcHPBWFromFWHM(); + void calcFHWMFromHPBW(); + void calcColumnDensity(); + qreal calcSeriesFloor(QXYSeries *series, int percent=10); + void calcVrAndDistanceToPeak(double freq, FFTMeasurement *fft, int row); + int calcDistanceToPeak(double vr, float l, float b, double& r, double &d1, double &d2); + void calcDistances(); + void clearLoSMarker(const QString& name); + void updateLoSMarker(const QString& name, float l, float b, float d); + bool losMarkerEnabled(const QString& name); + void showLoSMarker(const QString& name); + void showLoSMarker(int row); + void sensorMeasurementReceived(const RadioAstronomy::MsgSensorMeasurement& measurement); + void updateSpectrumMarkerTableVisibility(); + void updatePowerMarkerTableVisibility(); + void updatePowerChartWidgetsVisibility(); + void updateSpectrumChartWidgetsVisibility(); + void updateSpectrumSelect(); + void updatePowerSelect(); + void spectrumAutoscale(); + void powerAutoscale(); + void calcSpectrumMarkerDelta(); + void calcPowerMarkerDelta(); + void calcPowerPeakDelta(); + QRgb intensityToColor(float intensity); + void set2DAxisTitles(); + void update2DSettingsFromSweep(); + void create2DImage(); + void update2DImage(FFTMeasurement* fft, bool skipCalcs); + void recolour2DImage(); + void power2DAutoscale(); + void powerColourAutoscale(); + void updatePowerColourScaleStep(); + void updateSpectrumMinMax(qreal x, qreal y); + RadioAstronomyGUI::FFTMeasurement* currentFFT(); + void spectrumUpdateXRange(FFTMeasurement* fft = nullptr); + void spectrumUpdateYRange(FFTMeasurement* fft = nullptr); + void updateDistanceColumns(); + void updateBWLimits(); + void updateIntegrationTime(); + void updateTSys0(); + double calcTSys0() const; + double calcTau() const; + double calcTau(const FFTMeasurement* fft) const; + double calcSigmaT(double tSys) const; + double calcSigmaS(double tSys) const; + double calcSigmaT(const FFTMeasurement* fft) const; + double calcSigmaS(const FFTMeasurement* fft) const; + double calcFlux(double Ta, const FFTMeasurement *fft) const; + double calcTSource(FFTMeasurement *fft) const; + void updatePowerColumns(int row, FFTMeasurement* fft); + void calcPowerChartTickCount(int width); + void calcSpectrumChartTickCount(QValueAxis *axis, int width); + int powerYUnitsToIndex(RadioAstronomySettings::PowerYUnits units); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void resizePowerTable(); + void resizePowerMarkerTable(); + void resizeSpectrumMarkerTable(); + QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot); + + enum PowerTableCol { + POWER_COL_DATE, + POWER_COL_TIME, + POWER_COL_POWER, + POWER_COL_POWER_DB, + POWER_COL_POWER_DBM, + POWER_COL_TSYS, + POWER_COL_TSYS0, + POWER_COL_TSOURCE, + POWER_COL_TB, + POWER_COL_TSKY, + POWER_COL_FLUX, + POWER_COL_SIGMA_T, + POWER_COL_SIGMA_S, + POWER_COL_OMEGA_A, + POWER_COL_OMEGA_S, + POWER_COL_RA, + POWER_COL_DEC, + POWER_COL_GAL_LON, + POWER_COL_GAL_LAT, + POWER_COL_AZ, + POWER_COL_EL, + POWER_COL_VBCRS, + POWER_COL_VLSR, + POWER_COL_SOLAR_FLUX, + POWER_COL_AIR_TEMP, + POWER_COL_SENSOR_1, + POWER_COL_SENSOR_2 + }; + + enum PowerMarkerTable { + POWER_MARKER_COL_NAME, + POWER_MARKER_COL_DATE, + POWER_MARKER_COL_TIME, + POWER_MARKER_COL_VALUE, + POWER_MARKER_COL_DELTA_X, + POWER_MARKER_COL_DELTA_Y, + POWER_MARKER_COL_DELTA_TO + }; + + enum PowerMarkerRow { + POWER_MARKER_ROW_PEAK_MAX, + POWER_MARKER_ROW_PEAK_MIN, + POWER_MARKER_ROW_M1, + POWER_MARKER_ROW_M2, + POWER_MARKER_ROWS + }; + + enum SpectrumMarkerTable { + SPECTRUM_MARKER_COL_NAME, + SPECTRUM_MARKER_COL_FREQ, + SPECTRUM_MARKER_COL_VALUE, + SPECTRUM_MARKER_COL_DELTA_X, + SPECTRUM_MARKER_COL_DELTA_Y, + SPECTRUM_MARKER_COL_DELTA_TO, + SPECTRUM_MARKER_COL_VR, + SPECTRUM_MARKER_COL_R, + SPECTRUM_MARKER_COL_D, + SPECTRUM_MARKER_COL_PLOT_MAX, + SPECTRUM_MARKER_COL_R_MIN, + SPECTRUM_MARKER_COL_V + }; + + enum SpecrumMarkerRow { + SPECTRUM_MARKER_ROW_PEAK, + SPECTRUM_MARKER_ROW_M1, + SPECTRUM_MARKER_ROW_M2, + SPECTRUM_MARKER_ROWS + }; + +protected: + void resizeEvent(QResizeEvent* size); + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_sampleRate_changed(qint64 index); + void on_rfBW_changed(qint64 index); + + void on_integration_changed(qint64 value); + void on_fftSize_currentIndexChanged(int index); + void on_fftWindow_currentIndexChanged(int index); + void on_filterFreqs_editingFinished(); + + void on_starTracker_currentTextChanged(const QString& text); + void on_rotator_currentTextChanged(const QString& text); + void on_showSensors_clicked(); + + void on_tempRXSelect_currentIndexChanged(int value); + void on_tempRX_valueChanged(double value); + void on_tempCMB_valueChanged(double value); + void on_tempGal_valueChanged(double value); + void on_tempSP_valueChanged(double value); + void on_tempAtm_valueChanged(double value); + void on_tempAir_valueChanged(double value); + void on_zenithOpacity_valueChanged(double value); + void on_elevation_valueChanged(double value); + void on_tempAtmLink_toggled(bool checked); + void on_tempAirLink_toggled(bool checked); + void on_tempGalLink_toggled(bool checked); + void on_elevationLink_toggled(bool checked); + + void on_gainVariation_valueChanged(double value); + void on_omegaAUnits_currentIndexChanged(int index); + void on_sourceType_currentIndexChanged(int index); + void on_omegaS_valueChanged(double value); + void on_omegaSUnits_currentIndexChanged(int index); + + void on_spectrumChartSelect_currentIndexChanged(int index); + void on_showCalSettings_clicked(); + void on_startCalHot_clicked(); + void on_startCalCold_clicked(); + void on_recalibrate_toggled(bool checked=false); + void on_spectrumShowLegend_toggled(bool checked); + void on_spectrumShowRefLine_toggled(bool checked); + void on_spectrumTemp_toggled(bool checked); + void on_spectrumMarker_toggled(bool checked); + void on_spectrumPeak_toggled(bool checked); + void on_spectrumReverseXAxis_toggled(bool checked); + void on_savePowerData_clicked(); + void on_savePowerChartImage_clicked(); + void on_saveSpectrumData_clicked(); + void on_loadSpectrumData_clicked(); + void on_saveSpectrumChartImage_clicked(); + void on_saveSpectrumChartImages_clicked(); + void on_clearData_clicked(); + void on_clearCal_clicked(); + + void spectrumSeries_clicked(const QPointF &point); + void on_spectrumAutoscale_toggled(bool checked=false); + void on_spectrumAutoscaleX_clicked(); + void on_spectrumAutoscaleY_clicked(); + void on_spectrumReference_valueChanged(double value); + void on_spectrumRange_valueChanged(double value); + void on_spectrumCenterFreq_valueChanged(double value); + void on_spectrumSpan_valueChanged(double value); + void on_spectrumYUnits_currentIndexChanged(int index); + void on_spectrumBaseline_currentIndexChanged(int index); + void on_spectrumIndex_valueChanged(int value); + + void on_spectrumLine_currentIndexChanged(int value); + void on_spectrumLineFrequency_valueChanged(double value); + void on_refFrame_currentIndexChanged(int value); + void on_sunDistanceToGC_valueChanged(double value); + void on_sunOrbitalVelocity_valueChanged(double value); + void spectrumMarkerTableItemChanged(QTableWidgetItem *item); + + void on_spectrumGaussianFreq_valueChanged(double value); + void on_spectrumGaussianAmp_valueChanged(double value); + void on_spectrumGaussianFloor_valueChanged(double value); + void on_spectrumGaussianFWHM_valueChanged(double value); + void on_spectrumGaussianTurb_valueChanged(double value); + void on_spectrumTemperature_valueChanged(double t); + void on_spectrumShowLAB_toggled(bool checked=false); + void on_spectrumShowDistance_toggled(bool checked=false); + + void on_tCalHotSelect_currentIndexChanged(int index); + void on_tCalHot_valueChanged(double value); + void on_tCalColdSelect_currentIndexChanged(int index); + void on_tCalCold_valueChanged(double value); + + void on_powerChartSelect_currentIndexChanged(int index); + void on_powerYUnits_currentIndexChanged(int index); + void on_powerShowMarker_toggled(bool checked); + void on_powerShowTsys0_toggled(bool checked); + void on_powerShowAirTemp_toggled(bool checked); + void on_powerShowSensor1_toggled(bool checked); + void on_powerShowSensor2_toggled(bool checked); + void on_powerShowPeak_toggled(bool checked); + void on_powerShowAvg_toggled(bool checked); + void on_powerShowLegend_toggled(bool checked); + void powerSeries_clicked(const QPointF &point); + + void on_powerAutoscale_toggled(bool checked); + void on_powerAutoscaleY_clicked(); + void on_powerAutoscaleX_clicked(); + void on_powerReference_valueChanged(double value); + void on_powerRange_valueChanged(double value); + void on_powerStartTime_dateTimeChanged(QDateTime value); + void on_powerEndTime_dateTimeChanged(QDateTime value); + + void on_powerShowGaussian_clicked(bool checked=false); + void on_powerGaussianCenter_dateTimeChanged(QDateTime dateTime); + void on_powerGaussianAmp_valueChanged(double value); + void on_powerGaussianFloor_valueChanged(double value); + void on_powerGaussianFWHM_valueChanged(double value); + void on_powerGaussianHPBW_valueChanged(double value); + + void on_runMode_currentIndexChanged(int index); + void on_sweepType_currentIndexChanged(int index); + void on_startStop_clicked(bool checked=false); + void on_sweepStartAtTime_currentIndexChanged(int index); + void on_sweepStartDateTime_dateTimeChanged(const QDateTime& dateTime); + void on_sweep1Start_valueChanged(double value); + void on_sweep1Stop_valueChanged(double value); + void on_sweep1Step_valueChanged(double value); + void on_sweep1Delay_valueChanged(double value); + void on_sweep2Start_valueChanged(double value); + void on_sweep2Stop_valueChanged(double value); + void on_sweep2Step_valueChanged(double value); + void on_sweep2Delay_valueChanged(double value); + + void on_power2DLinkSweep_toggled(bool checked=false); + void on_power2DAutoscale_clicked(); + void on_power2DSweepType_currentIndexChanged(int index); + void on_power2DWidth_valueChanged(int value); + void on_power2DHeight_valueChanged(int value); + void on_power2DXMin_valueChanged(double value); + void on_power2DXMax_valueChanged(double value); + void on_power2DYMin_valueChanged(double value); + void on_power2DYMax_valueChanged(double value); + + void on_powerColourAutoscale_toggled(bool checked=false); + void on_powerColourScaleMin_valueChanged(double value); + void on_powerColourScaleMax_valueChanged(double value); + void on_powerColourPalette_currentIndexChanged(int index); + + void powerTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void powerTable_sectionResized(int logicalIndex, int oldSize, int newSize); + void powerTableColumnSelectMenu(QPoint pos); + void powerTableColumnSelectMenuChecked(bool checked = false); + void on_powerTable_cellDoubleClicked(int row, int column); + + void plotPowerChart(); + void plotPowerVsTimeChart(); + void plot2DChart(); + void plotAreaChanged(const QRectF& plotArea); + void customContextMenuRequested(QPoint point); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void tick(); + void networkManagerFinished(QNetworkReply *reply); + void downloadFinished(const QString& filename, bool success); +}; + +#endif // INCLUDE_RADIOASTRONOMYGUI_H diff --git a/plugins/channelrx/radioastronomy/radioastronomygui.ui b/plugins/channelrx/radioastronomy/radioastronomygui.ui new file mode 100644 index 000000000..d065346f4 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomygui.ui @@ -0,0 +1,5197 @@ + + + RadioAstronomyGUI + + + + 0 + 0 + 740 + 1688 + + + + + 0 + 0 + + + + + 740 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + Radio Astronomy + + + + + + + + 0 + 0 + 731 + 241 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Shift frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + SR + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Sample rate in samples per second + + + + + + + Sa/s + + + + + + + Qt::Vertical + + + + + + + Δν + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + RF bandwidth in Hertz + + + + + + + Hz + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + + + Qt::Horizontal + + + + + + + + + FFT + + + + + + + Qt::Vertical + + + + + + + Σ + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + FFT integration count per measurement + + + + + + + Qt::Vertical + + + + + + + Channels + + + + + + + + 56 + 0 + + + + FFT size (number of bins / channels) + + + + 16 + + + + + 32 + + + + + 64 + + + + + 128 + + + + + 256 + + + + + 512 + + + + + 1k + + + + + 2k + + + + + 4k + + + + + + + + Qt::Vertical + + + + + + + Window + + + + + + + + 56 + 0 + + + + FFT windowing function + + + + None + + + + + Hann + + + + + + + + Qt::Vertical + + + + + + + Filter + + + + + + + List of channels to filter to remove RFI (FFT bin is set to minimum of other bins) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + t<sub>Σ</sub> + + + + + + + Time taken for a single measurement in seconds or minutes + + + s + + + + + + + + + Qt::Horizontal + + + + + + + + + Star Tracker + + + + + + + + 140 + 0 + + + + Star Tracker feature that sets the observation target + + + + + + + Qt::Vertical + + + + + + + Rotator + + + + + + + + 140 + 0 + + + + Rotator controller for the antenna used for measurement + + + + None + + + + + + + + Qt::Vertical + + + + + + + Sensors + + + + + + + Open sensor settings dialog + + + + + + + :/listing.png:/listing.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + Trx + + + + + NF + + + + + + + + Receiver noise temperature in Kelvin or noise figure in dB + +This is the combined noise of LNA, feed and SDR + + + 1 + + + 10000.000000000000000 + + + + + + + + 0 + 0 + + + + K + + + + + + + Qt::Vertical + + + + + + + T<sub>CMB</sub> + + + + + + + Cosmic microwave background (CMB) temperature in Kelvin + +This should be 2.73K when pointing at the sky + + + 300.000000000000000 + + + 2.730000000000000 + + + + + + + K + + + + + + + Qt::Vertical + + + + + + + T<sub>Gal</sub> + + + + + + + Frequency dependent Galactic background temperature in Kelvin + + + 1 + + + 9999.000000000000000 + + + 2.000000000000000 + + + + + + + K + + + + + + + Check to automatically update Tgal based on center frequency. Uncheck to manually enter a temperature in Kelvin + + + + + + + :/link.png:/link.png + + + true + + + true + + + + + + + Qt::Vertical + + + + + + + T<sub>SP</sub> + + + + + + + Spillover temperature in Kelvin + + + 1 + + + 1000.000000000000000 + + + + + + + K + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + T<sub>sys0</sub> + + + + + + + System noise temperature in Kelvin assuming no source (i.e. Tsource=0) + + + 0 + + + + + + + K + + + + + + + + + + + T<sub>Atm</sub> + + + + + + + Atmospheric noise temperature in Kelvin + + + 1 + + + 1000.000000000000000 + + + 2.000000000000000 + + + + + + + K + + + + + + + Check to automatically calculate Tatm from Tair, tz and elevation. Uncheck to manually enter a value + + + + + + + :/link.png:/link.png + + + true + + + true + + + + + + + Qt::Vertical + + + + + + + T<sub>Air</sub> + + + + + + + Surface air temperature in degrees celsius + + + 0 + + + -100.000000000000000 + + + 20.000000000000000 + + + + + + + C + + + + + + + Check to automatically update Tair from air temperature from Star Tracker. Uncheck to set manually + + + + + + + :/link.png:/link.png + + + true + + + true + + + + + + + Qt::Vertical + + + + + + + τ<sub>z</sub> + + + + + + + Zenith opacity + + + 4 + + + 100.000000000000000 + + + 0.001000000000000 + + + 0.005500000000000 + + + + + + + Qt::Vertical + + + + + + + El + + + + + + + Elevation angle in degrees + + + 0 + + + 90.000000000000000 + + + 90.000000000000000 + + + + + + + ° + + + + + + + Check to automatically update elevation from Star Tracker. Uncheck to manually enter an elevation in degrees. + + + + + + + :/link.png:/link.png + + + true + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + σT<sub>sys0</sub> + + + + + + + Standard deviation / RMS of Tsys0 + + + 0 + + + + + + + K + + + + + + + + + Qt::Horizontal + + + + + + + + + Baseline + + + + + + + + 56 + 0 + + + + Baseline to subtract from Tsys to calculate Tsource + + + + Tsys0 + + + + + Tmin + + + + + Tcold + + + + + + + + Qt::Vertical + + + + + + + ΔG/G + + + + + + + Gain variation + + + 4 + + + 1000.000000000000000 + + + 0.010000000000000 + + + + + + + Qt::Vertical + + + + + + + HPBW + + + + + + + Antenna half-power beamwidth in degrees or solid angle in steradians from Star Tracker + + + 0 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + ° + + + + + sr + + + + + + + + Qt::Vertical + + + + + + + Ω<sub>S</sub> + + + + + + + + 80 + 0 + + + + Angular size of source relative to antenna beam + + + 1 + + + + Unknown + + + + + Compact + + + + + Extended + + + + + Sun + + + + + Cas A + + + + + + + + Angle / solid angle subtended by source + + + 5 + + + 0.000010000000000 + + + 180.000000000000000 + + + 0.100000000000000 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Select degrees to enter source angle or steradians for source solid angle + + + + ° + + + + + sr + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + σS<sub>sys0</sub> + + + + + + + Standard deviation / RMS of flux assuming no source (i.e. Tsource=0) + + + 0 + + + + + + + Jy + + + + + + + + + + + 0 + 270 + 731 + 51 + + + + + 0 + 0 + + + + Run Control + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true + + + + + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + -5.000000000000000 + + + + + + + Stop + + + + + + + + 70 + 0 + + + + Measurement mode + + + + Single + + + + + Continuous + + + + + Sweep + + + + + + + + Delay in seconds after rotation before measurements starts + + + 1 + + + 1000000.000000000000000 + + + + + + + + 60 + 0 + + + + Coordinates to sweep + + + 0 + + + + Az/El + + + + + l/b + + + + + Offset + + + + + + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + -5.000000000000000 + + + + + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + 5.000000000000000 + + + + + + + Clear data + + + + + + + :/bin.png:/bin.png + + + + + + + Settle + + + + + + + El + + + + + + + Start + + + + + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + 5.000000000000000 + + + + + + + 1 + + + -180.000000000000000 + + + 180.000000000000000 + + + 5.000000000000000 + + + + + + + Az + + + + + + + Delay + + + + + + + Delay in seconds after a measurement before the next step starts + + + 1 + + + 1000000.000000000000000 + + + + + + + Start + + + + + + + + 56 + 0 + + + + When to start the sweep + + + + Now + + + + + At + + + + + + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + 5.000000000000000 + + + + + + + Stop + + + + + + + Start/stop measurements + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Step + + + + + + + Step + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 120 + 16777215 + + + + Measurement status + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Sweep status + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + 0 + 350 + 731 + 631 + + + + + 0 + 0 + + + + + 200 + 320 + + + + Spectrometer + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Select data to plot on chart + + + + Spectrum + + + + + Cal spectrum + + + + + + + + + 56 + 0 + + + + Spectrum Y axis units + + + + dBFS + + + + + SNR + + + + + dBm + + + + + Tsys K + + + + + Tsource K + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Open calibration settings dialog + + + + + + + :/listing.png:/listing.png + + + + + + + Clear calibration data + + + + + + + :/bin.png:/bin.png + + + + + + + Start hot calibration + + + + + + + :/radioastronomy/icons/hot.png:/radioastronomy/icons/hot.png + + + + + + + Start cold calibration + + + + + + + :/radioastronomy/icons/cold.png:/radioastronomy/icons/cold.png + + + + + + + Recalibrate all measurements when checked. Only use calibration for new measurements when unchecked. + + + + + + + :/recycle.png:/recycle.png + + + true + + + false + + + + + + + Display legend + + + + + + + :/radioastronomy/icons/legend.png:/radioastronomy/icons/legend.png + + + true + + + false + + + + + + + Download and plot LAB survey data for the corresponding Galactic latitude and longitude + + + + + + + :/radioastronomy/icons/lab.png:/radioastronomy/icons/lab.png + + + true + + + false + + + + + + + Calculate and plot distance to gas clouds + + + + + + + :/radioastronomy/icons/galactictriangle.png:/radioastronomy/icons/galactictriangle.png + + + true + + + false + + + + + + + Display reference spectral line + + + + + + + :/radioastronomy/icons/refline.png:/radioastronomy/icons/refline.png + + + true + + + false + + + + + + + Display Gaussian fitting tools + + + + + + + :/radioastronomy/icons/gaussian.png:/radioastronomy/icons/gaussian.png + + + true + + + false + + + + + + + Display markers + + + + + + + :/radioastronomy/icons/marker.png:/radioastronomy/icons/marker.png + + + true + + + false + + + + + + + Display peak data + + + + + + + :/radioastronomy/icons/peak.png:/radioastronomy/icons/peak.png + + + true + + + false + + + + + + + Reverse X axis + + + + + + + :/radioastronomy/icons/reverse.png:/radioastronomy/icons/reverse.png + + + true + + + false + + + + + + + Save charts to an animation file + + + + + + + :/camera.png:/camera.png + + + + + + + Save chart to an image file + + + + + + + :/picture.png:/picture.png + + + + + + + Load data from a .csv file + + + + + + + :/load.png:/load.png + + + + + + + Save data to a .csv file + + + + + + + :/save.png:/save.png + + + + + + + + + + 200 + 275 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Continuously scale X and Y axis so all data is visible + + + + + + A + + + true + + + + + + + + 0 + 0 + + + + Set X scale to fit all data + + + Autoscale spectrum + + + X + + + false + + + + + + + + 0 + 0 + + + + Set Y scale to fit all data + + + Autoscale spectrum + + + Y + + + false + + + + + + + Ref + + + + + + + Spectrum reference level (Y axis maximum value) + + + 1 + + + -150.000000000000000 + + + 10000000000000.000000000000000 + + + + + + + Range + + + + + + + Spectrum Y axis range + + + 1 + + + 0.100000000000000 + + + 10000000000000.000000000000000 + + + + + + + CF + + + + + + + Spectrum X axis center frequency in MHz + + + 3 + + + 0.000000000000000 + + + 20000.000000000000000 + + + 0.100000000000000 + + + + + + + Span + + + + + + + Spectrum X axis range in MHz + + + 2 + + + 0.010000000000000 + + + 100.000000000000000 + + + 0.100000000000000 + + + + + + + Sel + + + + + + + + 80 + 0 + + + + Object to select when clicking on the chart + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Date & time + + + + + + + Select spectrum + + + 0 + + + 1 + + + Qt::Horizontal + + + + + + + true + + + Date and time the spectrum was recorded + + + true + + + QAbstractSpinBox::NoButtons + + + dd/MM/yyyy HH:mm:ss + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Line + + + + + + + Reference spectral line + + + + HI + + + + + OH + + + + + DI + + + + + Custom + + + + + + + + Reference spectral line frequency in MHz + + + 6 + + + 20000.000000000000000 + + + 1420.404999999999973 + + + + + + + MHz + + + + + + + Qt::Vertical + + + + + + + Ref + + + + + + + Reference frame for velocities + + + + Topo + + + + + BCRS + + + + + LSR + + + + + + + + Qt::Vertical + + + + + + + R☉ + + + + + + + Distance from Sun to Galactic center in kiloparsec. Estimates range from 7.4 to 8.7 + + + 1 + + + 7.000000000000000 + + + 9.000000000000000 + + + 0.100000000000000 + + + 8.100000000000000 + + + + + + + kpc + + + + + + + Qt::Vertical + + + + + + + V☉ + + + + + + + Sun's orbital velocity around the Galactic Center in km/s. Typical values are 220 and 248 + + + 0 + + + 100.000000000000000 + + + 300.000000000000000 + + + 248.000000000000000 + + + + + + + km/s + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + f<sub>0</sub> + + + + + + + Centre frequency of Gaussian in MHz. Click on chart to set. + + + 3 + + + 20000.000000000000000 + + + 0.010000000000000 + + + 1420.404999999999973 + + + + + + + MHz + + + + + + + Qt::Vertical + + + + + + + a + + + + + + + Gaussian amplitude + + + 2 + + + 0.010000000000000 + + + 10000000.000000000000000 + + + + + + + f + + + + + + + Gaussian floor (minimum value) + + + 2 + + + -10000000.000000000000000 + + + 10000000.000000000000000 + + + 0.000000000000000 + + + + + + + Qt::Vertical + + + + + + + Δf<sub>FWHM</sub> + + + + + + + Frequency difference at full width half maximum in Hertz + + + 0 + + + 0.000000000000000 + + + 1000000.000000000000000 + + + 84774.000000000000000 + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + T<sub>K</sub> + + + + + + + Kinetic temperature in Kelvin + +CNM 50-100 +WNM 5000-10000 + + + 0 + + + 100000.000000000000000 + + + 100.000000000000000 + + + 6680.000000000000000 + + + + + + + K + + + + + + + Qt::Vertical + + + + + + + v<sub>t</sub> + + + + + + + RMS of turbulent velocity in km/s + + + 1 + + + 1000000.000000000000000 + + + 2.400000000000000 + + + + + + + km/s + + + + + + + Qt::Vertical + + + + + + + N<sub>H</sub> + + + + + + + Column density (cm<sup>-2</sup>) + + + true + + + + + + + cm<sup>-2</sup> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Tcold + + + + + Pcold + + + + + + + + Cold calibration antenna temperature in Kelvin or total channel power in dBm + + + 1 + + + -200.000000000000000 + + + 10000000000000.000000000000000 + + + 10.000000000000000 + + + + + + + K + + + + + + + Qt::Vertical + + + + + + + + Thot + + + + + Phot + + + + + + + + Hot calibration antenna temperature in Kelvin or total channel power in dBm + + + 1 + + + -200.000000000000000 + + + 10000000000000.000000000000000 + + + 120.000000000000000 + + + + + + + K + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Avg Δ + + + + + + + Average difference in dB between hot and cold. + +This should be close to the expected difference in power between hot and cold calibration points + + + true + + + + + + + dB + + + + + + + Qt::Vertical + + + + + + + y + + + + + + + Y-factor + + + + + + + Qt::Vertical + + + + + + + T<sub>rx</sub> + + + + + + + Receiver noise temperature in Kelvin calculated from Y-factor + + + true + + + + + + + K + + + + + + + Qt::Vertical + + + + + + + T<sub>sky</sub> + + + + + + + Sky temperature in Kelvin at time of cold calibration from StarTracker + + + + + + + K + + + + + + + Qt::Vertical + + + + + + + T<sub>SP</sub> + + + + + + + Estimated spillover temperature in Kelvin + + + true + + + + + + + K + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + true + + + 0 + + + false + + + + Name + + + Marker name + + + + + Freq (MHz) + + + Frequency in MHz + + + + + Power (dBFS) + + + + + ΔX (MHz) + + + + + ΔY + + + + + ΔTo + + + + + Vr (km/s) + + + Radial velocity in km/s + + + + + R (kpc) + + + Distance to Galactic center in kiloparsec + + + + + d (kpc) + + + Distance from observer in kiloparsec + + + + + Plot max + + + Check to plot maximum of two solutions for d in Star Tracker + + + + + Rmin (kpc) + + + Minimum distance to Galactic center along the line-of-slight in kiloparsec + + + + + Vmax (km/s) + + + Orbital velocity at minimum distance to Galactic Centre along line-of-sight in km/s + + + + + + + + + + + + + 0 + 990 + 731 + 481 + + + + + 0 + 0 + + + + + 200 + 230 + + + + Radiometer + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Select data to plot on chart + + + + Power vs time + + + + + Tsys vs time + + + + + Tsource vs time + + + + + Flux vs time + + + + + 2D map + + + + + + + + + 60 + 0 + + + + + dBFS + + + + + dBm + + + + + Watts + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Display legend + + + + + + + :/radioastronomy/icons/legend.png:/radioastronomy/icons/legend.png + + + true + + + true + + + + + + + Plot sensor 2 data + + + + + + + :/slot2_on.png:/slot2_on.png + + + true + + + true + + + + + + + Plot sensor 1 data + + + + + + + :/slot1_on.png:/slot1_on.png + + + true + + + true + + + + + + + Plot air temperature data + + + + + + + :/radioastronomy/icons/temperature.png:/radioastronomy/icons/temperature.png + + + true + + + true + + + + + + + Plot Tsys0 + + + + + + + :/radioastronomy/icons/noise.png:/radioastronomy/icons/noise.png + + + true + + + true + + + + + + + Display mean, RMS and standard deviation + + + σ + + + true + + + true + + + + + + + Display Gaussian fitting tools + + + + + + + :/radioastronomy/icons/gaussian.png:/radioastronomy/icons/gaussian.png + + + true + + + false + + + + + + + Display markers + + + + + + + :/radioastronomy/icons/marker.png:/radioastronomy/icons/marker.png + + + true + + + true + + + + + + + Display peak data + + + + + + + :/radioastronomy/icons/peak.png:/radioastronomy/icons/peak.png + + + true + + + true + + + + + + + Save chart to an image file + + + + + + + :/picture.png:/picture.png + + + + + + + Save data to a .csv file + + + + + + + :/save.png:/save.png + + + + + + + + + + 200 + 200 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Continuously scale X and Y axis so all data is visible + + + Autoscale spectrum + + + A + + + true + + + + + + + + 0 + 0 + + + + Set X scale to fit all data + + + Autoscale spectrum + + + X + + + false + + + + + + + + 0 + 0 + + + + Set Y scale to fit all data + + + Autoscale spectrum + + + Y + + + false + + + + + + + Ref + + + + + + + Reference level (Y axis maximum value) + + + 1 + + + -150.000000000000000 + + + 10000000000000.000000000000000 + + + + + + + Range + + + + + + + Y axis range + + + 1 + + + 0.100000000000000 + + + 10000000000000.000000000000000 + + + + + + + Start + + + + + + + X axis start time + + + + + + + End + + + + + + + X axis end time + + + + + + + Sel + + + + + + + + 80 + 0 + + + + Object to select when clicking on the chart + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 2 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Center + + + + + + + Date and time (x axis value) of the center of the Gaussian + + + dd/MM/yyyy HH:mm:ss + + + + + + + Qt::Vertical + + + + + + + a + + + + + + + Gaussian amplitude + + + 2 + + + 0.010000000000000 + + + 10000000.000000000000000 + + + + + + + f + + + + + + + Gaussian floor (minimum value) + + + 2 + + + -10000000.000000000000000 + + + 10000000.000000000000000 + + + 0.000000000000000 + + + + + + + Qt::Vertical + + + + + + + Δt<sub>FWHM</sub> + + + + + + + Time difference at full width half maximum in seconds + + + 0 + + + 0.000000000000000 + + + 10000000.000000000000000 + + + 10.000000000000000 + + + 1350.000000000000000 + + + + + + + s + + + + + + + Qt::Vertical + + + + + + + HPBW + + + + + + + Antenna half-power beam width in degrees assuming a drift-scan of the Sun with a radiation pattern matching the Gaussian + + + 1 + + + 0.100000000000000 + + + 180.000000000000000 + + + 0.100000000000000 + + + 5.000000000000000 + + + + + + + ° + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 2 + + + + + Width of map image in pixels + + + 0 + + + 1000 + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Width of map image in pixels + + + 0 + + + 1000 + + + 0 + + + + + + + Minimum value for X axis + + + 1 + + + -360.000000000000000 + + + 360.000000000000000 + + + + + + + Check to automatically update map dimensions when a sweep starts + + + + + + + :/link.png:/link.png + + + true + + + true + + + + + + + Maximum value for X axis + + + 1 + + + -360.000000000000000 + + + 400.000000000000000 + + + + + + + Height + + + + + + + to + + + + + + + Width + + + + + + + Y Range + + + + + + + Qt::Vertical + + + + + + + Qt::Vertical + + + + + + + ° + + + + + + + Maximum value for Y axis + + + 1 + + + -180.000000000000000 + + + 180.000000000000000 + + + + + + + + 0 + 0 + + + + Automatically scale axes range based on data in radiometer table + + + + + + A + + + false + + + + + + + Minimum value for Y axis + + + 1 + + + -180.000000000000000 + + + 180.000000000000000 + + + + + + + ° + + + + + + + X Range + + + + + + + ° + + + + + + + ° + + + + + + + + Az/El + + + + + l/b + + + + + Offset + + + + + + + + to + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Automatically scale colour gradient to range from minimum and maximum values + + + + + + A + + + true + + + + + + + Qt::Vertical + + + + + + + Min + + + + + + + 1 + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + + + + + dB + + + + + + + Qt::Vertical + + + + + + + Max + + + + + + + 1 + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + + + + + dB + + + + + + + Qt::Vertical + + + + + + + + Colour + + + + + Greyscale + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + true + + + 0 + + + false + + + + Name + + + Marker name + + + + + Date + + + Date of measurement + + + + + Time + + + Time of measurement + + + + + Power (dBFS) + + + Value of measurement + + + + + ΔX + + + Time difference between markers (d hh:mm:ss) + + + + + ΔY + + + Difference in Y values between markers + + + + + ΔTo + + + Which marker the delta values are to + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Mean + + + + + + + Arithmetic mean (average) + + + + + + + RMS + + + + + + + Root mean squared + + + + + + + σ + + + + + + + Standard deviation + + + + + + + + + + + + 10 + 1490 + 721 + 171 + + + + + 0 + 0 + + + + Radiometer Data + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + + Date + + + Date of measurement + + + + + Time + + + Time measurement finished + + + + + Power (FFT) + + + Measured power (Sum of FFT absolute magnitude) + + + + + Power (dBFS) + + + Measured power (dBFS) + + + + + Power (dBm) + + + Measured power (dBm) + + + + + Tsys (K) + + + Measured system noise temperature in Kelvin + + + + + Tsys0 (K) + + + System noise temperature in Kelvin assuming no source (i.e. Tsource=0) + + + + + Tsource (K) + + + Estimation of noise temperature contribution from astronomical source in Kelvin (Tsource=Tsys-Tsys0) + + + + + Tb (K) + + + Brightness temperature of source in Kelvin + + + + + Tsky (K) + + + Sky temperature in Kelvin towards target from Star Tracker + + + + + Sν (Jy) + + + Spectral flux density in Jansky + + + + + σTsys (K) + + + Standard deviation / RMS of temperature in Kelvin due to random fluctuations + + + + + σSsys (Jy) + + + Standard deviation / RMS of flux in Jansky due to random fluctuations + + + + + ΩA (sr) + + + Antenna solid angle in steradians + + + + + ΩS (sr) + + + Source solid angle in steradians + + + + + RA + + + Right ascension + + + + + Dec + + + Declination + + + + + l + + + Galactic latitude in degrees + + + + + b + + + Galactic longitude in degrees + + + + + Az + + + Azimuth in degrees + + + + + El + + + Elevation in degrees + + + + + Vbcrs + + + Velocity towards RA/Dec relative to barycentric reference frame (km/s) + + + + + Vlsr + + + Velocity towards RA/Dec relative to local standard of rest (km/s) + + + + + Solar Flux (Jy) + + + Solar flux in Jansky from Star Tracker + + + + + Air Temp (C) + + + Surface air temperature in degrees Celsius from Star Tracker + + + + + Sensor 1 + + + Sensor 1 data + + + + + Sensor 2 + + + Sensor 2 data + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + QChartView + QGraphicsView +
QtCharts
+
+ + WrappingDateTimeEdit + QDateTimeEdit +
gui/wrappingdatetimeedit.h
+ 1 +
+
+ + powerMean + powerRMS + powerSD + powerTable + fftSize + fftWindow + filterFreqs + starTracker + rotator + showSensors + tempRXSelect + tempRX + tempCMB + tempGal + tempGalLink + tempSP + tempAtm + tempAtmLink + tempAir + tempAirLink + zenithOpacity + elevation + elevationLink + gainVariation + sourceType + omegaS + omegaSUnits + startStop + clearData + runMode + sweepStartAtTime + sweepType + sweep1Start + sweep1Stop + sweep1Step + sweep1Delay + sweepStartDateTime + sweep2Start + sweep2Stop + sweep2Step + sweep2Delay + spectrumChartSelect + spectrumYUnits + clearCal + integration + startCalHot + startCalCold + recalibrate + deltaFrequency + spectrumShowLegend + spectrumShowLAB + spectrumShowDistance + rfBW + spectrumShowRefLine + spectrumTemp + spectrumMarker + spectrumPeak + powerChart + spectrumReverseXAxis + saveSpectrumChartImages + saveSpectrumChartImage + loadSpectrumData + saveSpectrumData + spectrumChart + spectrumAutoscale + sampleRate + spectrumAutoscaleX + spectrumAutoscaleY + spectrumReference + spectrumRange + spectrumCenterFreq + spectrumSpan + spectrumSelect + spectrumIndex + spectrumDateTime + spectrumLine + spectrumLineFrequency + refFrame + sunDistanceToGC + sunOrbitalVelocity + spectrumGaussianFreq + spectrumGaussianAmp + spectrumGaussianFloor + spectrumGaussianFWHM + spectrumTemperature + spectrumGaussianTurb + columnDensity + tCalColdSelect + tCalCold + tCalHotSelect + tCalHot + calAvgDiff + calYFactor + calTrx + calTsky + calTsp + spectrumMarkerTable + powerChartSelect + powerYUnits + powerShowLegend + powerShowSensor2 + powerShowSensor1 + powerShowAirTemp + powerShowTsys0 + powerShowAvg + powerShowGaussian + powerShowMarker + powerShowPeak + savePowerChartImage + savePowerData + powerAutoscale + powerAutoscaleX + powerAutoscaleY + powerReference + powerRange + powerStartTime + powerEndTime + powerSelect + powerGaussianCenter + powerGaussianAmp + powerGaussianFloor + powerGaussianFWHM + powerGaussianHPBW + power2DLinkSweep + power2DSweepType + power2DWidth + power2DXMin + power2DXMax + power2DAutoscale + power2DHeight + power2DYMin + power2DYMax + powerColourAutoscale + powerColourScaleMin + powerColourScaleMax + powerColourPalette + powerMarkerTable + + + + + + +
diff --git a/plugins/channelrx/radioastronomy/radioastronomyplugin.cpp b/plugins/channelrx/radioastronomy/radioastronomyplugin.cpp new file mode 100644 index 000000000..8c2661b55 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomyplugin.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 "radioastronomygui.h" +#endif +#include "radioastronomy.h" +#include "radioastronomywebapiadapter.h" +#include "radioastronomyplugin.h" + +const PluginDescriptor RadioAstronomyPlugin::m_pluginDescriptor = { + RadioAstronomy::m_channelId, + QStringLiteral("Radio Astronomy"), + QStringLiteral("6.17.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +RadioAstronomyPlugin::RadioAstronomyPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& RadioAstronomyPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void RadioAstronomyPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerRxChannel(RadioAstronomy::m_channelIdURI, RadioAstronomy::m_channelId, this); +} + +void RadioAstronomyPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + RadioAstronomy *instance = new RadioAstronomy(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* RadioAstronomyPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) const +{ + (void) deviceUISet; + (void) rxChannel; + return 0; +} +#else +ChannelGUI* RadioAstronomyPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const +{ + return RadioAstronomyGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +ChannelWebAPIAdapter* RadioAstronomyPlugin::createChannelWebAPIAdapter() const +{ + return new RadioAstronomyWebAPIAdapter(); +} diff --git a/plugins/channelrx/radioastronomy/radioastronomyplugin.h b/plugins/channelrx/radioastronomy/radioastronomyplugin.h new file mode 100644 index 000000000..edd1b1697 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomyplugin.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_RADIOASTRONOMYPLUGIN_H +#define INCLUDE_RADIOASTRONOMYPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class RadioAstronomyPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.radioastronomy") + +public: + explicit RadioAstronomyPlugin(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_RADIOASTRONOMYPLUGIN_H diff --git a/plugins/channelrx/radioastronomy/radioastronomysensordialog.cpp b/plugins/channelrx/radioastronomy/radioastronomysensordialog.cpp new file mode 100644 index 000000000..cbea748b8 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysensordialog.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 + +#include "util/visa.h" + +#include "radioastronomysensordialog.h" + +RadioAstronomySensorDialog::RadioAstronomySensorDialog(RadioAstronomySettings *settings, QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::RadioAstronomySensorDialog) +{ + ui->setupUi(this); + ui->sensor1Enabled->setChecked(settings->m_sensorEnabled[0]); + ui->sensor1Name->setText(settings->m_sensorName[0]); + ui->sensor1Device->setText(settings->m_sensorDevice[0]); + ui->sensor1Init->setPlainText(settings->m_sensorInit[0]); + ui->sensor1Measure->setText(settings->m_sensorMeasure[0]); + ui->sensor2Enabled->setChecked(settings->m_sensorEnabled[1]); + ui->sensor2Name->setText(settings->m_sensorName[1]); + ui->sensor2Device->setText(settings->m_sensorDevice[1]); + ui->sensor2Init->setPlainText(settings->m_sensorInit[1]); + ui->sensor2Measure->setText(settings->m_sensorMeasure[1]); + ui->period->setValue(settings->m_sensorMeasurePeriod); + VISA visa; + if (!visa.isAvailable()) + { + ui->sensor1Group->setEnabled(false); + ui->sensor2Group->setEnabled(false); + } +} + +RadioAstronomySensorDialog::~RadioAstronomySensorDialog() +{ + delete ui; +} + +void RadioAstronomySensorDialog::accept() +{ + m_settings->m_sensorEnabled[0] = ui->sensor1Enabled->isChecked(); + m_settings->m_sensorName[0] = ui->sensor1Name->text().trimmed(); + m_settings->m_sensorDevice[0] = ui->sensor1Device->text().trimmed(); + m_settings->m_sensorInit[0] = ui->sensor1Init->toPlainText(); + m_settings->m_sensorMeasure[0] = ui->sensor1Measure->text().trimmed(); + m_settings->m_sensorEnabled[1] = ui->sensor2Enabled->isChecked(); + m_settings->m_sensorName[1] = ui->sensor2Name->text().trimmed(); + m_settings->m_sensorDevice[1] = ui->sensor2Device->text().trimmed(); + m_settings->m_sensorInit[1] = ui->sensor2Init->toPlainText(); + m_settings->m_sensorMeasure[1] = ui->sensor2Measure->text().trimmed(); + m_settings->m_sensorMeasurePeriod = ui->period->value(); + QDialog::accept(); +} diff --git a/plugins/channelrx/radioastronomy/radioastronomysensordialog.h b/plugins/channelrx/radioastronomy/radioastronomysensordialog.h new file mode 100644 index 000000000..267574b4f --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysensordialog.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_RADIOASTRONOMYSENSORDIALOG_H +#define INCLUDE_RADIOASTRONOMYSENSORDIALOG_H + +#include "ui_radioastronomysensordialog.h" +#include "radioastronomysettings.h" + +class RadioAstronomySensorDialog : public QDialog { + Q_OBJECT + +public: + explicit RadioAstronomySensorDialog(RadioAstronomySettings *settings, QWidget* parent = 0); + ~RadioAstronomySensorDialog(); + + RadioAstronomySettings *m_settings; + +private slots: + void accept(); + +private: + Ui::RadioAstronomySensorDialog* ui; +}; + +#endif // INCLUDE_RADIOASTRONOMYSENSORDIALOG_H diff --git a/plugins/channelrx/radioastronomy/radioastronomysensordialog.ui b/plugins/channelrx/radioastronomy/radioastronomysensordialog.ui new file mode 100644 index 000000000..34d524cc7 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysensordialog.ui @@ -0,0 +1,251 @@ + + + RadioAstronomySensorDialog + + + + 0 + 0 + 514 + 746 + + + + + Liberation Sans + 9 + + + + Sensor Control Settings + + + + + + + 0 + 0 + + + + + + + Sensor 1 + + + + + + + + + Device + + + + + + + Init + + + + + + + + + + Measure + + + + + + + + + + Name + + + + + + + + + + Enabled + + + + + + + Check to enable measurements from this device + + + + + + + + + + + + + Sensor 2 + + + + + + Device + + + + + + + Init + + + + + + + + + + Enabled + + + + + + + Name + + + + + + + Measure + + + + + + + + + + + + + + + + Check to enable measurements from this device + + + + + + + + + + + + + Timing + + + + + + Measurement period (seconds) + + + + + + + Delay in seconds between measurements being made + + + 3 + + + 0.001000000000000 + + + 100.000000000000000 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + period + + + + + + + buttonBox + accepted() + RadioAstronomySensorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RadioAstronomySensorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/channelrx/radioastronomy/radioastronomysettings.cpp b/plugins/channelrx/radioastronomy/radioastronomysettings.cpp new file mode 100644 index 000000000..fe03f8cfa --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysettings.cpp @@ -0,0 +1,491 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "radioastronomysettings.h" + +const QStringList RadioAstronomySettings::m_pipeTypes = { + QStringLiteral("StarTracker") +}; + +const QStringList RadioAstronomySettings::m_pipeURIs = { + QStringLiteral("sdrangel.feature.startracker") +}; + +RadioAstronomySettings::RadioAstronomySettings() : + m_channelMarker(0) +{ + resetToDefaults(); +} + +void RadioAstronomySettings::resetToDefaults() +{ + m_inputFrequencyOffset = 0; + m_sampleRate = 1000000; + m_rfBandwidth = 1000000; + m_integration = 4000; + m_fftSize = 256; + m_fftWindow = HAN; + m_filterFreqs = ""; + + m_starTracker = ""; + m_rotator = "None"; + + m_tempRX = 75.0f; + m_tempCMB = 2.73; + m_tempGal = 2.0; + m_tempSP = 85.0; + m_tempAtm = 2.0; + m_tempAir = 15.0; + m_zenithOpacity = 0.0055; + m_elevation = 90.0; + m_tempGalLink = true; + m_tempAtmLink = true; + m_tempAirLink = true; + m_elevationLink = true; + + m_gainVariation = 0.0011f; + m_sourceType = UNKNOWN; + m_omegaS = 0.0; + m_omegaSUnits = DEGREES; + m_omegaAUnits = DEGREES; + + m_spectrumPeaks = false; + m_spectrumMarkers = false; + m_spectrumTemp = false; + m_spectrumReverseXAxis = false; + m_spectrumRefLine = false; + m_spectrumLAB = false; + m_spectrumDistance = false; + m_spectrumLegend = false; + m_spectrumReference = 0.0f; + m_spectrumRange = 120.0f; + m_spectrumCenterFreqOffset = 0.0f; + m_spectrumAutoscale = true; + m_spectrumSpan = 1.0f; + m_spectrumYScale = SY_DBFS; + m_spectrumBaseline = SBL_TSYS0; + m_recalibrate = false; + m_tCalHot = 300.0f; + m_tCalCold = 10.0f; + m_line = HI; + m_lineCustomFrequency = 0.0f; + m_refFrame = LSR; + + m_powerAutoscale = true; + m_powerReference = 0.0f; + m_powerRange = 100.0f; + m_powerPeaks = false; + m_powerMarkers = false; + m_powerAvg = false; + m_powerLegend = false; + m_powerYData = PY_POWER; + m_powerYUnits = PY_DBFS; + m_powerShowTsys0 = false; + m_powerShowAirTemp = false; + m_powerShowGaussian = false; + + m_power2DLinkSweep = true; + m_power2DSweepType = SWP_OFFSET; + m_power2DHeight = 3; + m_power2DWidth = 3; + m_power2DXMin = 0; + m_power2DXMax = 10; + m_power2DYMin = 0; + m_power2DYMax = 10; + m_powerColourAutoscale = true; + m_powerColourScaleMin = 0.0f; + m_powerColourScaleMax = 0.0f; + m_powerColourPalette = "Colour"; + + m_sensorName[0] = "Temperature"; + m_sensorDevice[0] = ""; + m_sensorInit[0] = "UNIT:TEMP C"; + m_sensorMeasure[0] = "MEAS:TEMP?"; + m_sensorEnabled[0] = false; + m_sensorVisible[0] = false; + m_sensorName[1] = "Voltage"; + m_sensorDevice[1] = ""; + m_sensorInit[1] = ""; + m_sensorMeasure[1] = "MEAS:VOLT:DC?"; + m_sensorEnabled[1] = false; + m_sensorVisible[1] = false; + m_sensorMeasurePeriod = 1.0f; + + m_sunDistanceToGC = 8.1f; + m_sunOrbitalVelocity = 248.0f; + + m_runMode = CONTINUOUS; + m_sweepStartAtTime = false; + m_sweepStartDateTime = QDateTime::currentDateTime(); + m_sweepType = SWP_OFFSET; + m_sweep1Start = -5.0; + m_sweep1Stop = 5.0; + m_sweep1Step = 5.0; + m_sweep1Delay = 0.0; + m_sweep2Start = -5.0; + m_sweep2Stop = 5.0; + m_sweep2Step = 5.0; + m_sweep2Delay = 0.0; + + m_gpioEnabled = false; + m_gpioPin = 0; + m_gpioSense = 1; + m_startCalCommand = ""; + m_stopCalCommand = ""; + m_calCommandDelay = 1.0f; + + m_rgbColor = QColor(102, 0, 0).rgb(); + m_title = "Radio Astronomy"; + 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 < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) + { + m_powerTableColumnIndexes[i] = i; + m_powerTableColumnSizes[i] = -1; // Autosize + } +} + +QByteArray RadioAstronomySettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, m_sampleRate); + s.writeS32(3, m_rfBandwidth); + s.writeS32(4, m_integration); + s.writeS32(5, m_fftSize); + s.writeS32(6, (int)m_fftWindow); + s.writeString(7, m_filterFreqs); + + s.writeString(10, m_starTracker); + s.writeString(11, m_rotator); + + s.writeFloat(20, m_tempRX); + s.writeFloat(21, m_tempCMB); + s.writeFloat(22, m_tempGal); + s.writeFloat(23, m_tempSP); + s.writeFloat(24, m_tempAtm); + s.writeFloat(25, m_tempAir); + s.writeFloat(26, m_zenithOpacity); + s.writeFloat(27, m_elevation); + s.writeBool(28, m_tempGalLink); + s.writeBool(29, m_tempAtmLink); + s.writeBool(30, m_tempAirLink); + s.writeBool(31, m_elevationLink); + + s.writeFloat(40, m_gainVariation); + s.writeS32(41, (int)m_sourceType); + s.writeFloat(42, m_omegaS); + s.writeS32(43, (int)m_omegaSUnits); + s.writeS32(44, (int)m_omegaAUnits); + + s.writeBool(50, m_spectrumPeaks); + s.writeBool(51, m_spectrumMarkers); + s.writeBool(52, m_spectrumTemp); + s.writeBool(53, m_spectrumReverseXAxis); + s.writeBool(54, m_spectrumRefLine); + s.writeBool(55, m_spectrumLegend); + s.writeBool(56, m_spectrumDistance); + s.writeBool(57, m_spectrumLAB); + + s.writeFloat(60, m_spectrumReference); + s.writeFloat(61, m_spectrumRange); + s.writeFloat(62, m_spectrumSpan); + s.writeFloat(63, m_spectrumCenterFreqOffset); + s.writeBool(64, m_spectrumAutoscale); + s.writeS32(65, (int)m_spectrumYScale); + s.writeS32(66, (int)m_spectrumBaseline); + + s.writeBool(70, m_recalibrate); + s.writeFloat(71, m_tCalHot); + s.writeFloat(72, m_tCalCold); + + s.writeS32(73, (int)m_line); + s.writeFloat(74, m_lineCustomFrequency); + s.writeS32(75, (int)m_refFrame); + + s.writeFloat(76, m_sunDistanceToGC); + s.writeFloat(77, m_sunOrbitalVelocity); + + s.writeBool(80, m_powerPeaks); + s.writeBool(81, m_powerMarkers); + s.writeBool(82, m_powerAvg); + s.writeBool(83, m_powerLegend); + s.writeBool(84, m_powerShowTsys0); + s.writeBool(85, m_powerShowAirTemp); + s.writeBool(86, m_powerShowGaussian); + s.writeFloat(87, m_powerReference); + s.writeFloat(88, m_powerRange); + s.writeBool(89, m_powerAutoscale); + s.writeS32(90, (int)m_powerYData); + s.writeS32(91, (int)m_powerYUnits); + + s.writeBool(100, m_power2DLinkSweep); + s.writeS32(102, (int)m_power2DSweepType); + s.writeS32(103, m_power2DWidth); + s.writeS32(104, m_power2DHeight); + s.writeFloat(105, m_power2DXMin); + s.writeFloat(106, m_power2DXMax); + s.writeFloat(107, m_power2DYMin); + s.writeFloat(108, m_power2DYMax); + s.writeBool(109, m_powerColourAutoscale); + s.writeFloat(110, m_powerColourScaleMin); + s.writeFloat(111, m_powerColourScaleMax); + s.writeString(112, m_powerColourPalette); + + s.writeS32(120, m_runMode); + s.writeBool(121, m_sweepStartAtTime); + s.writeS64(122, m_sweepStartDateTime.toMSecsSinceEpoch()); + s.writeS32(123, (int)m_sweepType); + s.writeFloat(124, m_sweep1Start); + s.writeFloat(125, m_sweep1Stop); + s.writeFloat(126, m_sweep1Step); + s.writeFloat(127, m_sweep1Delay); + s.writeFloat(128, m_sweep2Start); + s.writeFloat(129, m_sweep2Stop); + s.writeFloat(130, m_sweep2Step); + s.writeFloat(131, m_sweep2Delay); + + s.writeString(140, m_sensorName[0]); + s.writeString(141, m_sensorDevice[0]); + s.writeString(142, m_sensorInit[0]); + s.writeString(143, m_sensorMeasure[0]); + s.writeBool(144, m_sensorEnabled[0]); + s.writeBool(145, m_sensorVisible[0]); + s.writeString(146, m_sensorName[1]); + s.writeString(147, m_sensorDevice[1]); + s.writeString(148, m_sensorInit[1]); + s.writeString(149, m_sensorMeasure[1]); + s.writeBool(150, m_sensorEnabled[1]); + s.writeBool(151, m_sensorVisible[1]); + s.writeFloat(152, m_sensorMeasurePeriod); + + s.writeBool(160, m_gpioEnabled); + s.writeS32(161, m_gpioPin); + s.writeS32(162, m_gpioSense); + + s.writeString(167, m_startCalCommand); + s.writeString(168, m_stopCalCommand); + s.writeFloat(169, m_calCommandDelay); + + s.writeU32(180, m_rgbColor); + s.writeString(181, m_title); + if (m_channelMarker) { + s.writeBlob(182, m_channelMarker->serialize()); + } + s.writeS32(183, m_streamIndex); + s.writeBool(184, m_useReverseAPI); + s.writeString(185, m_reverseAPIAddress); + s.writeU32(186, m_reverseAPIPort); + s.writeU32(187, m_reverseAPIDeviceIndex); + s.writeU32(188, m_reverseAPIChannelIndex); + + for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) { + s.writeS32(400 + i, m_powerTableColumnIndexes[i]); + } + for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) { + s.writeS32(500 + i, m_powerTableColumnSizes[i]); + } + + return s.final(); +} + +bool RadioAstronomySettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + qint64 dttmp; + + d.readS32(1, &m_inputFrequencyOffset, 0); + d.readS32(2, &m_sampleRate, 1000000); + d.readS32(3, &m_rfBandwidth, 1000000); + d.readS32(4, &m_integration, 4000); + d.readS32(5, &m_fftSize, 256); + d.readS32(6, (int*)&m_fftWindow, (int)HAN); + d.readString(7, &m_filterFreqs, ""); + + d.readString(10, &m_starTracker, ""); + d.readString(11, &m_rotator, "None"); + + d.readFloat(20, &m_tempRX, 75.0f); + d.readFloat(21, &m_tempCMB, 2.73f); + d.readFloat(22, &m_tempGal, 2.0f); + d.readFloat(23, &m_tempSP, 85.0f); + d.readFloat(24, &m_tempAtm, 2.0f); + d.readFloat(25, &m_tempAir, 15.0f); + d.readFloat(26, &m_zenithOpacity, 0.0055f); + d.readFloat(27, &m_elevation, 90.0f); + d.readBool(28, &m_tempGalLink, true); + d.readBool(29, &m_tempAtmLink, true); + d.readBool(30, &m_tempAirLink, true); + d.readBool(31, &m_elevationLink, true); + + d.readFloat(40, &m_gainVariation, 0.0011f); + d.readS32(41, (int*)&m_sourceType, UNKNOWN); + d.readFloat(42, &m_omegaS, 0.0f); + d.readS32(43, (int*)&m_omegaSUnits, DEGREES); + d.readS32(44, (int*)&m_omegaAUnits, DEGREES); + + d.readBool(50, &m_spectrumPeaks, false); + d.readBool(51, &m_spectrumMarkers, false); + d.readBool(52, &m_spectrumTemp, false); + d.readBool(53, &m_spectrumReverseXAxis, false); + d.readBool(54, &m_spectrumRefLine, false); + d.readBool(55, &m_spectrumLegend, false); + d.readBool(56, &m_spectrumDistance, false); + d.readBool(57, &m_spectrumLAB, false); + + d.readFloat(60, &m_spectrumReference, 0.0f); + d.readFloat(61, &m_spectrumRange, 120.0f); + d.readFloat(62, &m_spectrumSpan, 1.0f); + d.readFloat(63, &m_spectrumCenterFreqOffset, 0.0f); + d.readBool(64, &m_spectrumAutoscale, false); + d.readS32(65, (int*)&m_spectrumYScale, SY_DBFS); + d.readS32(66, (int*)&m_spectrumBaseline, SBL_TSYS0); + + d.readBool(70, &m_recalibrate, false); + d.readFloat(71, &m_tCalHot, 300.0f); + d.readFloat(72, &m_tCalCold, 10.0f); + + d.readS32(73, (int*)&m_line, (int)HI); + d.readFloat(74, &m_lineCustomFrequency, 0.0f); + d.readS32(75, (int*)&m_refFrame, LSR); + + d.readFloat(76, &m_sunDistanceToGC, 8.1f); + d.readFloat(77, &m_sunOrbitalVelocity, 248.0f); + + d.readBool(80, &m_powerPeaks, false); + d.readBool(81, &m_powerMarkers, false); + d.readBool(82, &m_powerAvg, false); + d.readBool(83, &m_powerLegend, false); + d.readBool(84, &m_powerShowTsys0, false); + d.readBool(85, &m_powerShowAirTemp, false); + d.readBool(86, &m_powerShowGaussian, false); + d.readFloat(87, &m_powerReference, 0.0f); + d.readFloat(88, &m_powerRange, 100.0f); + d.readBool(89, &m_powerAutoscale, true); + d.readS32(90, (int*)&m_powerYData, PY_POWER); + d.readS32(91, (int*)&m_powerYUnits, PY_DBFS); + + d.readBool(100, &m_power2DLinkSweep, true); + d.readS32(102, (int*)&m_power2DSweepType, SWP_OFFSET); + d.readS32(103, &m_power2DWidth, 3); + d.readS32(104, &m_power2DHeight, 3); + d.readFloat(105, &m_power2DXMin, 0); + d.readFloat(106, &m_power2DXMax, 10); + d.readFloat(107, &m_power2DYMin, 0); + d.readFloat(108, &m_power2DYMax, 10); + d.readBool(109, &m_powerColourAutoscale, true); + d.readFloat(110, &m_powerColourScaleMin, 0.0f); + d.readFloat(111, &m_powerColourScaleMax, 0.0f); + d.readString(112, &m_powerColourPalette, "Colour"); + + d.readS32(120, (int *)&m_runMode, CONTINUOUS); + d.readBool(121, &m_sweepStartAtTime, false); + d.readS64(122, &dttmp, QDateTime::currentDateTime().toMSecsSinceEpoch()); + m_sweepStartDateTime = QDateTime::fromMSecsSinceEpoch(dttmp); + d.readS32(123, (int*)&m_sweepType, SWP_OFFSET); + d.readFloat(124, &m_sweep1Start, -5.0f); + d.readFloat(125, &m_sweep1Stop, 5.0f); + d.readFloat(126, &m_sweep1Step, 5.0f); + d.readFloat(127, &m_sweep1Delay, 0.0f); + d.readFloat(128, &m_sweep2Start, -5.0f); + d.readFloat(129, &m_sweep2Stop, 5.0); + d.readFloat(130, &m_sweep2Step, 5.0f); + d.readFloat(131, &m_sweep2Delay, 0.0f); + + d.readString(140, &m_sensorName[0], ""); + d.readString(141, &m_sensorDevice[0], ""); + d.readString(142, &m_sensorInit[0], ""); + d.readString(143, &m_sensorMeasure[0], ""); + d.readBool(144, &m_sensorEnabled[0], false); + d.readBool(145, &m_sensorVisible[0], false); + d.readString(146, &m_sensorName[1], ""); + d.readString(147, &m_sensorDevice[1], ""); + d.readString(148, &m_sensorInit[1], ""); + d.readString(149, &m_sensorMeasure[1], ""); + d.readBool(150, &m_sensorEnabled[1], false); + d.readBool(151, &m_sensorVisible[1], false); + d.readFloat(152, &m_sensorMeasurePeriod, 1.0f); + + d.readBool(160, &m_gpioEnabled, false); + d.readS32(161, &m_gpioPin, 0); + d.readS32(162, &m_gpioSense, 1); + + d.readString(167, &m_startCalCommand, ""); + d.readString(168, &m_stopCalCommand, ""); + d.readFloat(169, &m_calCommandDelay, 1.0f); + + d.readU32(180, &m_rgbColor, QColor(102, 0, 0).rgb()); + d.readString(181, &m_title, "Radio Astronomy"); + d.readBlob(182, &bytetmp); + if (m_channelMarker) { + m_channelMarker->deserialize(bytetmp); + } + d.readS32(183, &m_streamIndex, 0); + d.readBool(184, &m_useReverseAPI, false); + d.readString(185, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(186, &utmp, 0); + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + d.readU32(187, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(188, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) { + d.readS32(400 + i, &m_powerTableColumnIndexes[i], i); + } + for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) { + d.readS32(500 + i, &m_powerTableColumnSizes[i], -1); + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/channelrx/radioastronomy/radioastronomysettings.h b/plugins/channelrx/radioastronomy/radioastronomysettings.h new file mode 100644 index 000000000..dc1a2c9e9 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysettings.h @@ -0,0 +1,225 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMYSETTINGS_H +#define INCLUDE_RADIOASTRONOMYSETTINGS_H + +#include +#include +#include + +#include "dsp/dsptypes.h" + +class Serializable; + +// Number of columns in the tables +#define RADIOASTRONOMY_POWERTABLE_COLUMNS 27 + +// Number of sensors +#define RADIOASTRONOMY_SENSORS 2 + +struct RadioAstronomySettings +{ + int m_inputFrequencyOffset; + int m_sampleRate; + int m_rfBandwidth; + int m_integration; //!< Number of samples to integrate + + int m_fftSize; + enum FFTWindow { + REC, + HAN + } m_fftWindow; //!< FFT windowing function + QString m_filterFreqs; //!< List of channels (bin indices) to filter in FFT to remove RFI + + QString m_starTracker; //!< Name of Star Tracker plugin to link with + QString m_rotator; //!< Name of antenna rotator + + float m_tempRX; //!< Receiver noise temperature in K (Front end NF) + float m_tempCMB; //!< Cosmic microwave background temperature in K + float m_tempGal; //!< Galactic background temperature in K + float m_tempSP; //!< Spillover temperature in K + float m_tempAtm; //!< Atmospheric temperature in K + float m_tempAir; //!< Surface air temperature in C + float m_zenithOpacity; //!< Opacity of atmosphere at zenith + float m_elevation; //!< Elevation in degrees, if not using value from Star Tracker + bool m_tempGalLink; + bool m_tempAtmLink; + bool m_tempAirLink; + bool m_elevationLink; + + float m_gainVariation; //!< delta G/G + enum SourceType { + UNKNOWN, + COMPACT, + EXTENDED, + SUN, + CAS_A + } m_sourceType; //!< Whether the source it smaller than the beam + float m_omegaS; //!< Source angle + enum AngleUnits { + DEGREES, + STERRADIANS + } m_omegaSUnits; + enum AngleUnits m_omegaAUnits; + + bool m_spectrumPeaks; + bool m_spectrumMarkers; + bool m_spectrumTemp; + bool m_spectrumReverseXAxis; + bool m_spectrumRefLine; + bool m_spectrumLAB; + bool m_spectrumDistance; + bool m_spectrumLegend; + float m_spectrumReference; //!< In dB + float m_spectrumRange; //!< In dB + float m_spectrumSpan; //!< In Mhz + float m_spectrumCenterFreqOffset; //!< Offset - rather than absolute - In Mhz + bool m_spectrumAutoscale; + enum SpectrumYScale { + SY_DBFS, + SY_SNR, + SY_DBM, + SY_TSYS, + SY_TSOURCE + } m_spectrumYScale; + enum SpectrumBaseline { + SBL_TSYS0, + SBL_TMIN, + SBL_CAL_COLD + } m_spectrumBaseline; + bool m_recalibrate; + float m_tCalHot; //!< Hot calibration antenna noise temperature in K (Sky + Spillover?) + float m_tCalCold; //!< Cold calibration antenna noise temperature in K + enum Line { + HI, + OH, + DI, + CUSTOM_LINE + } m_line; //!< Spectral line to plot and use as Doppler reference + float m_lineCustomFrequency; //!< Spectral line frequency when m_line==CUSTOM + enum RefFrame { + TOPOCENTRIC, + BCRS, + LSR + } m_refFrame; //!< Reference frame for velocities + + float m_sunDistanceToGC; //!< Sun distance to Galactic Center In kpc + float m_sunOrbitalVelocity; //!< In km/s around GC + + bool m_powerPeaks; + bool m_powerMarkers; + bool m_powerAvg; + bool m_powerLegend; + bool m_powerShowTsys0; //!< Plot total noise temperature + bool m_powerShowAirTemp; + bool m_powerShowGaussian; + float m_powerReference; //!< In dB + float m_powerRange; //!< In dB + bool m_powerAutoscale; + enum PowerYData { + PY_POWER, + PY_TSYS, + PY_TSOURCE, + PY_FLUX, + PY_2D_MAP + } m_powerYData; + enum PowerYUnits { + PY_DBFS, + PY_DBM, + PY_WATTS, + PY_KELVIN, + PY_SFU, + PY_JANSKY + } m_powerYUnits; + + enum SweepType { + SWP_AZEL, + SWP_LB, + SWP_OFFSET + }; + + bool m_power2DLinkSweep; //. // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" + +#include "radioastronomy.h" +#include "radioastronomysink.h" + +RadioAstronomySink::RadioAstronomySink(RadioAstronomy *aisDemod) : + m_radioAstronomy(aisDemod), + m_channelSampleRate(1000000), + m_channelFrequencyOffset(0), + m_fftSequence(-1), + m_fft(nullptr), + m_fftCounter(0), + m_fftSum(nullptr), + m_fftTemp(nullptr), + m_fftSumCount(0), + m_enabled(false), + m_cal(false), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_messageQueueToChannel(nullptr) +{ + m_magsq = 0.0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +RadioAstronomySink::~RadioAstronomySink() +{ + delete[] m_fftSum; + delete[] m_fftTemp; +} + +void RadioAstronomySink::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 RadioAstronomySink::processOneSample(Complex &ci) +{ + // Calculate power + double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag(); + double magsq = (magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED)); + + // Calculate average and peak levels for level meter + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + m_magsqCount++; + + if (m_enabled || m_cal) + { + // Add to FFT input buffer + m_fft->in()[m_fftCounter] = Complex(ci.real() / SDR_RX_SCALEF, ci.imag() / SDR_RX_SCALEF); + m_fftCounter++; + if (m_fftCounter >= m_settings.m_fftSize) + { + // Calculate FFT + m_fftWindow.apply(m_fft->in()); + m_fft->transform(); + m_fftCounter = 0; + + // Calculate power and accumulate + for (int i = 0; i < m_settings.m_fftSize; i++) + { + Complex s = m_fft->out()[i]; + Real v = s.real() * s.real() + s.imag() * s.imag(); + Real enbw = 1.0f; + /*if (m_settings.m_fftWindow == RadioAstronomySettings::HAN && m_settings.m_fftCorrection == RadioAstronomySettings::POWER) { + enbw = 1.5; // FIXME: Small dependence on fftSize in Matlab + }*/ + m_fftSum[i] += v / (enbw * m_settings.m_fftSize * m_settings.m_fftSize); // Why FFT size here and not Fs? + } + + m_fftSumCount++; + if (m_fftSumCount >= m_settings.m_integration) + { + // Average + for (int i = 0; i < m_settings.m_fftSize; i++) { + m_fftSum[i] /= m_fftSumCount; + } + + // Put negative frequencies first + std::copy(m_fftSum + m_settings.m_fftSize/2, m_fftSum + m_settings.m_fftSize, m_fftTemp); + std::copy(m_fftSum, m_fftSum + m_settings.m_fftSize/2, m_fftTemp + m_settings.m_fftSize/2); + + // Filter freqs with RFI + if (m_filterBins.size() > 0) + { + // Find minimum value to use as replacement + // Should possibly use an average of the n lowest values or something + float minVal = std::numeric_limits::max(); + for (int i = 0; i < m_settings.m_fftSize; i++) { + minVal = std::min(minVal, m_fftTemp[i]); + } + for (int i = 0; i < m_filterBins.size(); i++) + { + int bin = m_filterBins[i]; + if (bin < m_settings.m_fftSize) { + m_fftTemp[bin] = minVal; + } + } + } + + getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(100)); + + if (m_cal) + { + // Indicate calibration complete + if (getMessageQueueToChannel()) + { + RadioAstronomy::MsgCalComplete *msg = RadioAstronomy::MsgCalComplete::create(m_fftTemp, m_settings.m_fftSize, QDateTime::currentDateTime(), m_hot); + getMessageQueueToChannel()->push(msg); + } + + // Cal complete + m_cal = false; + } + else + { + // Send averaged FFT to channel + if (getMessageQueueToChannel()) + { + + RadioAstronomy::MsgFFTMeasurement *msg = RadioAstronomy::MsgFFTMeasurement::create(m_fftTemp, m_settings.m_fftSize, QDateTime::currentDateTime()); + getMessageQueueToChannel()->push(msg); + } + + m_enabled = (m_settings.m_runMode == RadioAstronomySettings::CONTINUOUS); + if (m_enabled) { + getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0)); + } + } + + m_fftSumCount = 0; + std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f); + } + else + { + // Don't send more than ~4 updates per second + int fftsPerSecond = m_settings.m_sampleRate / m_settings.m_fftSize; + if ((m_fftSumCount % (fftsPerSecond/4)) == 0) { + getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(100 * m_fftSumCount / m_settings.m_integration)); + } + } + + } + } +} + +void RadioAstronomySink::startMeasurements() +{ + getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0)); + m_enabled = true; + m_fftSumCount = 0; + std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f); +} + +void RadioAstronomySink::stopMeasurements() +{ + m_enabled = false; +} + +void RadioAstronomySink::startCal(bool hot) +{ + getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0)); + m_cal = true; + m_hot = hot; + m_fftSumCount = 0; + std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f); +} + +void RadioAstronomySink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "RadioAstronomySink::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, m_settings.m_rfBandwidth / 2.0f); + m_interpolatorDistance = (Real) channelSampleRate / (Real) m_settings.m_sampleRate; + m_interpolatorDistanceRemain = m_interpolatorDistance; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void RadioAstronomySink::applySettings(const RadioAstronomySettings& settings, bool force) +{ + qDebug() << "RadioAstronomySink::applySettings:" + << " m_sampleRate: " << settings.m_sampleRate + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_fftSize: " << settings.m_fftSize + << " m_fftWindow: " << settings.m_fftWindow + << " m_filterFreqs: " << settings.m_filterFreqs + << " force: " << force; + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) + || (settings.m_sampleRate != m_settings.m_sampleRate) + || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.0f); // 2.0 rather than 2.2 as in other plugins, to reduce rolloff at edge of band + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) settings.m_sampleRate; + m_interpolatorDistanceRemain = m_interpolatorDistance; + } + + if ((settings.m_fftSize != m_settings.m_fftSize) || force) + { + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + if (m_fftSequence >= 0) { + fftFactory->releaseEngine(m_settings.m_fftSize, false, m_fftSequence); + } + m_fftSequence = fftFactory->getEngine(settings.m_fftSize, false, &m_fft); + m_fftCounter = 0; + delete[] m_fftSum; + delete[] m_fftTemp; + m_fftSum = new Real[settings.m_fftSize](); + m_fftTemp = new Real[settings.m_fftSize](); + m_fftSumCount = 0; + } + + if ((settings.m_fftSize != m_settings.m_fftSize) + || (settings.m_fftWindow != m_settings.m_fftWindow) + || force) + { + if (settings.m_fftWindow == RadioAstronomySettings::HAN) { + m_fftWindow.create(FFTWindow::Hanning, settings.m_fftSize); + } else { + m_fftWindow.create(FFTWindow::Rectangle, settings.m_fftSize); + } + } + + if ((settings.m_filterFreqs != m_settings.m_filterFreqs) || force) + { + m_filterBins.clear(); + QStringList filterFreqs = settings.m_filterFreqs.split(" "); + for (int i = 0; i < filterFreqs.size(); i++) + { + bool ok; + int bin = filterFreqs[i].toInt(&ok); + if (ok) { + m_filterBins.append(bin); + } + } + } + + m_settings = settings; +} diff --git a/plugins/channelrx/radioastronomy/radioastronomysink.h b/plugins/channelrx/radioastronomy/radioastronomysink.h new file mode 100644 index 000000000..6386c42fd --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomysink.h @@ -0,0 +1,123 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMYSINK_H +#define INCLUDE_RADIOASTRONOMYSINK_H + +#include "dsp/channelsamplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/fftfactory.h" +#include "dsp/fftengine.h" +#include "dsp/fftwindow.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" + +#include "radioastronomysettings.h" + +class ChannelAPI; +class RadioAstronomy; + +class RadioAstronomySink : public ChannelSampleSink { +public: + RadioAstronomySink(RadioAstronomy *aisDemod); + ~RadioAstronomySink(); + + 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 RadioAstronomySettings& settings, bool force = false); + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + void setChannel(ChannelAPI *channel) { m_channel = channel; } + void startMeasurements(); + void stopMeasurements(); + void startCal(bool hot); + void clearCal(); + + 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; + }; + + RadioAstronomy *m_radioAstronomy; + RadioAstronomySettings m_settings; + ChannelAPI *m_channel; + int m_channelSampleRate; + int m_channelFrequencyOffset; + + int m_fftSequence; + FFTEngine *m_fft; + FFTWindow m_fftWindow; + int m_fftCounter; + QList m_filterBins; + + Real *m_fftSum; + Real *m_fftTemp; + int m_fftSumCount; + + bool m_enabled; + bool m_cal; + bool m_hot; + + 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; + + void processOneSample(Complex &ci); + MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } +}; + +#endif // INCLUDE_RADIOASTRONOMYSINK_H diff --git a/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.cpp b/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.cpp new file mode 100644 index 000000000..7615d0107 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.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 "radioastronomy.h" +#include "radioastronomywebapiadapter.h" + +RadioAstronomyWebAPIAdapter::RadioAstronomyWebAPIAdapter() +{} + +RadioAstronomyWebAPIAdapter::~RadioAstronomyWebAPIAdapter() +{} + +int RadioAstronomyWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setRadioAstronomySettings(new SWGSDRangel::SWGRadioAstronomySettings()); + response.getRadioAstronomySettings()->init(); + RadioAstronomy::webapiFormatChannelSettings(response, m_settings); + + return 200; +} + +int RadioAstronomyWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; + (void) errorMessage; + RadioAstronomy::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response); + + return 200; +} diff --git a/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.h b/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.h new file mode 100644 index 000000000..7262b960c --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomywebapiadapter.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_RADIOASTRONOMY_WEBAPIADAPTER_H +#define INCLUDE_RADIOASTRONOMY_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "radioastronomysettings.h" + +/** + * Standalone API adapter only for the settings + */ +class RadioAstronomyWebAPIAdapter : public ChannelWebAPIAdapter { +public: + RadioAstronomyWebAPIAdapter(); + virtual ~RadioAstronomyWebAPIAdapter(); + + 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: + RadioAstronomySettings m_settings; +}; + +#endif // INCLUDE_RADIOASTRONOMY_WEBAPIADAPTER_H diff --git a/plugins/channelrx/radioastronomy/radioastronomyworker.cpp b/plugins/channelrx/radioastronomy/radioastronomyworker.cpp new file mode 100644 index 000000000..99d8a0776 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomyworker.cpp @@ -0,0 +1,160 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "radioastronomy.h" +#include "radioastronomyworker.h" + +MESSAGE_CLASS_DEFINITION(RadioAstronomyWorker::MsgConfigureRadioAstronomyWorker, Message) + +RadioAstronomyWorker::RadioAstronomyWorker(RadioAstronomy* radioAstronomy) : + m_radioAstronomy(radioAstronomy), + m_msgQueueToChannel(nullptr), + m_msgQueueToGUI(nullptr), + m_running(false), + m_mutex(QMutex::Recursive), + m_sensorTimer(this) +{ + connect(&m_sensorTimer, SIGNAL(timeout()), this, SLOT(measureSensors())); + m_sensorTimer.start((int)round(m_settings.m_sensorMeasurePeriod*1000.0)); + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) { + m_session[i] = VI_NULL; + } +} + +RadioAstronomyWorker::~RadioAstronomyWorker() +{ + m_inputMessageQueue.clear(); + m_visa.closeDefault(); +} + +void RadioAstronomyWorker::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); +} + +bool RadioAstronomyWorker::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; + return m_running; +} + +void RadioAstronomyWorker::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = false; +} + +void RadioAstronomyWorker::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool RadioAstronomyWorker::handleMessage(const Message& cmd) +{ + if (MsgConfigureRadioAstronomyWorker::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureRadioAstronomyWorker& cfg = (MsgConfigureRadioAstronomyWorker&) cmd; + + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else + { + return false; + } +} + +void RadioAstronomyWorker::applySettings(const RadioAstronomySettings& settings, bool force) +{ + qDebug() << "RadioAstronomyWorker::applySettings:" + << " m_sensorEnabled[0]: " << settings.m_sensorEnabled[0] + << " m_sensorDevice[0]: " << settings.m_sensorDevice[0] + << " m_sensorInit[0]: " << settings.m_sensorInit[0] + << " m_sensorMeasure[0]: " << settings.m_sensorMeasure[0] + << " force: " << force; + + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) + { + if ( (settings.m_sensorEnabled[i] != m_settings.m_sensorEnabled[i]) + || (settings.m_sensorEnabled[i] && (settings.m_sensorDevice[i] != m_settings.m_sensorDevice[i])) + || force) + { + if (!settings.m_sensorEnabled[i] && (m_session[i] != VI_NULL)) + { + m_visa.close(m_session[i]); + m_session[i] = VI_NULL; + } + if (settings.m_sensorEnabled[i] && !settings.m_sensorDevice[i].trimmed().isEmpty()) + { + m_visa.openDefault(); + m_session[i] = m_visa.open(settings.m_sensorDevice[i]); + } + } + if ( (settings.m_sensorEnabled[i] && !m_settings.m_sensorEnabled[i]) + || (settings.m_sensorEnabled[i] && (settings.m_sensorInit[i] != m_settings.m_sensorInit[i])) + || force) + { + if (m_session[i]) { + m_visa.processCommands(m_session[i], settings.m_sensorInit[i]); + } + } + } + if ((settings.m_sensorMeasurePeriod != m_settings.m_sensorMeasurePeriod) || force) { + m_sensorTimer.start((int)round(settings.m_sensorMeasurePeriod * 1000.0)); + } + + m_settings = settings; +} + +void RadioAstronomyWorker::measureSensors() +{ + for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) + { + if (m_settings.m_sensorEnabled[i] && m_session[i]) + { + QStringList results = m_visa.processCommands(m_session[i], m_settings.m_sensorMeasure[i]); + if (results.size() >= 1) + { + double value = results[0].toDouble(); + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(RadioAstronomy::MsgSensorMeasurement::create(i, value)); + } + } + else + { + qDebug() << "RadioAstronomyWorker::measureSensors: No result for command " << m_settings.m_sensorMeasure[i]; + } + } + } +} diff --git a/plugins/channelrx/radioastronomy/radioastronomyworker.h b/plugins/channelrx/radioastronomy/radioastronomyworker.h new file mode 100644 index 000000000..760210d62 --- /dev/null +++ b/plugins/channelrx/radioastronomy/radioastronomyworker.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_RADIOASTRONOMYWORKER_H +#define INCLUDE_RADIOASTRONOMYWORKER_H + +#include +#include + +#include "util/message.h" +#include "util/messagequeue.h" +#include "util/visa.h" + +#include "radioastronomysettings.h" + +class RadioAstronomy; + +class RadioAstronomyWorker : public QObject +{ + Q_OBJECT +public: + class MsgConfigureRadioAstronomyWorker : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const RadioAstronomySettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureRadioAstronomyWorker* create(const RadioAstronomySettings& settings, bool force) + { + return new MsgConfigureRadioAstronomyWorker(settings, force); + } + + private: + RadioAstronomySettings m_settings; + bool m_force; + + MsgConfigureRadioAstronomyWorker(const RadioAstronomySettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + RadioAstronomyWorker(RadioAstronomy* radioAstronomy); + ~RadioAstronomyWorker(); + void reset(); + bool startWork(); + void stopWork(); + bool isRunning() const { return m_running; } + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_msgQueueToChannel = messageQueue; } + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; } + +private: + + RadioAstronomy* m_radioAstronomy; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + MessageQueue *m_msgQueueToChannel; + MessageQueue *m_msgQueueToGUI; + RadioAstronomySettings m_settings; + bool m_running; + QMutex m_mutex; + + VISA m_visa; + ViSession m_session[RADIOASTRONOMY_SENSORS]; + QTimer m_sensorTimer; + + bool handleMessage(const Message& cmd); + void applySettings(const RadioAstronomySettings& settings, bool force = false); + MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; } + +private slots: + void handleInputMessages(); + void measureSensors(); +}; + +#endif // INCLUDE_RADIOASTRONOMYWORKER_H diff --git a/plugins/channelrx/radioastronomy/readme.md b/plugins/channelrx/radioastronomy/readme.md new file mode 100644 index 000000000..c8305aebb --- /dev/null +++ b/plugins/channelrx/radioastronomy/readme.md @@ -0,0 +1,864 @@ +

Radio Astronomy plugin

+ +

Introduction

+ +The Radio Astronomy plugin provides a number of tools to help make radio astronomy measurements. It supports: + +- A spectrometer for displaying time averaged spectra. +- A radiometer for displaying time averaged continuum measurements (total power). +- Calibration to enable measurements to be displayed as noise temperatures (K), power (dBm/Watts) and spectral flux density (Jy). +- Utilities are included for estimation and calculation of noise temperature components (Tsys, Trx, Tgal, Tatm, Tsky, Tsp) and sensitivity (sigma Tsys and sigma Sv). +- Spectra can be displayed against frequency and velocity (with a configurable reference spectral line), with the velocity adjusted to topocentric, Solar System barycentric or the Local Standard of Rest (LSR) reference frames. +- Calculation and plotting of radial and Galactocentric distance to HI clouds, based on spectral peaks. +- Position of HI clouds can be sent to Star Tracker plugin for visualisation on the Galactic line-of-sight image and created in to an animation mapping out the Milky Way's spiral arms. +- A Gaussian fitting tool in the spectrometer for HI cloud kinetic temperature and column density estimation. +- A Gaussian fitting tool in the radiometer to enable antenna HPBW measurement from Solar drift-scans. +- Ability to record and plot real-time surface air temperature and other sensor measurements (component voltages / temperatures) alongside radiometer measurements. +- Ability to export charts to animated .png files and static image files. +- Reference spectra from the LAB (Leiden/Argentine/Bonn) Galactic HI survey can be automatically downloaded and plotted for comparison against user measurements. +- 2D sweeps can be made and plotted in different coordinate systems (Az/El, Galactic, offsets around a target and drift scans). +- All spectra are held in memory and can be scrolled through. +- Data can be saved and loaded from .csv files. +- Hardware for calibration (E.g. RF switches) can be automatically controlled. + +![Radio Astronomy plugin GUI](../../../doc/img/RadioAstronomy_plugin.png) + +Several of the features in this plugin are tailored towards measurements of neutral hydrogen (HI) that is dispersed throughout the Milky Way's interstellar medium (ISM). +The HI in the ISM is a particularly interesting astronomical radiation source for SDR users, as the ground-state hyperfine transition of HI has a rest frequency of 1420.405MHz, +which is both quite powerful (due to the vast amount of HI spread throughout the Milky Way) and at a frequency that is easy to detect with relatively small dishes and low-cost LNAs and SDRs. +The HI spectrum can be used to determine some of the Milky Way's spiral structure and calculate rotation curves for the inner Milky Way, which suggest the presence of dark matter. + +In radio astronomy it is common to use noise temperatures rather than power, via the relation: + + T=P/(k*B) + +Where: + + T is the noise temperature in Kelvin + P is power in Watts + k is Boltzmann's constant + B is bandwidth in Hertz + +Similarly, for low frequencies (where the Rayleigh-Jeans approximation is valid), brightness temperatures are used rather than intensity: + + Tb=e*Iv*c^2/(2k*v^2) + +Where: + + Tb is the brightness temperature in Kelvin + e is the emissivity of the source + Iv is the intensity of the source (power per unit solid angle at the frequency v) + c is the speed of light + v is the frequency in Hertz + +This can be convenient, as if a large astronomical source completely fills the antenna beam with a uniform brightness temperature, +there will be an equal increase in the noise temperature measured by the receiver. +For thermal sources of radiation, the brightness temperature can also correspond directly to the physical temperature of the source. +This isn't true for non-thermal sources, however, such as synchrotron radiation. + +In this plugin, the following notation is used for the different temperature sources and combinations: + +- Trx - Receiver noise temperature. This is the combined noise temperature due to the LNA, feed line and SDR. +- Tcmb - Cosmic Microwave Background temperature (2.73K). +- Tgal - Galactic background temperature. An estimate of the frequency dependent background that is assumed to be the same in all directions. +- Tsky - Combined CMB, Galactic background and foreground as calculated in Star Tracker plugin using all-sky survey data. +- Tatm - Atmospheric emission, dependent upon frequency, opacity (which is dependent on temperature, pressure and water vapour) and elevation. +- Tsp - Spillover temperature due to thermal ground noise and other thermal noise sources such as trees and buildings around the antenna. +- Tair - Surface air temperature (In C, unlike all other temperatures which are in K). +- Tsource - Contribution from astronomical source (What we are typically trying to measure). +- Tsys0 - Total of all unwanted noise (Trx+Tcmb+Tgal+Tatm+Tsp). +- Tsys - System noise temperature. Sum of all received noise (Tsys0+Tsource). +- Tb - Brightness temperature of the source. +- Ta - Antenna temperature (which is typically Tcmb+Tgal+Tatm+Tsp+Tsource). + +Care should be taken when comparing to definitions in the literature, as these vary significantly. In particular, Tsys can be defined to include or +exclude the astronomical source contribution and Ta can be just the source or all antenna noise. + +For most astronomical observations, Tsource<Settings + +![Settings GUI](../../../doc/img/RadioAstronomy_Settings.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: SR - Sample Rate

+ +Sample rate in millions of samples per second. This determines the maximum bandwidth for the radiometer and spectrometer and thus the maximum Doppler shift range. +Typically this should be set to match SDRangel's baseband sample rate. + +

3: BW - RF Bandwidth

+ +This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. This can be used to eliminate RFI. + +

4: Channel power

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

5: Integration Count

+ +Specifies the number of FFTs that are summed in each average. Higher integration counts increase measurement time, but also increase sensitivity (up to the limit imposed by receiver gain variation). + +

6: Channels

+ +Specifies the number of channels (FFT bins). A larger number means an increased resolution in the spectrometer, +however, the sensitivity per bin is decreased for a fixed measurement time. + +

7: Window function

+ +A windowing function to be applied before the FFT, to compensate for spectral leakage. This can be either: + +- Rec: Rectangular or no windowing function. Use for broadband / continuum sources. +- Han: Hanning window. Use for narrowband / spectral line sources for better frequency resolution. + +

8: Filter

+ +Specifies a list of FFT bins that will have their values replaced with the minimum of other FFTs bins. This can be used to filter inband RFI. + +

9: Integration Time

+ +Displays in seconds or minutes how long it will take to make a single measurement. This is dependent on the sample rate (2), Integration Count (5) and number of channels (6). + +

10: Star Tracker

+ +Specifies the Star Tracker feature that determines the observation target. +A corresponding Star Tracker feature is required to calculate and display numerous values within the Radio Astronomy plugin and also for performing sweeps. + +

11: Rotator

+ +Specifies the rotator controller feature that is controlling the antenna used for measurements by the Radio Astronomy plugin. +This setting is used when sweeps are performed, to determine when rotation is complete and the antenna is pointing at the target. +If no rotator is used (and only drift scans are performed), this can be set to None. + +

12: Sensors

+ +Opens the Sensors dialog. + +The Sensors dialog allows two measurements such as voltage and temperature to be recorded and plotted along with radiometer data. +The sensor measurements are made using the VISA (Virtual Instruments Software Architecture) API, which is implemented in many +benchtop multimeters, oscilloscopes and spectrum analyzers. + +Sensor measurements can be used to monitor temperatures and voltages that might have an impact on measurements. For example, the gain +on the LNA will be dependent upon both, and gain variations limit the benefit of increased integration counts on sensitivity. + +![Sensors dialog](../../../doc/img/RadioAstronomy_SensorSettings.png) + +

13: Trx/NF

+ +Sets the receiver noise temperature in Kelvin or noise figure (NF) in dB. This is the noise temperature / figure for the combination of the LNA, feed line and SDR (at a specific gain setting). +The value set here can be measured using SDRangel's [Noise Figure plugin](https://github.com/f4exb/sdrangel/blob/master/plugins/channelrx/noisefigure/readme.md) +or estimated from datasheet values for the individual components using the [Friis formula](https://en.wikipedia.org/wiki/Friis_formulas_for_noise). + +

14: Tcmb

+ +Sets the contribution to noise temperature from the Cosmic Microwave Background (CMB). This is 2.73K when an antenna is pointed at the Sky, but may be set to 0K if measurements are made +where there is no CMB contribution (E.g. if the feedhorn is covered with absorbing foam or a 50Ohm terminator or other noise source is used for calibration). + +

15: Tgal

+ +Sets the contribution to the noise temperature from the Galactic background. This is frequency dependent and varies with direction. It does not include +the Galactic foreground (i.e. the increased noise temperature when looking in the Galactic plane). +If the link button to the right is unchecked, a value can be entered manually. +If the link button is checked, Tgal is calculated using: + + Tgal = 25.2 * (f/f0)^-2.75 + +Where: + + 25.2 is the 50th percentile of the all-sky distribution temperature. + f is the center frequency. + f0 is 408MHz. + +Tgal is used in calibration to estimate Tsp. + +

16: Tsp

+ +Sets spillover noise temperature. This is unwanted noise due to thermal ground radiation or other thermal radiation sources such as buildings and trees that can be picked +up via an antenna's side and back lobes. +It can be very dependent on azimuth and elevation in urban environments. +An estimate for Tsp can be made via the hot/cold calibration process. + +

17: Tatm

+ +Contribution to noise temperature due to atmospheric emission. Atmospheric emission is dependent upon frequency, opacity (which is dependent on air temperature, pressure and water vapour) and antenna elevation. +If the link button to the right is unchecked, a value can be entered manually. +If the link button is checked, Tatm is calculated using: + + Tatm = Tair * (1 - exp(-tau_z*sec(el))) + +Where: + + Tair is the surface air temperature from (18). + tau_z is the zenith opacity from (19). + el is the elevation of the antenna from (20) + +

18: Tair

+ +Tair specifies the surface air temperature at the antenna location in degrees Celsius. +If the link button to the right is unchecked, a value can be entered manually. +If the link button is checked, Tair is set to the air temperature value received from the Star Tracker plugin, which itself is periodically downloaded from openweathermap.org for the antenna's location. + +

19: tau_z - Zenith Opacity

+ +tau_z specifies the Zenith opacity. This value determines atmospheric absorption and emission. It is dependent upon air temperature, pressure and water vapour. + +The default value of 0.0055 roughly corresponds to clear air as per ITU-R P.372-14 figure 5 at 1.4GHz. + +

20: El - Antenna Elevation

+ +This specifies the antenna elevation in degrees. It is used for calculating atmospheric emission and absorption. +If the link button to the right is unchecked, a value can be entered manually. +If the link button is checked, El is automatically set to the elevation received from the Star Tracker plugin. + +

21: Tsys0

+ +This displays the value of Tsys0, the system noise temperature without an astronomical source, which is calculated as: + + Tsys0=Trx+Tcmb+Tgal+Tsp+Tatm. + +

22: sigma Tsys0

+ +This displays the value of sigma Tsys0, which is the standard deviation / RMS of Tsys0, and gives an indication of the sensitivity. It is calculated as: + + sigma_Tsys0 = Tsys0 * sqrt(1/(B*tau) + (deltaG/G)^2) + +

24: Baseline

+ +Specifies the baseline used for calculating Tsource from Tsys. This can be: + +- Tsys0 - Tsource = Tsys-Tsys0. +- Tmin - Tsource = Tsys-Tmin - where Tmin is the minimum in-band temperature. +- Tcold - Tsource = Tsys-Tcold - where Tcold is the cold calibration spectrum. This can be used for on/off source observations. + +

25: delta G / G - Gain Variation

+ +delta G / G specifies the gain variation of the LNA / receiver. Gain variation places a limit on the sensitvity improvement available by increased integration counts. +This value is only used for the estimation of sigma_Tsys0 and sigma_Ssys0, it does not affect any measurements. + +

26: HPBW / Omega A

+ +This displays the antenna half-power (-3dB) beamwidth (HPBW) in degrees or beam solid angle in steradians, as set in the Star Tracker plugins set by (10). + +

27: Omega S Type

+ +This sets the type of astronomical source, with respect to its and the antenna beam's angular size: + +- Unknown - Used for when the source is unknown. Brightness temperature (Tb) will not be able to be calculated. +- Compact - The source is smaller than the antenna beam. The source angle can be entered in (28). +- Extended - The source is larger than the antenna beam. +- Sun - The source is the Sun and the source angle is set to 0.53 degrees. +- CasA - The source is Cassiopeia A and the source is set to 0.08333 degrees. + +

28: Omega S

+ +Enter the angle subtended by the astronomical source. This can be a diameter entered in degrees or solid angle in steradians. +Values for many astronomical sources are available in the [SIMBAD Astronomical Database](http://simbad.u-strasbg.fr/simbad/). + +

29: Omega S Units

+ +Select whether Omega S is calculated from a diameter in degrees or solid angle in steradians. + +

Run Control

+ +![Run Control GUI](../../../doc/img/RadioAstronomy_RunControl.png) + +

Start / Stop

+ +Starts or stops measurements. + +The Radio Astronomy plugin has this button in addition to SDRangel's device acquisition Start/stop button, so that the +SDR continues to run when measurements are not being taken. This can help to reduce small gain & frequency variations that may occur in a SDR +when it is turned on from cold. + +

Clear Measurements

+ +Clears all measurements from memory. Calibration data is kept. + +

Run Mode

+ +The run mode field determines the number of measurements that are made then the Start button is pressed. + +- Single: A single measurement is made. +- Continuous: Measurements are continuously made until the Stop button is pressed. +- Sweep: Measurements are made at each of the coordinates specified by the sweep fields. + +

Start Time

+ +The start time field determines when the measurements start after the Start button is pressed: + +- Now: Measurements start immediately. +- At: Measurements start at the date and time specified in the date/time editor immediately below. + +Delaying the start of the measurements can be used to ensure that the target has risen to its maximum elevation. + +

Sweep Parameters

+ +The Sweep Type field determines the coordinates that are swept: + +- Az/El - Coordinates are swept in azimuth then elevation. +- l/b - Coordinates are swept in Galactic longitude then latitude. +- Offset - Sweep values are azimuth and elevation offsets from the center of the target set in Star Tracker. + +Here are some examples: + +Sweep Type: Az/El +Az Start: 100 +Az Stop: 120 +Az Step: 10 +El Start: 80 +El Stop: 85 +El Step: 5 +Will measure at (Az,El): 100,80 110,80, 120,80 100,85 110,85, 120,85 + +Sweep Type: Az/El +Az Start: 15 +Az Stop: 345 +Az Step: -15 +El Start: 85 +El Stop: 85 +El Step: 0 +Will measure at (Az,El): 15,85 0,85, 345,85 + +Sweep Type: l/b +l Start: -60 +l Stop: 60 +l Step: 20 +b Start: 0 +b Stop: 0 +b Step: 0 +Will measure at (l,b): 300,0 320,0 340,0 0,0 20,0 40,0, 60,0 + +Sweep Type: Offset +Az Start: -5 +Az Stop: 5 +Az Step: 5 +El Start: -5 +El Stop: 5 +El Step: 5 +Target in Star Tracker: Sun +Will measure at (Az,El): Sun-5,Sun-5 Sun,Sun-5 Sun+5,Sun-5 Sun-5,Sun Sun,Sun Sun+5,Sun Sun-5,Sun+5 Sun,Sun+5 Sun+5,Sun+5 + +The Settle field specifies a delay in seconds after antenna rotation has completed, before the measurement starts. + +The Delay field specifies a delay in seconds after a measurement has completed, before the antenna is rotated for the next measurement. + +

Status

+ +The measurement status bar shows how complete a measurement is in percent. + +

Spectrometer

+ +![Spectrometer GUI](../../../doc/img/RadioAstronomy_Spectrometer.png) + +

Spectrum Selection

+ +This combo box selects between the display of measurement spectra and calibration spectra. + +

Y Axis Units

+ +Selects the units for the Y-axis: + +- dBFS displays received power in dB relative to fullscale. +- SNR displays the signal to noise ratio (where the noise is the cold calibration spectrum). +- dBm displays the received power in dBm (requires hot calibration). +- TSys K displays the system noise temperature in Kelvin (requires hot calibration). +- TSource K displays the astronomical source temperature in Kelvin (requires hot calibration). + +

Display Legend

+ +Displays a legend at the side of the chart with the colour and name of each series. + +

Display LAB Reference Spectrum

+ +When checked, a reference spectrum from the Leiden/Argentine/Bonn (LAB) Galactic HI survey corresponding to the current Galactic coordinates +and antenna HPBW will be downloaded and displayed along side the measured spectrum. +This allows a comparison between your HI measurements and that from a professional survey. + +![LAB Reference Spectrum](../../../doc/img/RadioAstronomy_LAB.png) + +In order to reduce bandwidth to the server supplying this data, it is recommended to use this option sparingly. + +If the series does not appear on Windows (and you see "SSL handshake failed" in the log file), you may need to open https://www.astro.uni-bonn.de/hisurvey/euhou/index.php in your Web browser first, so that the certificate for the website is downloaded. + +

Calculate and plot distance to HI gas clouds

+ +When checked, the marker table will have six additional columns that display estimates of the distance to a HI cloud corresponding to the marker and the tangent point +along the line of sight. + +- Vr is radial velocity of the cloud in km/s, relative to the selected reference frame. +- R is the distance from the cloud to the Galactic centre in kiloparsecs (kpc). +- d is the line-of-sight distance to the cloud in kpc. In some instances there can be two possible solutions. +- Plot max determines whether the smaller or larger solution to d is sent to the Star Tracker plugin for display. +- Rmin is the minimum distance to the Galactic centre in kiloparsecs (kpc) along the line of sight (i.e. at the tangent point). +- Vmax is the orbital velocity at the tangent point. + +Vmax can be plotted against Rmin to determine the rotation curve of the Milky Way. +d can be plotted against Galactic longitude in Star Tracker to map out the Milky Way's spiral arms. + +The spectrometer GUI will also display two additional fields, R0 and V0, +which allow you to enter the distance from the Sun to the Galactic centre and the Sun's orbital velocity +around the Galactic centre, which are used in the above calculations. + +![Distance to HI Cloud](../../../doc/img/RadioAstronomy_RadioAstronomy_DistanceToHICloud.png) + +

Display Reference Spectral Line

+ +When checked, a horizontal axis showing Doppler shift in km/s is added to the top of the spectrometer chart and a vertical reference spectral line is plotted at 0km/s. +The rest frequency of the spectral line can be set via the reference spectral line field or manually entered. +The relationship between the frequency and velocity axes is determined by the selected reference frame. + +![Reference Spectral Line](../../../doc/img/RadioAstronomy_RefLine.png) + +

Display Gaussian Fitting Tools

+ +When checked, the Gaussian fitting tools are displayed. These allow a Gaussian to be fitted to a spectral peak for kinetic temperature and column density estimation. + +![Gaussian Fit](../../../doc/img/RadioAstronomy_SpectrumGaussian.png) + +

Display Markers

+ +When checked, the marker table is displayed and the user may place two markers (M1 and M2) on the chart for accurate display of the corresponding values. + +

Display Peaks

+ +When checked, the peak table is displayed and the peak Max marker is displayed at the maximum value in the spectrum. + +

Reverse X axis

+ +When checked, the X axis is reversed. This allows switching between an axis that increases with frequency (which is most common in engineering) or increases with velocity (which is most common in radio astronomy). + +

Save Charts to an Animation File

+ +Click to export all of the spectral measurements to a animated .png file. + +

Save Chart to an Image File

+ +Click to save the current chart to an image file. + +

Load Data from a .csv File

+ +Click to restore data that had been saved to a .csv file. All existing data will be cleared. + +

Save Data from a .csv File

+ +Click to save all data to a .csv file. + +

Autoscale

+ +When checked, continuously automatically scales both X and Y axis so all data is visible. When unchecked, the axis scales can be set manually. + +

Autoscale X

+ +When clicked, automatically scales the X axis so all data is visible. + +

Autoscale Y

+ +When clicked, automatically scales the Y axis so all data is visible. + +

Ref

+ +Sets the reference level (maximum value) of the Y axis. + +

Range

+ +Sets the range of the Y axis. + +

CF

+ +Sets the centre frequency of the X axis. + +

Span

+ +Sets the span (range) of the X axis. + +

Sel

+ +Selects what is selected when clicking on the chart: + +- M1 sets position of marker 1 +- M2 sets position of marker 2 +- Gaussian sets peak of Gaussian + +

Date & Time

+ +Allows the user to scroll through and select the recorded spectra, showing the date and time they were measured at. + +

Line

+ +Specifies the rest frequency of the reference spectral line: + +- HI neutral hydrogen at 1420.405760MHz. +- OH hydroxyl at 1612.231040Mhz. +- DI neutral deuterium at 327.384MHz. +- Custom allows a user-defined frequency in MHz to be entered. + +

Reference Frame

+ +Determines the reference frame used for calculating velocities from frequency. + +- Topo is a topocentric reference frame (i.e. relative to the observation location). +- BCRS is the barycentric celestial reference system (i.e. relative to the Solar System's barycenter (centre of mass)). +- LSR is the local standard of rest (i.e. relative to the local standard of rest, which accounts for the Sun's movements relative to other nearby stars). + +Professional astronomers tend to plot spectra using the LSR, so any observed Doppler shift can assumed to be due to the source moving. + +

R0

+ +Specifies the distance of the Sun from the Galactic centre in kpc. + +

V0

+ +Specifies the orbital velocity of the Sun around the Galactic centre in km/s. + +

f0

+ +Specifies the frequency of the centre of the Gaussian in MHz. + +

a

+ +Specifies the amplitude of the Gaussian. Units correspond to the Y axis units. + +

f

+ +Specifies the floor (minimum value of the Gaussian). Units correspond to the Y axis units. + +

Delta f FHWM

+ +Specifies the full-width at half maximum of the Gaussian in Hertz. + +

Tk

+ +An estimate of kinetic temperature in Kelvin of a HI cloud whose spectral profile matches the Gaussian. + +Note that it's not possible to determine how much spectral broadening is due to kinetic temperature and +how much is due to turbulent velocity, as from a single measurement, there is no way +to distinguish between the two. + +

Vt

+ +An estimate of the turbulent velocity within a HI cloud whose spectral profile matches the Gaussian. + +

NH

+ +Estimated column density of an optically thin HI cloud whose spectral profile matches the Gaussian, +measured in HI atoms per square centimetre. + +

Marker Table

+ +The marker table displays corresponding values for markers that are placed on the chart. + +

Radiometer

+ +![Radiometer GUI](../../../doc/img/RadioAstronomy_Radiometer.png) + +

Chart Selection

+ +This field selects between the display of power, temperature and flux in one or two dimensions. + +

Y Axis Units

+ +Selects the units for the Y-axis: + +- dBFS displays received power in dB relative to fullscale. +- dBm displays the received power in dBm (requires hot calibration). +- dBm displays the received power in Watts (requires hot calibration). +- K displays the temperature in Kelvin (requires hot calibration). +- SFU displays the flux in Solar Flux units (requires hot calibration). +- Jy displays the flux in Jansky (requires hot calibration). + +

Display Legend

+ +Displays a legend at the side of the chart with the colour and name of each series. + +

Plot Sensor 2

+ +Plot the data recorded for sensor 2 on the chart. + +

Plot Sensor 1

+ +Plot the data recorded for sensor 1 on the chart. + +

Plot Air Temperature

+ +Plot the surface air temperature data received from Star Tracker on the chart. + +

Plot Tsys0

+ +Plot Tsys0 on the chart. + +

Display Statistics

+ +Displays statistics calculated across all measurements (not just those visible on the chart), including the mean, RMS and standard deviation. + +

Display Gaussian Fitting Tools

+ +When checked, the Gaussian fitting tools are displayed. These allow a Gaussian to be fitted to the data, allowing measurement of the HPBW of the antenna. + +

Display Markers

+ +When checked, the marker table is displayed and the user may place two markers (M1 and M2) on the chart for accurate display of the corresponding values from the measurement series. + +

Display Peaks

+ +When checked, the marker table is displayed and the peak Max and Min markers are displayed at the maximum and minimum values on the measurement series. + +

Save Chart to an Image File

+ +Click to save the current chart to an image file. + +

Save Data to a .csv File

+ +Click to save data from the Radiometer Data table to a .csv file. + +

Autoscale

+ +When checked, continuously automatically scales both X and Y axis so all data is visible. When unchecked, the axis scales can be set manually. + +

Autoscale X

+ +When clicked, automatically scales the X axis so all data is visible. + +

Autoscale Y

+ +When clicked, automatically scales the Y axis so all data is visible. + +

Ref

+ +Sets the reference level (maximum value) of the Y axis. + +

Range

+ +Sets the range of the Y axis. + +

Start

+ +Sets the start time of the X axis. + +

End

+ +Sets the end time of the X axis. + +

Sel

+ +Selects what is selected when clicking on the chart: + +- Row highlights the corresponding row in the Radiometer Data table to the point clicked. +- M1 sets position of marker 1 +- M2 sets position of marker 2 +- Gaussian sets peak of Gaussian + +

Center

+ +Specifies the date and time of the center of the Gaussian. + +

a

+ +Specifies the amplitude of the Gaussian. Units correspond to the Y axis units. + +

f

+ +Specifies the floor (minimum value of the Gaussian). Units correspond to the Y axis units. + +

Delta t FHWM

+ +Specifies the full-width at half maximum of the Gaussian in seconds. + +

HPBW

+ +An estimate of the HPBW in degrees of an antenna whose main lobe corresponds to the Gaussian profile of a drift scan of the Sun, using a linear scale (E.g. Y axis must not be in not dB). + +![Radiometer Gaussian Fit](../../../doc/img/RadioAstronomy_RadiometerGaussian.png) + +

Marker Table

+ +The marker table displays corresponding values for markers that are placed on the chart. + +

Radiometer 2D Map

+ +![Radiometer 2D Map](../../../doc/img/RadioAstronomy_Radiometer2D.png) + +

Link Sweep

+ +When checked, the parameters for the 2D Map will be automatically updated based on the Run Control sweep parameters. + +

Sweep Type

+ +Sets the coordinates used for the axes of the map. + +

Width

+ +Width in pixels of the map. Typically there should be one pixel per measurement. + +

Height

+ +Height in pixels of the map. + +

X Range

+ +Specifies the range of the 2D map's horizontal axis. This determines how measurements map to pixels. + +

Y Range

+ +Specifies the range of the 2D map's vertical axis. + +

Autoscale

+ +Automatically scales the X and Y axes to fit the Radiometer data in the table. + +

Colour Autoscale

+ +Automatically scales the colour palette to the range of existing values in the Radiometer data table. + +

Min

+ +Specifies the value that maps to the first colour in the palette. All values lower than this will be clipped to the first colour. + +

Max

+ +Specifies the value that maps to the last colour in the palette. All values higher than this will be clipped to the last colour. + +

Palette

+ +Specifies the palette / gradient used to plot the 2D map. This can either be colour or greyscale. The gradient is applied linearly between the Min and Max values. + +

Radiometer Data

+ +The Radiometer Data table shows measurement results and settings at the time of measurement in tabular form. + +![Radiometer Data GUI](../../../doc/img/RadioAstronomy_RadiometerData.png) + +The columns in the table include: + +- Date - Date at the end of the measurement. +- Time - Time at the end of the measurement. +- Power (FFT) - Power relative to fullscale (sum of FFT absolute magnitude). +- Power (dBFS) - Power in dBFS. +- Power (dBm) - Power in dBm. +- Tsys (K) - System noise temperature in Kelvin. +- Tsys0 (K) - System noise temperature (excluding Tsource) in Kelvin. +- Tsource (K) - Source noise temperature in Kelvin. +- Tb (K) - Source brightness temperature in Kelvin. +- Tsky (K) - Sky temperature in Kelvin towards the target from Star Tracker. +- Sv (Jy) - Spectral flux density in Jansky. +- sigmaTsys (K) - Standard deviation of Tsys in Kelvin. +- sigmaSsys (Jy) - Standard deviation of Sv in Jansky. +- omegaA (sr) - Antenna beam solid angle. +- omegaS (sr) - Source solid angle. +- RA - Right ascension of target from Star Tracker. +- Dec - Declination of target from Star Tracker. +- l - Galactic longitude of target from Star Tracker. +- b - Galactic latitude of target from Star Tracker. +- Az - Azimuth of target from Star Tracker. +- El - Elevation of target from Star Tracker. +- Vbcrs - Observer velocity relative to barycentric celestial reference system (BCRS). +- Vlsr - Observer velocity relative to local standard of rest (LSR). +- Solar Flux (jy) - Solar flux from Star Tracker. +- Air Temp (C) - Surface air temperature at observation point in Celsius from Star Tracker. +- Sensor 1 - Data recorded for Sensor 1. +- Sensor 2 - Data recorded for Sensor 2. + +Right clicking on the table shows a popup menu that supports: + +- Copying the value in the cell to the clipboard +- Deleting the selected rows +- Applying the current values of Tsys0, baseline and omega S to recalculate Tsource, Tb and Sv. + +

Calibration

+ +Power measurements in SDRs are typically relative (E.g. dBFS) rather than absolute (E.g. dBm). In order to produce absolute power measurements, +and thus noise temperature measurements, we need to perform a calibration process that calculates a mapping from the relative power value to an absolute value. +Also, there are multiple unwanted noise sources that contribute to the measured power (LNA and receiver noise, for example), +that we wish to subtract from our power measurement, to get a measurement of the power of the radiation received from the astronomical object we are observing. + +The first step is to measure the noise of the receiver, Trx. This is the combined noise of the LNA, feed line and SDR, for a particular gain setting. +This can be measured with a calibrated noise source connected to the LNA input using SDRangel's [Noise Figure plugin](https://github.com/f4exb/sdrangel/blob/master/plugins/channelrx/noisefigure/readme.md), +or estimated from datasheet values for the individual components using the [Friis formula](https://en.wikipedia.org/wiki/Friis_formulas_for_noise). +It is also possible to calculate this within the Radio Astronomy plugin by running a hot and cold calibration. The plugin will then use the Y factor method +to estimate Trx, and this will be displayed in the Trx field, below the chart. Whatever method is used, the value should be entered in to the Trx field in the Settings area. + +In order to map relative powers to absolute powers (and temperatures), a hot calibration should be run. To run a hot calibration, the noise +temperature of the calibration source is entered in to the Thot field (or power into Phot) and then press the "Start hot calibration" button. (The process +is likewise to run a cold calibration). The main consideration for a user, is what can be used as a calibration source and how is it connected to the antenna/receiver. +There are two ways, with and without an antenna: + +For parabolic dishes or horn antennas, an object at a known temperature can be used, so long as it completely covers the feed horn aperture. +The object needs to be as close to an ideal blackbody as possible, with high emissivity at the frequencies of interest, so that the temperature +of the object results in an identical increase in noise temperature in the antenna. If the dish is steerable to point towards the ground, the temperature +of the ground may be used. + +It is also possible to calibrate by directly connecting a noise source to the LNA input. This could be as simple as 50Ohm termination resistor, +which should result in a noise temperature corresponding to the physical temperature of the resistor, assuming good impedance matching and very low insertion loss. + +One large unknown can be the spillover temperature, Tsp. This is the noise contribution due to ground or building thermal radiation leaking in +to the feed horn from the back or side lobes. Once Trx is known, is possible to estimate Tsp by performing a hot and cold calibration, +where the hot calibration uses an object blocking the feed, but the cold calibration has the feed unblocked pointing to a cold part of the sky. +The temperature of the cold sky can be estimated from an all-sky survey in Star Tracker, and this is displayed under the calibration chart as Tsky. +If Thot is measured with Tsp=0, Tcold is Tsky, and Trx is known, then the plugin can estimate Tsp for the cold measurement. Note that Tsp is typically strongly +dependent on the antenna's elevation and azimuth, as this changes the amount of ground thermal radiation that gets in to the antenna. + +![Calibration Settings dialog](../../../doc/img/RadioAstronomy_Calibration.png) + +

Show Calibration Settings Dialog

+ +When clicked, shows the Calibration Settings dialog. + +The Calibration Settings dialog allows a user to control hardware used for calibration. It supports two methods: GPIO pins in a SDR can be toggled during calibration and/or +commands/scripts can be run before and after calibration. The pre-calibration delay setting specifies a delay in seconds between the GPIO being toggled or start command +being executed, before the calibration routine in the plugin starts. + +An example of its use would be to electronically switch in a 50Ohm resistor to the LNA input when calibration is run, using one of the SDR's GPIO pins to control the RF switch. + +![Calibration Settings dialog](../../../doc/img/RadioAstronomy_CalibrationSettings.png) + +

Clear Calibration Data

+ +Clears all calibration data. + +

Start Hot Calibration

+ +Starts a measurement that will be used as the hot calibration data. + +

Start Cold Calibration

+ +Starts a measurement that will be used as the cold calibration data. + +

Recalibrate All Measurements

+ +When checked, results of a new calibration will be applied to all existing measurements. When unchecked, the calibration will only apply to new measurements. + +

API

+ +Full details of the API can be found in the Swagger documentation. Here is a quick example of how to start a measurement from the command line: + + curl -X POST "http://127.0.0.1:8091/sdrangel/deviceset/0/channel/0/actions" -d '{"channelType": "RadioAstronomy", "direction": 0, "RadioAstronomyActions": { "start": {"sampleRate": 2000000} }}' + +Or to set the sample rate: + + curl -X PATCH "http://127.0.0.1:8091/sdrangel/deviceset/0/channel/0/settings" -d '{"channelType": "RadioAstronomy", "direction": 0, "RadioAstronomySettings": {"sampleRate": 2000000}}' + +

Attribution

+ +Many equations are from Essential Radio Astronomy by James Condon and Scott Ransom: https://www.cv.nrao.edu/~sransom/web/xxx.html + +The Leiden/Argentine/Bonn (LAB) Survey of Galactic HI: https://arxiv.org/abs/astro-ph/0504140 and EU-HOU project: https://www.astro.uni-bonn.de/hisurvey/euhou/index.php + +Thermometer icons are by Freepik from https://www.flaticon.com/ + +Reverse icon by Creaticca Creative Agency from https://www.flaticon.com/ diff --git a/plugins/feature/startracker/startracker.cpp b/plugins/feature/startracker/startracker.cpp index b8cd38a47..908d810c8 100644 --- a/plugins/feature/startracker/startracker.cpp +++ b/plugins/feature/startracker/startracker.cpp @@ -662,13 +662,13 @@ void StarTracker::networkManagerFinished(QNetworkReply *reply) void StarTracker::weatherUpdated(float temperature, float pressure, float humidity) { - if (!isnan(temperature)) { + if (!std::isnan(temperature)) { m_settings.m_temperature = temperature; } - if (!isnan(pressure)) { + if (!std::isnan(pressure)) { m_settings.m_pressure = pressure; } - if (!isnan(humidity)) { + if (!std::isnan(humidity)) { m_settings.m_humidity = humidity; } diff --git a/sdrbase/channel/channelwebapiutils.cpp b/sdrbase/channel/channelwebapiutils.cpp index 9f27394a4..53bbb0557 100644 --- a/sdrbase/channel/channelwebapiutils.cpp +++ b/sdrbase/channel/channelwebapiutils.cpp @@ -27,6 +27,8 @@ #include "SWGDeviceSet.h" #include "SWGChannelActions.h" #include "SWGFileSinkActions.h" +#include "SWGFeatureSettings.h" +#include "SWGFeatureReport.h" #include "maincore.h" #include "device/deviceset.h" @@ -37,14 +39,13 @@ #include "dsp/devicesamplemimo.h" #include "webapi/webapiadapterinterface.h" #include "webapi/webapiutils.h" +#include "feature/featureset.h" +#include "feature/feature.h" -// Get device center frequency -bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz) +bool ChannelWebAPIUtils::getDeviceSettings(unsigned int deviceIndex, SWGSDRangel::SWGDeviceSettings &deviceSettingsResponse, DeviceSet *&deviceSet) { - SWGSDRangel::SWGDeviceSettings deviceSettingsResponse; QString errorResponse; int httpRC; - DeviceSet *deviceSet; // Get current device settings std::vector deviceSets = MainCore::instance()->getDeviceSets(); @@ -74,114 +75,129 @@ bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &fr } else { - qDebug() << "ChannelWebAPIUtils::getCenterFrequency - not a sample source device " << deviceIndex; + qDebug() << "ChannelWebAPIUtils::getDeviceSettings - not a sample source device " << deviceIndex; return false; } } else { - qDebug() << "ChannelWebAPIUtils::getCenterFrequency - no device " << deviceIndex; + qDebug() << "ChannelWebAPIUtils::getDeviceSettings - no device " << deviceIndex; return false; } if (httpRC/100 != 2) { - qWarning("ChannelWebAPIUtils::getCenterFrequency: get device frequency error %d: %s", + qWarning("ChannelWebAPIUtils::getDeviceSettings: get device settings error %d: %s", httpRC, qPrintable(errorResponse)); return false; } - QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); - return WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz); + return true; +} + +bool ChannelWebAPIUtils::getFeatureSettings(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureSettings &featureSettingsResponse, Feature *&feature) +{ + QString errorResponse; + int httpRC; + FeatureSet *featureSet; + + // Get current feature settings + std::vector featureSets = MainCore::instance()->getFeatureeSets(); + if (featureSetIndex < featureSets.size()) + { + featureSet = featureSets[featureSetIndex]; + if (featureIndex < featureSet->getNumberOfFeatures()) + { + feature = featureSet->getFeatureAt(featureIndex); + httpRC = feature->webapiSettingsGet(featureSettingsResponse, errorResponse); + } + else + { + qDebug() << "ChannelWebAPIUtils::getFeatureSettings: no feature " << featureSetIndex << ":" << featureIndex; + return false; + } + } + else + { + qDebug() << "ChannelWebAPIUtils::getFeatureSettings: no feature set " << featureSetIndex; + return false; + } + + if (httpRC/100 != 2) + { + qWarning("ChannelWebAPIUtils::getFeatureSettings: get feature settings error %d: %s", + httpRC, qPrintable(errorResponse)); + return false; + } + + return true; +} + + +// Get device center frequency +bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz) +{ + SWGSDRangel::SWGDeviceSettings deviceSettingsResponse; + DeviceSet *deviceSet; + + if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet)) + { + QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); + return WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz); + } + else + { + return false; + } } // Set device center frequency bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double frequencyInHz) { SWGSDRangel::SWGDeviceSettings deviceSettingsResponse; - QString errorResponse; int httpRC; DeviceSet *deviceSet; - // Get current device settings - std::vector deviceSets = MainCore::instance()->getDeviceSets(); - if (deviceIndex < deviceSets.size()) + if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet)) { - deviceSet = deviceSets[deviceIndex]; - if (deviceSet->m_deviceSourceEngine) + // Patch centerFrequency + QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); + double freq; + if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", freq)) { - deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId())); - deviceSettingsResponse.setDirection(0); + WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz); + QStringList deviceSettingsKeys; + deviceSettingsKeys.append("centerFrequency"); + deviceSettingsResponse.init(); + deviceSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource(); - httpRC = source->webapiSettingsGet(deviceSettingsResponse, errorResponse); - } - else if (deviceSet->m_deviceSinkEngine) - { - deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId())); - deviceSettingsResponse.setDirection(1); - DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink(); - httpRC = sink->webapiSettingsGet(deviceSettingsResponse, errorResponse); - } - else if (deviceSet->m_deviceMIMOEngine) - { - deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId())); - deviceSettingsResponse.setDirection(2); - DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO(); - httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, errorResponse); + + httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::setCenterFrequency: set device frequency %f OK", frequencyInHz); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::setCenterFrequency: set device frequency error %d: %s", + httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } } else { - qDebug() << "ChannelWebAPIUtils::setCenterFrequency: not a sample source device " << deviceIndex; + qWarning("ChannelWebAPIUtils::setCenterFrequency: no centerFrequency key in device settings"); return false; } } else { - qDebug() << "ChannelWebAPIUtils::setCenterFrequency: no device " << deviceIndex; return false; } - - if (httpRC/100 != 2) - { - qWarning("ChannelWebAPIUtils::setCenterFrequency: get device frequency error %d: %s", - httpRC, qPrintable(errorResponse)); - return false; - } - - // Patch centerFrequency - QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); - double freq; - if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", freq)) - { - WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz); - QStringList deviceSettingsKeys; - deviceSettingsKeys.append("centerFrequency"); - deviceSettingsResponse.init(); - deviceSettingsResponse.fromJsonObject(*jsonObj); - SWGSDRangel::SWGErrorResponse errorResponse2; - - DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource(); - - httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage()); - - if (httpRC/100 == 2) - { - qDebug("ChannelWebAPIUtils::setCenterFrequency: set device frequency %f OK", frequencyInHz); - } - else - { - qWarning("ChannelWebAPIUtils::setCenterFrequency: set device frequency error %d: %s", - httpRC, qPrintable(*errorResponse2.getMessage())); - return false; - } - } - else - { - qWarning("ChannelWebAPIUtils::setCenterFrequency: no centerFrequency key in device settings"); - return false; - } - - return true; } // Start acquisition @@ -462,3 +478,214 @@ bool ChannelWebAPIUtils::satelliteLOS(const QString name) } return true; } + +bool ChannelWebAPIUtils::getDeviceSetting(unsigned int deviceIndex, const QString &setting, int &value) +{ + SWGSDRangel::SWGDeviceSettings deviceSettingsResponse; + DeviceSet *deviceSet; + + if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet)) + { + QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); + return WebAPIUtils::getSubObjectInt(*jsonObj, setting, value); + } + else + { + return false; + } +} + +bool ChannelWebAPIUtils::patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value) +{ + SWGSDRangel::SWGDeviceSettings deviceSettingsResponse; + QString errorResponse; + int httpRC; + DeviceSet *deviceSet; + + if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet)) + { + // Patch centerFrequency + QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject(); + int oldValue; + if (WebAPIUtils::getSubObjectInt(*jsonObj, setting, oldValue)) + { + WebAPIUtils::setSubObjectInt(*jsonObj, setting, value); + QStringList deviceSettingsKeys; + deviceSettingsKeys.append(setting); + deviceSettingsResponse.init(); + deviceSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + + DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource(); + + httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::patchDeviceSetting: set device setting %s OK", setting); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::patchDeviceSetting: set device setting error %d: %s", + httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } + } + else + { + qWarning("ChannelWebAPIUtils::patchDeviceSetting: no key %s in device settings", setting); + return false; + } + } + else + { + return false; + } +} + +// Set feature setting +bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value) +{ + SWGSDRangel::SWGFeatureSettings featureSettingsResponse; + int httpRC; + Feature *feature; + + if (getFeatureSettings(featureSetIndex, featureIndex, featureSettingsResponse, feature)) + { + // Patch settings + QJsonObject *jsonObj = featureSettingsResponse.asJsonObject(); + QString oldValue; + if (WebAPIUtils::getSubObjectString(*jsonObj, setting, oldValue)) + { + WebAPIUtils::setSubObjectString(*jsonObj, setting, value); + QStringList featureSettingsKeys; + featureSettingsKeys.append(setting); + featureSettingsResponse.init(); + featureSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + + httpRC = feature->webapiSettingsPutPatch(false, featureSettingsKeys, featureSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %s OK", setting, value); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %s error %d: %s", + setting, value, httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } + } + else + { + qWarning("ChannelWebAPIUtils::patchFeatureSetting: no key %s in feature settings", setting); + return false; + } + } + else + { + return false; + } +} + +bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value) +{ + SWGSDRangel::SWGFeatureSettings featureSettingsResponse; + QString errorResponse; + int httpRC; + FeatureSet *featureSet; + Feature *feature; + + if (getFeatureSettings(featureSetIndex, featureIndex, featureSettingsResponse, feature)) + { + // Patch settings + QJsonObject *jsonObj = featureSettingsResponse.asJsonObject(); + double oldValue; + if (WebAPIUtils::getSubObjectDouble(*jsonObj, setting, oldValue)) + { + WebAPIUtils::setSubObjectDouble(*jsonObj, setting, value); + QStringList featureSettingsKeys; + featureSettingsKeys.append(setting); + featureSettingsResponse.init(); + featureSettingsResponse.fromJsonObject(*jsonObj); + SWGSDRangel::SWGErrorResponse errorResponse2; + + httpRC = feature->webapiSettingsPutPatch(false, featureSettingsKeys, featureSettingsResponse, *errorResponse2.getMessage()); + + if (httpRC/100 == 2) + { + qDebug("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %f OK", setting, value); + return true; + } + else + { + qWarning("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %s error %d: %s", + setting, value, httpRC, qPrintable(*errorResponse2.getMessage())); + return false; + } + } + else + { + qWarning("ChannelWebAPIUtils::patchFeatureSetting: no key %s in feature settings", setting); + return false; + } + } + else + { + return false; + } +} + +bool ChannelWebAPIUtils::getFeatureReportValue(unsigned int featureSetIndex, unsigned int featureIndex, const QString &key, int &value) +{ + SWGSDRangel::SWGFeatureReport featureReport; + QString errorResponse; + int httpRC; + FeatureSet *featureSet; + Feature *feature; + + // Get feature report + std::vector featureSets = MainCore::instance()->getFeatureeSets(); + if (featureSetIndex < featureSets.size()) + { + featureSet = featureSets[featureSetIndex]; + if (featureIndex < featureSet->getNumberOfFeatures()) + { + feature = featureSet->getFeatureAt(featureIndex); + httpRC = feature->webapiReportGet(featureReport, errorResponse); + } + else + { + qDebug() << "ChannelWebAPIUtils::getFeatureReportValue: no feature " << featureSetIndex << ":" << featureIndex; + return false; + } + } + else + { + qDebug() << "ChannelWebAPIUtils::getFeatureReportValue: no feature set " << featureSetIndex; + return false; + } + + if (httpRC/100 != 2) + { + qWarning("ChannelWebAPIUtils::getFeatureReportValue: get feature report error %d: %s", + httpRC, qPrintable(errorResponse)); + return false; + } + + // Get value of requested key + QJsonObject *jsonObj = featureReport.asJsonObject(); + if (WebAPIUtils::getSubObjectInt(*jsonObj, key, value)) + { + // Done + return true; + } + else + { + qWarning("ChannelWebAPIUtils::getFeatureReportValue: no key %s in feature report", key); + return false; + } +} diff --git a/sdrbase/channel/channelwebapiutils.h b/sdrbase/channel/channelwebapiutils.h index 0550ed34e..50bbe0cbe 100644 --- a/sdrbase/channel/channelwebapiutils.h +++ b/sdrbase/channel/channelwebapiutils.h @@ -20,8 +20,14 @@ #include +#include "SWGDeviceSettings.h" +#include "SWGFeatureSettings.h" + #include "export.h" +class DeviceSet; +class Feature; + class SDRBASE_API ChannelWebAPIUtils { public: @@ -34,6 +40,14 @@ public: static bool startStopFileSinks(unsigned int deviceIndex, bool start); static bool satelliteAOS(const QString name, bool northToSouthPass); static bool satelliteLOS(const QString name); + static bool getDeviceSetting(unsigned int deviceIndex, const QString &setting, int &value); + static bool patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value); + static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value); + static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value); + static bool getFeatureReportValue(unsigned int featureSetIndex, unsigned int featureIndex, const QString &key, int &value); +protected: + static bool getDeviceSettings(unsigned int deviceIndex, SWGSDRangel::SWGDeviceSettings &deviceSettingsResponse, DeviceSet *&deviceSet); + static bool getFeatureSettings(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureSettings &featureSettingsResponse, Feature *&feature); }; #endif // SDRBASE_CHANNEL_CHANNELWEBAPIUTILS_H_ diff --git a/sdrbase/util/units.h b/sdrbase/util/units.h index 9a17d0699..d82a1a0b7 100644 --- a/sdrbase/util/units.h +++ b/sdrbase/util/units.h @@ -298,6 +298,30 @@ public: return sfu * 1e-22f; } + template + static T wattsPerMetrePerHertzToSolarFluxUnits(T w) + { + return w / 1e-22f; + } + + template + static T wattsPerMetrePerHertzToJansky(T w) + { + return w / 1e-26f; + } + + template + static T noiseFigureToNoiseTemp(T nfdB, T refTempK=T(290.0)) + { + return refTempK * (std::pow(T(10.0), nfdB/T(10.0)) - T(1.0)); + } + + template + static T noiseTempToNoiseFigureTo(T tempK, T refTempK=T(290.0)) + { + return T(10.0) * std::log10(tempK/refTempK+T(1.0)); + } + }; #endif // INCLUDE_UNITS_H diff --git a/sdrbase/util/visa.cpp b/sdrbase/util/visa.cpp index eee60b1c5..6cc01935d 100644 --- a/sdrbase/util/visa.cpp +++ b/sdrbase/util/visa.cpp @@ -106,6 +106,31 @@ void VISA::close(ViSession session) } } +QStringList VISA::processCommands(ViSession session, const QString& commands) +{ + QStringList list = commands.split("\n"); + QStringList results; + for (int i = 0; i < list.size(); i++) + { + QString command = list[i].trimmed(); + if (!command.isEmpty() && !command.startsWith("#")) // Allow # to comment out lines + { + qDebug() << "VISA ->: " << command; + QByteArray bytes = QString("%1\n").arg(command).toLatin1(); + char *cmd = bytes.data(); + viPrintf(session, cmd); + if (command.endsWith("?")) + { + char buf[1024] = ""; + char format[] = "%t"; + viScanf(session, format, buf); + results.append(buf); + qDebug() << "VISA <-: " << QString(buf).trimmed(); + } + } + } + return results; +} #ifdef _MSC_VER diff --git a/sdrbase/util/visa.h b/sdrbase/util/visa.h index 153a60de9..b8f60cfae 100644 --- a/sdrbase/util/visa.h +++ b/sdrbase/util/visa.h @@ -61,6 +61,7 @@ public: void closeDefault(); ViSession open(const QString& device); void close(ViSession session); + QStringList processCommands(ViSession session, const QString& commands); // Is the VISA library available bool isAvailable() const diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 5bd9274eb..bf5d093e6 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -4305,6 +4305,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings()); channelSettings->getPagerDemodSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "RadioAstronomySettings") + { + channelSettings->setRadioAstronomySettings(new SWGSDRangel::SWGRadioAstronomySettings()); + channelSettings->getRadioAstronomySettings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "RadioClockSettings") { channelSettings->setRadioClockSettings(new SWGSDRangel::SWGRadioClockSettings()); @@ -4412,6 +4417,11 @@ bool WebAPIRequestMapper::getChannelActions( channelActions->setIeee802154ModActions(new SWGSDRangel::SWGIEEE_802_15_4_ModActions()); channelActions->getIeee802154ModActions()->fromJsonObject(actionsJsonObject); } + else if (channelActionsKey == "RadioAstronomyActions") + { + channelActions->setRadioAstronomyActions(new SWGSDRangel::SWGRadioAstronomyActions()); + channelActions->getRadioAstronomyActions()->fromJsonObject(actionsJsonObject); + } else if (channelActionsKey == "PacketModActions") { channelActions->setPacketModActions(new SWGSDRangel::SWGPacketModActions()); @@ -5004,6 +5014,8 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setPacketDemodSettings(nullptr); channelSettings.setPacketModSettings(nullptr); channelSettings.setPagerDemodSettings(nullptr); + channelSettings.setRadioAstronomySettings(nullptr); + channelSettings.setRadioClockSettings(nullptr); channelSettings.setRemoteSinkSettings(nullptr); channelSettings.setRemoteSourceSettings(nullptr); channelSettings.setSsbDemodSettings(nullptr); @@ -5033,6 +5045,8 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setNoiseFigureReport(nullptr); channelReport.setIeee802154ModReport(nullptr); channelReport.setPacketModReport(nullptr); + channelReport.setRadioAstronomyReport(nullptr); + channelReport.setRadioClockReport(nullptr); channelReport.setRemoteSourceReport(nullptr); channelReport.setSsbDemodReport(nullptr); channelReport.setSsbModReport(nullptr); @@ -5051,6 +5065,7 @@ void WebAPIRequestMapper::resetChannelActions(SWGSDRangel::SWGChannelActions& ch channelActions.setChannelType(nullptr); channelActions.setFileSourceActions(nullptr); channelActions.setIeee802154ModActions(nullptr); + channelActions.setRadioAstronomyActions(nullptr); channelActions.setPacketModActions(nullptr); } diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index bef32be37..f05f84e8b 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -25,7 +25,6 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channel.adsbdemod", "ADSBDemodSettings"}, {"sdrangel.channel.modais", "AISModSettings"}, {"sdrangel.channel.aisdemod", "AISDemodSettings"}, - {"sdrangel.channel.aptdemod", "APTDemodSettings"}, {"sdrangel.channel.amdemod", "AMDemodSettings"}, {"sdrangel.channel.aptdemod", "APTDemodSettings"}, {"de.maintech.sdrangelove.channel.am", "AMDemodSettings"}, // remap @@ -64,6 +63,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channeltx.modssb", "SSBModSettings"}, {"sdrangel.channel.ssbdemod", "SSBDemodSettings"}, {"de.maintech.sdrangelove.channel.ssb", "SSBDemodSettings"}, // remap + {"sdrangel.channel.radioastronomy", "RadioAstronomySettings"}, {"sdrangel.channeltx.udpsource", "UDPSourceSettings"}, {"sdrangel.channeltx.udpsink", "UDPSinkSettings"}, // remap {"sdrangel.channel.udpsink", "UDPSinkSettings"}, @@ -157,6 +157,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"PagerDemod", "PagerDemodSettings"}, {"LocalSink", "LocalSinkSettings"}, {"LocalSource", "LocalSourceSettings"}, + {"RadioAstronomy", "RadioAstronomySettings"}, {"RadioClock", "RadioClockSettings"}, {"RemoteSink", "RemoteSinkSettings"}, {"RemoteSource", "RemoteSourceSettings"}, @@ -179,6 +180,7 @@ const QMap WebAPIUtils::m_channelTypeToActionsKey = { {"FileSource", "FileSourceActions"}, {"SigMFFileSink", "SigMFFileSinkActions"}, {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModActions"}, + {"RadioAstronomy", "RadioAstronomyActions"}, {"PacketMod", "PacketModActions"} }; @@ -431,6 +433,51 @@ bool WebAPIUtils::setSubObjectInt(QJsonObject &json, const QString &key, int val return false; } +// Get string value from within nested JSON object +bool WebAPIUtils::getSubObjectString(const QJsonObject &json, const QString &key, QString &value) +{ + for (QJsonObject::const_iterator it = json.begin(); it != json.end(); it++) + { + QJsonValue jsonValue = it.value(); + + if (jsonValue.isObject()) + { + QJsonObject subObject = jsonValue.toObject(); + + if (subObject.contains(key)) + { + value = subObject[key].toString(); + return true; + } + } + } + + return false; +} + +// Set string value withing nested JSON object +bool WebAPIUtils::setSubObjectString(QJsonObject &json, const QString &key, const QString &value) +{ + for (QJsonObject::iterator it = json.begin(); it != json.end(); it++) + { + QJsonValue jsonValue = it.value(); + + if (jsonValue.isObject()) + { + QJsonObject subObject = jsonValue.toObject(); + + if (subObject.contains(key)) + { + subObject[key] = value; + it.value() = subObject; + return true; + } + } + } + + return false; +} + // look for value in key=value bool WebAPIUtils::extractValue(const QJsonObject &json, const QString &key, QJsonValue &value) { diff --git a/sdrbase/webapi/webapiutils.h b/sdrbase/webapi/webapiutils.h index 2c6c28714..a9b511123 100644 --- a/sdrbase/webapi/webapiutils.h +++ b/sdrbase/webapi/webapiutils.h @@ -49,6 +49,8 @@ public: static bool setSubObjectDouble(QJsonObject &json, const QString &key, double value); static bool getSubObjectInt(const QJsonObject &json, const QString &key, int &value); static bool setSubObjectInt(QJsonObject &json, const QString &key, int value); + static bool getSubObjectString(const QJsonObject &json, const QString &key, QString &value); + static bool setSubObjectString(QJsonObject &json, const QString &key, const QString &value); static bool extractValue(const QJsonObject &json, const QString &key, QJsonValue &value); static bool extractArray(const QJsonObject &json, const QString &key, QJsonArray &value); static bool extractObject(const QJsonObject &json, const QString &key, QJsonObject &value); diff --git a/sdrgui/gui/wrappingdatetimeedit.cpp b/sdrgui/gui/wrappingdatetimeedit.cpp index 14cac8347..b492795a1 100644 --- a/sdrgui/gui/wrappingdatetimeedit.cpp +++ b/sdrgui/gui/wrappingdatetimeedit.cpp @@ -20,52 +20,61 @@ WrappingDateTimeEdit::WrappingDateTimeEdit(QWidget *parent) : QDateTimeEdit(parent) { + // We need to set this true in order for stepBy to be called + // when minutes are at 59 + // However, this also causes dates/times to wrap from max to min + // which we don't want, so in stepBy, we clip to max/min ourselves setWrapping(true); } void WrappingDateTimeEdit::stepBy(int steps) { if (currentSection() == QDateTimeEdit::MonthSection) - setDate(date().addMonths(steps)); + { + clipAndSetDate(date().addMonths(steps)); + } else if (currentSection() == QDateTimeEdit::DaySection) - setDate(date().addDays(steps)); + { + clipAndSetDate(date().addDays(steps)); + } else if (currentSection() == QDateTimeEdit::HourSection) { - QTime t = time(); - int h = t.hour(); - - setTime(time().addSecs(steps*3600)); - - if ((h < -steps) && (steps < 0)) - setDate(date().addDays(-1)); - else if ((h + steps > 23) && (steps > 0)) - setDate(date().addDays(1)); + clipAndSetDateTime(dateTime().addSecs(steps*3600)); } else if (currentSection() == QDateTimeEdit::MinuteSection) { - QTime t = time(); - int h = t.hour(); - int m = t.minute(); - - setTime(time().addSecs(steps*60)); - - if ((m < -steps) && (steps < 0) && (h == 0)) - setDate(date().addDays(-1)); - else if ((m + steps > 59) && (steps > 0) && (h == 23)) - setDate(date().addDays(1)); + clipAndSetDateTime(dateTime().addSecs(steps*60)); } else if (currentSection() == QDateTimeEdit::SecondSection) { - QTime t = time(); - int h = t.hour(); - int m = t.minute(); - int s = t.second(); - - setTime(time().addSecs(steps)); - - if ((s < -steps) && (steps < 0) && (h == 0) && (m == 0)) - setDate(date().addDays(-1)); - else if ((s + steps > 59) && (steps > 0) && (h == 23) && (m == 59)) - setDate(date().addDays(1)); + clipAndSetDateTime(dateTime().addSecs(steps)); + } +} + +void WrappingDateTimeEdit::clipAndSetDate(QDate date) +{ + QDate max = maximumDate(); + QDate min = minimumDate(); + if (date > max) { + setDate(max); + } else if (date < min) { + setDate(min); + } else { + setDate(date); + } +} + +void WrappingDateTimeEdit::clipAndSetDateTime(QDateTime dateTime) +{ + // We have set wrapping as described in the constructor, but we don't want + // to wrap from max to min, so clip to this outself + QDateTime max = maximumDateTime(); + QDateTime min = minimumDateTime(); + if (dateTime > max) { + setDateTime(max); + } else if (dateTime < min) { + setDateTime(min); + } else { + setDateTime(dateTime); } } diff --git a/sdrgui/gui/wrappingdatetimeedit.h b/sdrgui/gui/wrappingdatetimeedit.h index ddc766222..58571ae43 100644 --- a/sdrgui/gui/wrappingdatetimeedit.h +++ b/sdrgui/gui/wrappingdatetimeedit.h @@ -30,6 +30,10 @@ public: explicit WrappingDateTimeEdit(QWidget *parent = nullptr); void stepBy(int steps) override; + +protected: + void clipAndSetDate(QDate date); + void clipAndSetDateTime(QDateTime dateTime); }; #endif // SDRGUI_GUI_WRAPPINGDATETIMEEDIT_H diff --git a/swagger/sdrangel/api/swagger/include/ChannelActions.yaml b/swagger/sdrangel/api/swagger/include/ChannelActions.yaml index dd497db1b..86ce9318a 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelActions.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelActions.yaml @@ -29,5 +29,7 @@ ChannelActions: $ref: "http://swgserver:8081/api/swagger/include/IEEE_802_15_4_Mod.yaml#/IEEE_802_15_4_ModActions" PacketModActions: $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModActions" + RadioAstronomyActions: + $ref: "http://swgserver:8081/api/swagger/include/RadioAstronomy.yaml#/RadioAstronomyActions" SigMFFileSinkActions: $ref: "http://swgserver:8081/api/swagger/include/SigMFFileSink.yaml#/SigMFFileSinkActions" diff --git a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml index b3cd88b35..a8914da66 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml @@ -53,6 +53,8 @@ ChannelReport: $ref: "http://swgserver:8081/api/swagger/include/NoiseFigure.yaml#/NoiseFigureReport" SSBDemodReport: $ref: "http://swgserver:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" + RadioAstronomyReport: + $ref: "http://swgserver:8081/api/swagger/include/RadioAstronomy.yaml#/RadioAstronomyReport" RadioClockReport: $ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockReport" RemoteSourceReport: diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index 1964ba6a2..d82a11a04 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -81,6 +81,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModSettings" PagerDemodSettings: $ref: "http://swgserver:8081/api/swagger/include/PagerDemod.yaml#/PagerDemodSettings" + RadioAstronomySettings: + $ref: "http://swgserver:8081/api/swagger/include/RadioAstronomy.yaml#/RadioAstronomySettings" RadioClockSettings: $ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockSettings" RemoteSinkSettings: diff --git a/swagger/sdrangel/api/swagger/include/RadioAstronomy.yaml b/swagger/sdrangel/api/swagger/include/RadioAstronomy.yaml new file mode 100644 index 000000000..4362bc140 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/RadioAstronomy.yaml @@ -0,0 +1,92 @@ +RadioAstronomySettings: + description: RadioAstronomy + properties: + inputFrequencyOffset: + type: integer + format: int64 + sampleRate: + type: integer + rfBandwidth: + type: integer + integration: + type: integer + fftSize: + type: integer + fftWindow: + type: integer + filterFreqs: + type: string + starTracker: + type: string + rotator: + type: string + runMode: + description: "(0 for single, 1 for continuous, 2 for sweep)" + type: integer + sweepStartAtTime: + description: "(0 for now, 1 for at sweepStartDateTime)" + type: integer + sweepStartDateTime: + type: string + sweepType: + description: "(0 for Az/El, 1 for l/b, 2 for offset)" + type: integer + sweep1Start: + type: number + format: float + sweep1Stop: + type: number + format: float + sweep1Step: + type: number + format: float + sweep1Delay: + type: number + format: float + sweep2Start: + type: number + format: float + sweep2Stop: + type: number + format: float + sweep2Step: + type: number + format: float + sweep2Delay: + 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 + +RadioAstronomyReport: + description: RadioAstronomy + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + channelSampleRate: + type: integer + +RadioAstronomyActions: + description: RadioAstronomy + properties: + start: + type: object + description: "Start measurement(s)" diff --git a/swagger/sdrangel/api/swagger/swagger.yaml b/swagger/sdrangel/api/swagger/swagger.yaml index e25826e8f..06c590fee 100644 --- a/swagger/sdrangel/api/swagger/swagger.yaml +++ b/swagger/sdrangel/api/swagger/swagger.yaml @@ -3079,6 +3079,16 @@ definitions: MapItem: $ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapItem" + # StarTracker settings passed to/from Radio Astronomy plugin + StarTrackerTarget: + $ref: "http://swgserver:8081/api/swagger/include/StarTracker.yaml#/StarTrackerTarget" + + StarTrackerDisplaySettings: + $ref: "http://swgserver:8081/api/swagger/include/StarTracker.yaml#/StarTrackerDisplaySettings" + + StarTrackerDisplayLoSSettings: + $ref: "http://swgserver:8081/api/swagger/include/StarTracker.yaml#/StarTrackerDisplayLoSSettings" + # This isn't in GS232Controller, as it may eventually be used by other controllers or features TargetAzimuthElevation: description: "A target azimuth and elevation" diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelActions.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelActions.cpp index 10fe16473..90ec4ee8a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelActions.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelActions.cpp @@ -48,6 +48,8 @@ SWGChannelActions::SWGChannelActions() { m_ieee_802_15_4_mod_actions_isSet = false; packet_mod_actions = nullptr; m_packet_mod_actions_isSet = false; + radio_astronomy_actions = nullptr; + m_radio_astronomy_actions_isSet = false; sig_mf_file_sink_actions = nullptr; m_sig_mf_file_sink_actions_isSet = false; } @@ -78,6 +80,8 @@ SWGChannelActions::init() { m_ieee_802_15_4_mod_actions_isSet = false; packet_mod_actions = new SWGPacketModActions(); m_packet_mod_actions_isSet = false; + radio_astronomy_actions = new SWGRadioAstronomyActions(); + m_radio_astronomy_actions_isSet = false; sig_mf_file_sink_actions = new SWGSigMFFileSinkActions(); m_sig_mf_file_sink_actions_isSet = false; } @@ -108,6 +112,9 @@ SWGChannelActions::cleanup() { if(packet_mod_actions != nullptr) { delete packet_mod_actions; } + if(radio_astronomy_actions != nullptr) { + delete radio_astronomy_actions; + } if(sig_mf_file_sink_actions != nullptr) { delete sig_mf_file_sink_actions; } @@ -144,6 +151,8 @@ SWGChannelActions::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&packet_mod_actions, pJson["PacketModActions"], "SWGPacketModActions", "SWGPacketModActions"); + ::SWGSDRangel::setValue(&radio_astronomy_actions, pJson["RadioAstronomyActions"], "SWGRadioAstronomyActions", "SWGRadioAstronomyActions"); + ::SWGSDRangel::setValue(&sig_mf_file_sink_actions, pJson["SigMFFileSinkActions"], "SWGSigMFFileSinkActions", "SWGSigMFFileSinkActions"); } @@ -192,6 +201,9 @@ SWGChannelActions::asJsonObject() { if((packet_mod_actions != nullptr) && (packet_mod_actions->isSet())){ toJsonValue(QString("PacketModActions"), packet_mod_actions, obj, QString("SWGPacketModActions")); } + if((radio_astronomy_actions != nullptr) && (radio_astronomy_actions->isSet())){ + toJsonValue(QString("RadioAstronomyActions"), radio_astronomy_actions, obj, QString("SWGRadioAstronomyActions")); + } if((sig_mf_file_sink_actions != nullptr) && (sig_mf_file_sink_actions->isSet())){ toJsonValue(QString("SigMFFileSinkActions"), sig_mf_file_sink_actions, obj, QString("SWGSigMFFileSinkActions")); } @@ -299,6 +311,16 @@ SWGChannelActions::setPacketModActions(SWGPacketModActions* packet_mod_actions) this->m_packet_mod_actions_isSet = true; } +SWGRadioAstronomyActions* +SWGChannelActions::getRadioAstronomyActions() { + return radio_astronomy_actions; +} +void +SWGChannelActions::setRadioAstronomyActions(SWGRadioAstronomyActions* radio_astronomy_actions) { + this->radio_astronomy_actions = radio_astronomy_actions; + this->m_radio_astronomy_actions_isSet = true; +} + SWGSigMFFileSinkActions* SWGChannelActions::getSigMfFileSinkActions() { return sig_mf_file_sink_actions; @@ -344,6 +366,9 @@ SWGChannelActions::isSet(){ if(packet_mod_actions && packet_mod_actions->isSet()){ isObjectUpdated = true; break; } + if(radio_astronomy_actions && radio_astronomy_actions->isSet()){ + isObjectUpdated = true; break; + } if(sig_mf_file_sink_actions && sig_mf_file_sink_actions->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelActions.h b/swagger/sdrangel/code/qt5/client/SWGChannelActions.h index d80b97983..e11d1d2c6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelActions.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelActions.h @@ -28,6 +28,7 @@ #include "SWGFileSourceActions.h" #include "SWGIEEE_802_15_4_ModActions.h" #include "SWGPacketModActions.h" +#include "SWGRadioAstronomyActions.h" #include "SWGSigMFFileSinkActions.h" #include @@ -79,6 +80,9 @@ public: SWGPacketModActions* getPacketModActions(); void setPacketModActions(SWGPacketModActions* packet_mod_actions); + SWGRadioAstronomyActions* getRadioAstronomyActions(); + void setRadioAstronomyActions(SWGRadioAstronomyActions* radio_astronomy_actions); + SWGSigMFFileSinkActions* getSigMfFileSinkActions(); void setSigMfFileSinkActions(SWGSigMFFileSinkActions* sig_mf_file_sink_actions); @@ -116,6 +120,9 @@ private: SWGPacketModActions* packet_mod_actions; bool m_packet_mod_actions_isSet; + SWGRadioAstronomyActions* radio_astronomy_actions; + bool m_radio_astronomy_actions_isSet; + SWGSigMFFileSinkActions* sig_mf_file_sink_actions; bool m_sig_mf_file_sink_actions_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 75eefce19..14f39c952 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -74,6 +74,8 @@ SWGChannelReport::SWGChannelReport() { m_noise_figure_report_isSet = false; ssb_demod_report = nullptr; m_ssb_demod_report_isSet = false; + radio_astronomy_report = nullptr; + m_radio_astronomy_report_isSet = false; radio_clock_report = nullptr; m_radio_clock_report_isSet = false; remote_source_report = nullptr; @@ -152,6 +154,8 @@ SWGChannelReport::init() { m_noise_figure_report_isSet = false; ssb_demod_report = new SWGSSBDemodReport(); m_ssb_demod_report_isSet = false; + radio_astronomy_report = new SWGRadioAstronomyReport(); + m_radio_astronomy_report_isSet = false; radio_clock_report = new SWGRadioClockReport(); m_radio_clock_report_isSet = false; remote_source_report = new SWGRemoteSourceReport(); @@ -247,6 +251,9 @@ SWGChannelReport::cleanup() { if(ssb_demod_report != nullptr) { delete ssb_demod_report; } + if(radio_astronomy_report != nullptr) { + delete radio_astronomy_report; + } if(radio_clock_report != nullptr) { delete radio_clock_report; } @@ -342,6 +349,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&ssb_demod_report, pJson["SSBDemodReport"], "SWGSSBDemodReport", "SWGSSBDemodReport"); + ::SWGSDRangel::setValue(&radio_astronomy_report, pJson["RadioAstronomyReport"], "SWGRadioAstronomyReport", "SWGRadioAstronomyReport"); + ::SWGSDRangel::setValue(&radio_clock_report, pJson["RadioClockReport"], "SWGRadioClockReport", "SWGRadioClockReport"); ::SWGSDRangel::setValue(&remote_source_report, pJson["RemoteSourceReport"], "SWGRemoteSourceReport", "SWGRemoteSourceReport"); @@ -451,6 +460,9 @@ SWGChannelReport::asJsonObject() { if((ssb_demod_report != nullptr) && (ssb_demod_report->isSet())){ toJsonValue(QString("SSBDemodReport"), ssb_demod_report, obj, QString("SWGSSBDemodReport")); } + if((radio_astronomy_report != nullptr) && (radio_astronomy_report->isSet())){ + toJsonValue(QString("RadioAstronomyReport"), radio_astronomy_report, obj, QString("SWGRadioAstronomyReport")); + } if((radio_clock_report != nullptr) && (radio_clock_report->isSet())){ toJsonValue(QString("RadioClockReport"), radio_clock_report, obj, QString("SWGRadioClockReport")); } @@ -721,6 +733,16 @@ SWGChannelReport::setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report) { this->m_ssb_demod_report_isSet = true; } +SWGRadioAstronomyReport* +SWGChannelReport::getRadioAstronomyReport() { + return radio_astronomy_report; +} +void +SWGChannelReport::setRadioAstronomyReport(SWGRadioAstronomyReport* radio_astronomy_report) { + this->radio_astronomy_report = radio_astronomy_report; + this->m_radio_astronomy_report_isSet = true; +} + SWGRadioClockReport* SWGChannelReport::getRadioClockReport() { return radio_clock_report; @@ -915,6 +937,9 @@ SWGChannelReport::isSet(){ if(ssb_demod_report && ssb_demod_report->isSet()){ isObjectUpdated = true; break; } + if(radio_astronomy_report && radio_astronomy_report->isSet()){ + isObjectUpdated = true; break; + } if(radio_clock_report && radio_clock_report->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 2b3ac035a..f0d6d6996 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -44,6 +44,7 @@ #include "SWGNoiseFigureReport.h" #include "SWGPacketModReport.h" #include "SWGPagerDemodReport.h" +#include "SWGRadioAstronomyReport.h" #include "SWGRadioClockReport.h" #include "SWGRemoteSourceReport.h" #include "SWGSSBDemodReport.h" @@ -144,6 +145,9 @@ public: SWGSSBDemodReport* getSsbDemodReport(); void setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report); + SWGRadioAstronomyReport* getRadioAstronomyReport(); + void setRadioAstronomyReport(SWGRadioAstronomyReport* radio_astronomy_report); + SWGRadioClockReport* getRadioClockReport(); void setRadioClockReport(SWGRadioClockReport* radio_clock_report); @@ -253,6 +257,9 @@ private: SWGSSBDemodReport* ssb_demod_report; bool m_ssb_demod_report_isSet; + SWGRadioAstronomyReport* radio_astronomy_report; + bool m_radio_astronomy_report_isSet; + SWGRadioClockReport* radio_clock_report; bool m_radio_clock_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index fc37227fc..ca2a88d60 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -100,6 +100,8 @@ SWGChannelSettings::SWGChannelSettings() { m_packet_mod_settings_isSet = false; pager_demod_settings = nullptr; m_pager_demod_settings_isSet = false; + radio_astronomy_settings = nullptr; + m_radio_astronomy_settings_isSet = false; radio_clock_settings = nullptr; m_radio_clock_settings_isSet = false; remote_sink_settings = nullptr; @@ -204,6 +206,8 @@ SWGChannelSettings::init() { m_packet_mod_settings_isSet = false; pager_demod_settings = new SWGPagerDemodSettings(); m_pager_demod_settings_isSet = false; + radio_astronomy_settings = new SWGRadioAstronomySettings(); + m_radio_astronomy_settings_isSet = false; radio_clock_settings = new SWGRadioClockSettings(); m_radio_clock_settings_isSet = false; remote_sink_settings = new SWGRemoteSinkSettings(); @@ -334,6 +338,9 @@ SWGChannelSettings::cleanup() { if(pager_demod_settings != nullptr) { delete pager_demod_settings; } + if(radio_astronomy_settings != nullptr) { + delete radio_astronomy_settings; + } if(radio_clock_settings != nullptr) { delete radio_clock_settings; } @@ -455,6 +462,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&pager_demod_settings, pJson["PagerDemodSettings"], "SWGPagerDemodSettings", "SWGPagerDemodSettings"); + ::SWGSDRangel::setValue(&radio_astronomy_settings, pJson["RadioAstronomySettings"], "SWGRadioAstronomySettings", "SWGRadioAstronomySettings"); + ::SWGSDRangel::setValue(&radio_clock_settings, pJson["RadioClockSettings"], "SWGRadioClockSettings", "SWGRadioClockSettings"); ::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings"); @@ -603,6 +612,9 @@ SWGChannelSettings::asJsonObject() { if((pager_demod_settings != nullptr) && (pager_demod_settings->isSet())){ toJsonValue(QString("PagerDemodSettings"), pager_demod_settings, obj, QString("SWGPagerDemodSettings")); } + if((radio_astronomy_settings != nullptr) && (radio_astronomy_settings->isSet())){ + toJsonValue(QString("RadioAstronomySettings"), radio_astronomy_settings, obj, QString("SWGRadioAstronomySettings")); + } if((radio_clock_settings != nullptr) && (radio_clock_settings->isSet())){ toJsonValue(QString("RadioClockSettings"), radio_clock_settings, obj, QString("SWGRadioClockSettings")); } @@ -1003,6 +1015,16 @@ SWGChannelSettings::setPagerDemodSettings(SWGPagerDemodSettings* pager_demod_set this->m_pager_demod_settings_isSet = true; } +SWGRadioAstronomySettings* +SWGChannelSettings::getRadioAstronomySettings() { + return radio_astronomy_settings; +} +void +SWGChannelSettings::setRadioAstronomySettings(SWGRadioAstronomySettings* radio_astronomy_settings) { + this->radio_astronomy_settings = radio_astronomy_settings; + this->m_radio_astronomy_settings_isSet = true; +} + SWGRadioClockSettings* SWGChannelSettings::getRadioClockSettings() { return radio_clock_settings; @@ -1236,6 +1258,9 @@ SWGChannelSettings::isSet(){ if(pager_demod_settings && pager_demod_settings->isSet()){ isObjectUpdated = true; break; } + if(radio_astronomy_settings && radio_astronomy_settings->isSet()){ + isObjectUpdated = true; break; + } if(radio_clock_settings && radio_clock_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 58e8261d8..074c83cb1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -54,6 +54,7 @@ #include "SWGPacketDemodSettings.h" #include "SWGPacketModSettings.h" #include "SWGPagerDemodSettings.h" +#include "SWGRadioAstronomySettings.h" #include "SWGRadioClockSettings.h" #include "SWGRemoteSinkSettings.h" #include "SWGRemoteSourceSettings.h" @@ -194,6 +195,9 @@ public: SWGPagerDemodSettings* getPagerDemodSettings(); void setPagerDemodSettings(SWGPagerDemodSettings* pager_demod_settings); + SWGRadioAstronomySettings* getRadioAstronomySettings(); + void setRadioAstronomySettings(SWGRadioAstronomySettings* radio_astronomy_settings); + SWGRadioClockSettings* getRadioClockSettings(); void setRadioClockSettings(SWGRadioClockSettings* radio_clock_settings); @@ -342,6 +346,9 @@ private: SWGPagerDemodSettings* pager_demod_settings; bool m_pager_demod_settings_isSet; + SWGRadioAstronomySettings* radio_astronomy_settings; + bool m_radio_astronomy_settings_isSet; + SWGRadioClockSettings* radio_clock_settings; bool m_radio_clock_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 7529bc53c..902208ad4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -210,6 +210,9 @@ #include "SWGPresets.h" #include "SWGRDSReport.h" #include "SWGRDSReport_altFrequencies.h" +#include "SWGRadioAstronomyActions.h" +#include "SWGRadioAstronomyReport.h" +#include "SWGRadioAstronomySettings.h" #include "SWGRadioClockReport.h" #include "SWGRadioClockSettings.h" #include "SWGRange.h" @@ -253,7 +256,13 @@ #include "SWGSoapySDRReport.h" #include "SWGSpectrumServer.h" #include "SWGSpectrumServer_clients.h" +#include "SWGStarTrackerDisplayLoSSettings.h" +#include "SWGStarTrackerDisplayLoSSettings_2.h" +#include "SWGStarTrackerDisplaySettings.h" +#include "SWGStarTrackerDisplaySettings_2.h" #include "SWGStarTrackerSettings.h" +#include "SWGStarTrackerTarget.h" +#include "SWGStarTrackerTarget_2.h" #include "SWGSuccessResponse.h" #include "SWGTargetAzimuthElevation.h" #include "SWGTestMISettings.h" @@ -1269,6 +1278,21 @@ namespace SWGSDRangel { obj->init(); return obj; } + if(QString("SWGRadioAstronomyActions").compare(type) == 0) { + SWGRadioAstronomyActions *obj = new SWGRadioAstronomyActions(); + obj->init(); + return obj; + } + if(QString("SWGRadioAstronomyReport").compare(type) == 0) { + SWGRadioAstronomyReport *obj = new SWGRadioAstronomyReport(); + obj->init(); + return obj; + } + if(QString("SWGRadioAstronomySettings").compare(type) == 0) { + SWGRadioAstronomySettings *obj = new SWGRadioAstronomySettings(); + obj->init(); + return obj; + } if(QString("SWGRadioClockReport").compare(type) == 0) { SWGRadioClockReport *obj = new SWGRadioClockReport(); obj->init(); @@ -1484,11 +1508,41 @@ namespace SWGSDRangel { obj->init(); return obj; } + if(QString("SWGStarTrackerDisplayLoSSettings").compare(type) == 0) { + SWGStarTrackerDisplayLoSSettings *obj = new SWGStarTrackerDisplayLoSSettings(); + obj->init(); + return obj; + } + if(QString("SWGStarTrackerDisplayLoSSettings_2").compare(type) == 0) { + SWGStarTrackerDisplayLoSSettings_2 *obj = new SWGStarTrackerDisplayLoSSettings_2(); + obj->init(); + return obj; + } + if(QString("SWGStarTrackerDisplaySettings").compare(type) == 0) { + SWGStarTrackerDisplaySettings *obj = new SWGStarTrackerDisplaySettings(); + obj->init(); + return obj; + } + if(QString("SWGStarTrackerDisplaySettings_2").compare(type) == 0) { + SWGStarTrackerDisplaySettings_2 *obj = new SWGStarTrackerDisplaySettings_2(); + obj->init(); + return obj; + } if(QString("SWGStarTrackerSettings").compare(type) == 0) { SWGStarTrackerSettings *obj = new SWGStarTrackerSettings(); obj->init(); return obj; } + if(QString("SWGStarTrackerTarget").compare(type) == 0) { + SWGStarTrackerTarget *obj = new SWGStarTrackerTarget(); + obj->init(); + return obj; + } + if(QString("SWGStarTrackerTarget_2").compare(type) == 0) { + SWGStarTrackerTarget_2 *obj = new SWGStarTrackerTarget_2(); + obj->init(); + return obj; + } if(QString("SWGSuccessResponse").compare(type) == 0) { SWGSuccessResponse *obj = new SWGSuccessResponse(); obj->init(); diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.cpp b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.cpp new file mode 100644 index 000000000..fc24bcb47 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.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 "SWGRadioAstronomyActions.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRadioAstronomyActions::SWGRadioAstronomyActions(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRadioAstronomyActions::SWGRadioAstronomyActions() { + start = nullptr; + m_start_isSet = false; +} + +SWGRadioAstronomyActions::~SWGRadioAstronomyActions() { + this->cleanup(); +} + +void +SWGRadioAstronomyActions::init() { + start = NULL; + m_start_isSet = false; +} + +void +SWGRadioAstronomyActions::cleanup() { + if(start != nullptr) { + delete start; + } +} + +SWGRadioAstronomyActions* +SWGRadioAstronomyActions::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRadioAstronomyActions::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&start, pJson["start"], "SWGObject", "SWGObject"); + +} + +QString +SWGRadioAstronomyActions::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRadioAstronomyActions::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if((start != nullptr) && (start->isSet())){ + toJsonValue(QString("start"), start, obj, QString("SWGObject")); + } + + return obj; +} + +SWGObject* +SWGRadioAstronomyActions::getStart() { + return start; +} +void +SWGRadioAstronomyActions::setStart(SWGObject* start) { + this->start = start; + this->m_start_isSet = true; +} + + +bool +SWGRadioAstronomyActions::isSet(){ + bool isObjectUpdated = false; + do{ + if(start && start->isSet()){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.h b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.h new file mode 100644 index 000000000..92ecb71e7 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyActions.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. + */ + +/* + * SWGRadioAstronomyActions.h + * + * RadioAstronomy + */ + +#ifndef SWGRadioAstronomyActions_H_ +#define SWGRadioAstronomyActions_H_ + +#include + + +#include "SWGObject.h" + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRadioAstronomyActions: public SWGObject { +public: + SWGRadioAstronomyActions(); + SWGRadioAstronomyActions(QString* json); + virtual ~SWGRadioAstronomyActions(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRadioAstronomyActions* fromJson(QString &jsonString) override; + + SWGObject* getStart(); + void setStart(SWGObject* start); + + + virtual bool isSet() override; + +private: + SWGObject* start; + bool m_start_isSet; + +}; + +} + +#endif /* SWGRadioAstronomyActions_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.cpp b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.cpp new file mode 100644 index 000000000..1635b3cbf --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.cpp @@ -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. + */ + + +#include "SWGRadioAstronomyReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRadioAstronomyReport::SWGRadioAstronomyReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRadioAstronomyReport::SWGRadioAstronomyReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGRadioAstronomyReport::~SWGRadioAstronomyReport() { + this->cleanup(); +} + +void +SWGRadioAstronomyReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGRadioAstronomyReport::cleanup() { + + +} + +SWGRadioAstronomyReport* +SWGRadioAstronomyReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRadioAstronomyReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGRadioAstronomyReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRadioAstronomyReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGRadioAstronomyReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGRadioAstronomyReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGRadioAstronomyReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGRadioAstronomyReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGRadioAstronomyReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ + isObjectUpdated = true; break; + } + if(m_channel_sample_rate_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.h b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.h new file mode 100644 index 000000000..c79cfd411 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomyReport.h @@ -0,0 +1,64 @@ +/** + * 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. + */ + +/* + * SWGRadioAstronomyReport.h + * + * RadioAstronomy + */ + +#ifndef SWGRadioAstronomyReport_H_ +#define SWGRadioAstronomyReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRadioAstronomyReport: public SWGObject { +public: + SWGRadioAstronomyReport(); + SWGRadioAstronomyReport(QString* json); + virtual ~SWGRadioAstronomyReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRadioAstronomyReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGRadioAstronomyReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGRadioAstronomySettings.cpp b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomySettings.cpp new file mode 100644 index 000000000..38c9f77c1 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomySettings.cpp @@ -0,0 +1,764 @@ +/** + * 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 "SWGRadioAstronomySettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGRadioAstronomySettings::SWGRadioAstronomySettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGRadioAstronomySettings::SWGRadioAstronomySettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + rf_bandwidth = 0; + m_rf_bandwidth_isSet = false; + integration = 0; + m_integration_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + fft_window = 0; + m_fft_window_isSet = false; + filter_freqs = nullptr; + m_filter_freqs_isSet = false; + star_tracker = nullptr; + m_star_tracker_isSet = false; + rotator = nullptr; + m_rotator_isSet = false; + run_mode = 0; + m_run_mode_isSet = false; + sweep_start_at_time = 0; + m_sweep_start_at_time_isSet = false; + sweep_start_date_time = nullptr; + m_sweep_start_date_time_isSet = false; + sweep_type = 0; + m_sweep_type_isSet = false; + sweep1_start = 0.0f; + m_sweep1_start_isSet = false; + sweep1_stop = 0.0f; + m_sweep1_stop_isSet = false; + sweep1_step = 0.0f; + m_sweep1_step_isSet = false; + sweep1_delay = 0.0f; + m_sweep1_delay_isSet = false; + sweep2_start = 0.0f; + m_sweep2_start_isSet = false; + sweep2_stop = 0.0f; + m_sweep2_stop_isSet = false; + sweep2_step = 0.0f; + m_sweep2_step_isSet = false; + sweep2_delay = 0.0f; + m_sweep2_delay_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; +} + +SWGRadioAstronomySettings::~SWGRadioAstronomySettings() { + this->cleanup(); +} + +void +SWGRadioAstronomySettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + sample_rate = 0; + m_sample_rate_isSet = false; + rf_bandwidth = 0; + m_rf_bandwidth_isSet = false; + integration = 0; + m_integration_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + fft_window = 0; + m_fft_window_isSet = false; + filter_freqs = new QString(""); + m_filter_freqs_isSet = false; + star_tracker = new QString(""); + m_star_tracker_isSet = false; + rotator = new QString(""); + m_rotator_isSet = false; + run_mode = 0; + m_run_mode_isSet = false; + sweep_start_at_time = 0; + m_sweep_start_at_time_isSet = false; + sweep_start_date_time = new QString(""); + m_sweep_start_date_time_isSet = false; + sweep_type = 0; + m_sweep_type_isSet = false; + sweep1_start = 0.0f; + m_sweep1_start_isSet = false; + sweep1_stop = 0.0f; + m_sweep1_stop_isSet = false; + sweep1_step = 0.0f; + m_sweep1_step_isSet = false; + sweep1_delay = 0.0f; + m_sweep1_delay_isSet = false; + sweep2_start = 0.0f; + m_sweep2_start_isSet = false; + sweep2_stop = 0.0f; + m_sweep2_stop_isSet = false; + sweep2_step = 0.0f; + m_sweep2_step_isSet = false; + sweep2_delay = 0.0f; + m_sweep2_delay_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 +SWGRadioAstronomySettings::cleanup() { + + + + + + + if(filter_freqs != nullptr) { + delete filter_freqs; + } + if(star_tracker != nullptr) { + delete star_tracker; + } + if(rotator != nullptr) { + delete rotator; + } + + + if(sweep_start_date_time != nullptr) { + delete sweep_start_date_time; + } + + + + + + + + + + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGRadioAstronomySettings* +SWGRadioAstronomySettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGRadioAstronomySettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&sample_rate, pJson["sampleRate"], "qint32", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "qint32", ""); + + ::SWGSDRangel::setValue(&integration, pJson["integration"], "qint32", ""); + + ::SWGSDRangel::setValue(&fft_size, pJson["fftSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&fft_window, pJson["fftWindow"], "qint32", ""); + + ::SWGSDRangel::setValue(&filter_freqs, pJson["filterFreqs"], "QString", "QString"); + + ::SWGSDRangel::setValue(&star_tracker, pJson["starTracker"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rotator, pJson["rotator"], "QString", "QString"); + + ::SWGSDRangel::setValue(&run_mode, pJson["runMode"], "qint32", ""); + + ::SWGSDRangel::setValue(&sweep_start_at_time, pJson["sweepStartAtTime"], "qint32", ""); + + ::SWGSDRangel::setValue(&sweep_start_date_time, pJson["sweepStartDateTime"], "QString", "QString"); + + ::SWGSDRangel::setValue(&sweep_type, pJson["sweepType"], "qint32", ""); + + ::SWGSDRangel::setValue(&sweep1_start, pJson["sweep1Start"], "float", ""); + + ::SWGSDRangel::setValue(&sweep1_stop, pJson["sweep1Stop"], "float", ""); + + ::SWGSDRangel::setValue(&sweep1_step, pJson["sweep1Step"], "float", ""); + + ::SWGSDRangel::setValue(&sweep1_delay, pJson["sweep1Delay"], "float", ""); + + ::SWGSDRangel::setValue(&sweep2_start, pJson["sweep2Start"], "float", ""); + + ::SWGSDRangel::setValue(&sweep2_stop, pJson["sweep2Stop"], "float", ""); + + ::SWGSDRangel::setValue(&sweep2_step, pJson["sweep2Step"], "float", ""); + + ::SWGSDRangel::setValue(&sweep2_delay, pJson["sweep2Delay"], "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 +SWGRadioAstronomySettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGRadioAstronomySettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_sample_rate_isSet){ + obj->insert("sampleRate", QJsonValue(sample_rate)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_integration_isSet){ + obj->insert("integration", QJsonValue(integration)); + } + if(m_fft_size_isSet){ + obj->insert("fftSize", QJsonValue(fft_size)); + } + if(m_fft_window_isSet){ + obj->insert("fftWindow", QJsonValue(fft_window)); + } + if(filter_freqs != nullptr && *filter_freqs != QString("")){ + toJsonValue(QString("filterFreqs"), filter_freqs, obj, QString("QString")); + } + if(star_tracker != nullptr && *star_tracker != QString("")){ + toJsonValue(QString("starTracker"), star_tracker, obj, QString("QString")); + } + if(rotator != nullptr && *rotator != QString("")){ + toJsonValue(QString("rotator"), rotator, obj, QString("QString")); + } + if(m_run_mode_isSet){ + obj->insert("runMode", QJsonValue(run_mode)); + } + if(m_sweep_start_at_time_isSet){ + obj->insert("sweepStartAtTime", QJsonValue(sweep_start_at_time)); + } + if(sweep_start_date_time != nullptr && *sweep_start_date_time != QString("")){ + toJsonValue(QString("sweepStartDateTime"), sweep_start_date_time, obj, QString("QString")); + } + if(m_sweep_type_isSet){ + obj->insert("sweepType", QJsonValue(sweep_type)); + } + if(m_sweep1_start_isSet){ + obj->insert("sweep1Start", QJsonValue(sweep1_start)); + } + if(m_sweep1_stop_isSet){ + obj->insert("sweep1Stop", QJsonValue(sweep1_stop)); + } + if(m_sweep1_step_isSet){ + obj->insert("sweep1Step", QJsonValue(sweep1_step)); + } + if(m_sweep1_delay_isSet){ + obj->insert("sweep1Delay", QJsonValue(sweep1_delay)); + } + if(m_sweep2_start_isSet){ + obj->insert("sweep2Start", QJsonValue(sweep2_start)); + } + if(m_sweep2_stop_isSet){ + obj->insert("sweep2Stop", QJsonValue(sweep2_stop)); + } + if(m_sweep2_step_isSet){ + obj->insert("sweep2Step", QJsonValue(sweep2_step)); + } + if(m_sweep2_delay_isSet){ + obj->insert("sweep2Delay", QJsonValue(sweep2_delay)); + } + 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 +SWGRadioAstronomySettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGRadioAstronomySettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getSampleRate() { + return sample_rate; +} +void +SWGRadioAstronomySettings::setSampleRate(qint32 sample_rate) { + this->sample_rate = sample_rate; + this->m_sample_rate_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGRadioAstronomySettings::setRfBandwidth(qint32 rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getIntegration() { + return integration; +} +void +SWGRadioAstronomySettings::setIntegration(qint32 integration) { + this->integration = integration; + this->m_integration_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getFftSize() { + return fft_size; +} +void +SWGRadioAstronomySettings::setFftSize(qint32 fft_size) { + this->fft_size = fft_size; + this->m_fft_size_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getFftWindow() { + return fft_window; +} +void +SWGRadioAstronomySettings::setFftWindow(qint32 fft_window) { + this->fft_window = fft_window; + this->m_fft_window_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getFilterFreqs() { + return filter_freqs; +} +void +SWGRadioAstronomySettings::setFilterFreqs(QString* filter_freqs) { + this->filter_freqs = filter_freqs; + this->m_filter_freqs_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getStarTracker() { + return star_tracker; +} +void +SWGRadioAstronomySettings::setStarTracker(QString* star_tracker) { + this->star_tracker = star_tracker; + this->m_star_tracker_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getRotator() { + return rotator; +} +void +SWGRadioAstronomySettings::setRotator(QString* rotator) { + this->rotator = rotator; + this->m_rotator_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getRunMode() { + return run_mode; +} +void +SWGRadioAstronomySettings::setRunMode(qint32 run_mode) { + this->run_mode = run_mode; + this->m_run_mode_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getSweepStartAtTime() { + return sweep_start_at_time; +} +void +SWGRadioAstronomySettings::setSweepStartAtTime(qint32 sweep_start_at_time) { + this->sweep_start_at_time = sweep_start_at_time; + this->m_sweep_start_at_time_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getSweepStartDateTime() { + return sweep_start_date_time; +} +void +SWGRadioAstronomySettings::setSweepStartDateTime(QString* sweep_start_date_time) { + this->sweep_start_date_time = sweep_start_date_time; + this->m_sweep_start_date_time_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getSweepType() { + return sweep_type; +} +void +SWGRadioAstronomySettings::setSweepType(qint32 sweep_type) { + this->sweep_type = sweep_type; + this->m_sweep_type_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep1Start() { + return sweep1_start; +} +void +SWGRadioAstronomySettings::setSweep1Start(float sweep1_start) { + this->sweep1_start = sweep1_start; + this->m_sweep1_start_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep1Stop() { + return sweep1_stop; +} +void +SWGRadioAstronomySettings::setSweep1Stop(float sweep1_stop) { + this->sweep1_stop = sweep1_stop; + this->m_sweep1_stop_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep1Step() { + return sweep1_step; +} +void +SWGRadioAstronomySettings::setSweep1Step(float sweep1_step) { + this->sweep1_step = sweep1_step; + this->m_sweep1_step_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep1Delay() { + return sweep1_delay; +} +void +SWGRadioAstronomySettings::setSweep1Delay(float sweep1_delay) { + this->sweep1_delay = sweep1_delay; + this->m_sweep1_delay_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep2Start() { + return sweep2_start; +} +void +SWGRadioAstronomySettings::setSweep2Start(float sweep2_start) { + this->sweep2_start = sweep2_start; + this->m_sweep2_start_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep2Stop() { + return sweep2_stop; +} +void +SWGRadioAstronomySettings::setSweep2Stop(float sweep2_stop) { + this->sweep2_stop = sweep2_stop; + this->m_sweep2_stop_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep2Step() { + return sweep2_step; +} +void +SWGRadioAstronomySettings::setSweep2Step(float sweep2_step) { + this->sweep2_step = sweep2_step; + this->m_sweep2_step_isSet = true; +} + +float +SWGRadioAstronomySettings::getSweep2Delay() { + return sweep2_delay; +} +void +SWGRadioAstronomySettings::setSweep2Delay(float sweep2_delay) { + this->sweep2_delay = sweep2_delay; + this->m_sweep2_delay_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getRgbColor() { + return rgb_color; +} +void +SWGRadioAstronomySettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getTitle() { + return title; +} +void +SWGRadioAstronomySettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getStreamIndex() { + return stream_index; +} +void +SWGRadioAstronomySettings::setStreamIndex(qint32 stream_index) { + this->stream_index = stream_index; + this->m_stream_index_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGRadioAstronomySettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGRadioAstronomySettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGRadioAstronomySettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGRadioAstronomySettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGRadioAstronomySettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGRadioAstronomySettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGRadioAstronomySettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGRadioAstronomySettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ + isObjectUpdated = true; break; + } + if(m_sample_rate_isSet){ + isObjectUpdated = true; break; + } + if(m_rf_bandwidth_isSet){ + isObjectUpdated = true; break; + } + if(m_integration_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_size_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_window_isSet){ + isObjectUpdated = true; break; + } + if(filter_freqs && *filter_freqs != QString("")){ + isObjectUpdated = true; break; + } + if(star_tracker && *star_tracker != QString("")){ + isObjectUpdated = true; break; + } + if(rotator && *rotator != QString("")){ + isObjectUpdated = true; break; + } + if(m_run_mode_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep_start_at_time_isSet){ + isObjectUpdated = true; break; + } + if(sweep_start_date_time && *sweep_start_date_time != QString("")){ + isObjectUpdated = true; break; + } + if(m_sweep_type_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep1_start_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep1_stop_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep1_step_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep1_delay_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep2_start_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep2_stop_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep2_step_isSet){ + isObjectUpdated = true; break; + } + if(m_sweep2_delay_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/SWGRadioAstronomySettings.h b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomySettings.h new file mode 100644 index 000000000..432e4767f --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGRadioAstronomySettings.h @@ -0,0 +1,227 @@ +/** + * 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. + */ + +/* + * SWGRadioAstronomySettings.h + * + * RadioAstronomy + */ + +#ifndef SWGRadioAstronomySettings_H_ +#define SWGRadioAstronomySettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGRadioAstronomySettings: public SWGObject { +public: + SWGRadioAstronomySettings(); + SWGRadioAstronomySettings(QString* json); + virtual ~SWGRadioAstronomySettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGRadioAstronomySettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + qint32 getSampleRate(); + void setSampleRate(qint32 sample_rate); + + qint32 getRfBandwidth(); + void setRfBandwidth(qint32 rf_bandwidth); + + qint32 getIntegration(); + void setIntegration(qint32 integration); + + qint32 getFftSize(); + void setFftSize(qint32 fft_size); + + qint32 getFftWindow(); + void setFftWindow(qint32 fft_window); + + QString* getFilterFreqs(); + void setFilterFreqs(QString* filter_freqs); + + QString* getStarTracker(); + void setStarTracker(QString* star_tracker); + + QString* getRotator(); + void setRotator(QString* rotator); + + qint32 getRunMode(); + void setRunMode(qint32 run_mode); + + qint32 getSweepStartAtTime(); + void setSweepStartAtTime(qint32 sweep_start_at_time); + + QString* getSweepStartDateTime(); + void setSweepStartDateTime(QString* sweep_start_date_time); + + qint32 getSweepType(); + void setSweepType(qint32 sweep_type); + + float getSweep1Start(); + void setSweep1Start(float sweep1_start); + + float getSweep1Stop(); + void setSweep1Stop(float sweep1_stop); + + float getSweep1Step(); + void setSweep1Step(float sweep1_step); + + float getSweep1Delay(); + void setSweep1Delay(float sweep1_delay); + + float getSweep2Start(); + void setSweep2Start(float sweep2_start); + + float getSweep2Stop(); + void setSweep2Stop(float sweep2_stop); + + float getSweep2Step(); + void setSweep2Step(float sweep2_step); + + float getSweep2Delay(); + void setSweep2Delay(float sweep2_delay); + + 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; + + qint32 sample_rate; + bool m_sample_rate_isSet; + + qint32 rf_bandwidth; + bool m_rf_bandwidth_isSet; + + qint32 integration; + bool m_integration_isSet; + + qint32 fft_size; + bool m_fft_size_isSet; + + qint32 fft_window; + bool m_fft_window_isSet; + + QString* filter_freqs; + bool m_filter_freqs_isSet; + + QString* star_tracker; + bool m_star_tracker_isSet; + + QString* rotator; + bool m_rotator_isSet; + + qint32 run_mode; + bool m_run_mode_isSet; + + qint32 sweep_start_at_time; + bool m_sweep_start_at_time_isSet; + + QString* sweep_start_date_time; + bool m_sweep_start_date_time_isSet; + + qint32 sweep_type; + bool m_sweep_type_isSet; + + float sweep1_start; + bool m_sweep1_start_isSet; + + float sweep1_stop; + bool m_sweep1_stop_isSet; + + float sweep1_step; + bool m_sweep1_step_isSet; + + float sweep1_delay; + bool m_sweep1_delay_isSet; + + float sweep2_start; + bool m_sweep2_start_isSet; + + float sweep2_stop; + bool m_sweep2_stop_isSet; + + float sweep2_step; + bool m_sweep2_step_isSet; + + float sweep2_delay; + bool m_sweep2_delay_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 /* SWGRadioAstronomySettings_H_ */