diff --git a/doc/img/NoiseFigure_plugin_b210_comparison.png b/doc/img/NoiseFigure_plugin_b210_comparison.png new file mode 100644 index 000000000..359d3832a Binary files /dev/null and b/doc/img/NoiseFigure_plugin_b210_comparison.png differ diff --git a/doc/img/NoiseFigure_plugin_control.png b/doc/img/NoiseFigure_plugin_control.png new file mode 100644 index 000000000..e3f71e9ae Binary files /dev/null and b/doc/img/NoiseFigure_plugin_control.png differ diff --git a/doc/img/NoiseFigure_plugin_duo_comparison.png b/doc/img/NoiseFigure_plugin_duo_comparison.png new file mode 100644 index 000000000..032a068f0 Binary files /dev/null and b/doc/img/NoiseFigure_plugin_duo_comparison.png differ diff --git a/doc/img/NoiseFigure_plugin_enr.png b/doc/img/NoiseFigure_plugin_enr.png new file mode 100644 index 000000000..94b75e2ed Binary files /dev/null and b/doc/img/NoiseFigure_plugin_enr.png differ diff --git a/doc/img/NoiseFigure_plugin_hw_setup.png b/doc/img/NoiseFigure_plugin_hw_setup.png new file mode 100644 index 000000000..595fd3cf5 Binary files /dev/null and b/doc/img/NoiseFigure_plugin_hw_setup.png differ diff --git a/doc/img/NoiseFigure_plugin_lna_comparison.png b/doc/img/NoiseFigure_plugin_lna_comparison.png new file mode 100644 index 000000000..12142dd43 Binary files /dev/null and b/doc/img/NoiseFigure_plugin_lna_comparison.png differ diff --git a/doc/img/NoiseFigure_plugin_results.png b/doc/img/NoiseFigure_plugin_results.png new file mode 100644 index 000000000..0c300ae6c Binary files /dev/null and b/doc/img/NoiseFigure_plugin_results.png differ diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 9ea109601..89c024963 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(demodchirpchat) add_subdirectory(demodvorsc) add_subdirectory(demodpacket) add_subdirectory(demodais) +add_subdirectory(noisefigure) if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND) add_subdirectory(demoddab) diff --git a/plugins/channelrx/noisefigure/CMakeLists.txt b/plugins/channelrx/noisefigure/CMakeLists.txt new file mode 100644 index 000000000..54051d232 --- /dev/null +++ b/plugins/channelrx/noisefigure/CMakeLists.txt @@ -0,0 +1,65 @@ +project(noisefigure) + +set(noisefigure_SOURCES + noisefigure.cpp + noisefiguresettings.cpp + noisefigurebaseband.cpp + noisefiguresink.cpp + noisefigureplugin.cpp + noisefigurewebapiadapter.cpp +) + +set(noisefigure_HEADERS + noisefigure.h + noisefiguresettings.h + noisefigurebaseband.h + noisefiguresink.h + noisefigureplugin.h + noisefigurewebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${Boost_INCLUDE_DIRS} +) + +if(NOT SERVER_MODE) + set(noisefigure_SOURCES + ${noisefigure_SOURCES} + noisefiguregui.cpp + noisefiguregui.ui + noisefigurecontroldialog.ui + noisefigurecontroldialog.cpp + noisefigureenrdialog.ui + noisefigureenrdialog.cpp + ) + set(noisefigure_HEADERS + ${noisefigure_HEADERS} + noisefiguregui.h + noisefigurecontroldialog.h + noisefigureenrdialog.h + ) + + set(TARGET_NAME noisefigure) + set(TARGET_LIB "Qt5::Widgets" Qt5::Charts) + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME noisefiguresrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${noisefigure_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/noisefigure/noisefigure.cpp b/plugins/channelrx/noisefigure/noisefigure.cpp new file mode 100644 index 000000000..f4f8eec65 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigure.cpp @@ -0,0 +1,755 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +#include "noisefigure.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/deviceapi.h" +#include "feature/feature.h" +#include "util/db.h" +#include "channel/channelwebapiutils.h" +#include "maincore.h" + +MESSAGE_CLASS_DEFINITION(NoiseFigure::MsgConfigureNoiseFigure, Message) +MESSAGE_CLASS_DEFINITION(NoiseFigure::MsgPowerMeasurement, Message) +MESSAGE_CLASS_DEFINITION(NoiseFigure::MsgNFMeasurement, Message) +MESSAGE_CLASS_DEFINITION(NoiseFigure::MsgStartStop, Message) +MESSAGE_CLASS_DEFINITION(NoiseFigure::MsgFinished, Message) + +const char * const NoiseFigure::m_channelIdURI = "sdrangel.channel.noisefigure"; +const char * const NoiseFigure::m_channelId = "NoiseFigure"; + +NoiseFigure::NoiseFigure(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0), + m_state(IDLE) +{ + setObjectName(m_channelId); + + m_basebandSink = new NoiseFigureBaseband(this); + m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); + m_basebandSink->setChannel(this); + m_basebandSink->moveToThread(&m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); + m_deviceAPI->addChannelSinkAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +NoiseFigure::~NoiseFigure() +{ + qDebug("NoiseFigure::~NoiseFigure"); + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + + if (m_basebandSink->isRunning()) { + stop(); + } + + delete m_basebandSink; +} + +uint32_t NoiseFigure::getNumberOfDeviceStreams() const +{ + return m_deviceAPI->getNbSourceStreams(); +} + +void NoiseFigure::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +{ + (void) firstOfBurst; + m_basebandSink->feed(begin, end); +} + +void NoiseFigure::start() +{ + qDebug("NoiseFigure::start"); + + m_basebandSink->reset(); + m_basebandSink->startWork(); + m_thread.start(); + + DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency); + m_basebandSink->getInputMessageQueue()->push(dspMsg); + + NoiseFigureBaseband::MsgConfigureNoiseFigureBaseband *msg = NoiseFigureBaseband::MsgConfigureNoiseFigureBaseband::create(m_settings, true); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +void NoiseFigure::stop() +{ + qDebug("NoiseFigure::stop"); + m_basebandSink->stopWork(); + m_thread.quit(); + m_thread.wait(); +} + +void NoiseFigure::openVISADevice() +{ + m_visa.openDefault(); + m_session = m_visa.open(m_settings.m_visaDevice); +} + +void NoiseFigure::closeVISADevice() +{ + m_visa.close(m_session); + m_visa.closeDefault(); +} + +void NoiseFigure::processVISA(QStringList commands) +{ + if (m_session > 0) + { + for (int i = 0; i < commands.size(); i++) + { + QString command = commands[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(); + m_visa.viPrintf(m_session, cmd); + if (command.endsWith("?")) + { + char buf[1024] = ""; + m_visa.viScanf(m_session, "%t", buf); + qDebug() << "VISA <-: " << buf; + } + } + } + } +} + +// Calculate ENR at specified frequency +double NoiseFigure::calcENR(double frequency) +{ + int size = m_settings.m_enr.size(); + if (size >= 2) + { + int order = size > 3 ? 3 : size - 1; + std::vector x(size); + std::vector y(size); + for (int i = 0; i < size; i++) + { + x[i] = m_settings.m_enr[i]->m_frequency; + y[i] = m_settings.m_enr[i]->m_enr; + } + boost::math::barycentric_rational interpolant(std::move(x), std::move(y), order); + double enr = interpolant(frequency); + qDebug() << "ENR at " << frequency << " interpolated to " << enr; + return enr; + } + else if (size == 1) + { + return m_settings.m_enr[0]->m_enr; + } + else + { + return 0.0; + } +} + +// FSM for running measurements over multiple frequencies +void NoiseFigure::nextState() +{ + switch (m_state) + { + case IDLE: + if (m_settings.m_enr.size() < 1) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgFinished::create("ENRs has not been specified")); + } + return; + } + m_step = 0; + if (m_settings.m_frequencySpec == NoiseFigureSettings::LIST) + { + // Create list of frequencies from string + QRegExp separator("[( |,|\t|)]"); + QStringList freqStrings = m_settings.m_frequencies.trimmed().split(separator); + m_freqs.clear(); + for (int i = 0; i < freqStrings.size(); i++) + { + QString freqString = freqStrings[i].trimmed(); + if (!freqString.isEmpty()) + { + bool ok; + double freq = freqString.toDouble(&ok); + if (ok) { + m_freqs.append(freq * 1e6); + } else { + qDebug() << "NoiseFigure::nextState: Invalid frequency: " << freqString; + } + } + } + if (m_freqs.size() == 0) + { + qDebug() << "NoiseFigure::nextState: No frequencies in list"; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgFinished::create("No frequencies in list")); + } + return; + } + // Set start frequency and number of frequencies to step through + m_measurementFrequency = m_freqs[0]; + m_steps = m_freqs.size(); + } + else + { + if (m_settings.m_stopFrequency < m_settings.m_startFrequency) + { + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgFinished::create("Stop frequency must be greater or equal to start frequency")); + } + return; + } + // Set start frequency and number of frequencies to step through + m_measurementFrequency = m_settings.m_startFrequency * 1.e6; + if (m_settings.m_frequencySpec == NoiseFigureSettings::RANGE) { + m_steps = m_settings.m_steps; + } else { + m_steps = (m_settings.m_stopFrequency - m_settings.m_startFrequency) / m_settings.m_step + 1; + } + } + m_state = SET_FREQUENCY; + QTimer::singleShot(0, this, SLOT(nextState())); + break; + + case SET_FREQUENCY: + // Set radio centre frequency + if (ChannelWebAPIUtils::setCenterFrequency(getDeviceSetIndex(), m_measurementFrequency)) + { + qDebug() << "NoiseFigure::nextState: Set center frequency: " << m_measurementFrequency; + m_state = POWER_ON; + QTimer::singleShot(100, this, SLOT(nextState())); + } else + { + qDebug() << "NoiseFigure::nextState: Unable to set center frequency: " << m_measurementFrequency; + } + break; + + case POWER_ON: + // Power on noise source + powerOn(); + QTimer::singleShot(m_settings.m_powerDelay * 1000.0, this, SLOT(nextState())); + m_state = MEASURE_ON; + break; + + case MEASURE_ON: + // Start measurement of power when noise source is on + qDebug() << "NoiseFigure::nextState: Starting on measurement"; + m_basebandSink->startMeasurement(); + break; + + case POWER_OFF: + // Power off noise source + powerOff(); + QTimer::singleShot(m_settings.m_powerDelay * 1000.0, this, SLOT(nextState())); + m_state = MEASURE_OFF; + break; + + case MEASURE_OFF: + // Start measurement of power when noise source is off + qDebug() << "NoiseFigure::nextState: Starting off measurement"; + m_basebandSink->startMeasurement(); + break; + + case COMPLETE: + // Calculate noise figure and temperature using Y-factor method + double y = m_onPower - m_offPower; + double enr = calcENR(m_measurementFrequency/1e6); + double nf = 10.0*log10(pow(10.0, enr/10.0)/(pow(10.0, y/10.0)-1.0)); + double temp = 290.0*(pow(10.0, nf/10.0)-1.0); + + // Send result to GUI + if (getMessageQueueToGUI()) + { + MsgNFMeasurement *msg = MsgNFMeasurement::create(m_measurementFrequency/1e6, nf, temp, y, enr); + getMessageQueueToGUI()->push(msg); + } + + m_step++; + if (m_step >= m_steps) + { + // All frequencies measured + closeVISADevice(); + m_state = IDLE; + if (getMessageQueueToGUI()) { + getMessageQueueToGUI()->push(MsgFinished::create()); + } + } + else + { + // Move to next frequency + if (m_settings.m_frequencySpec == NoiseFigureSettings::LIST) { + m_measurementFrequency = m_freqs[m_step]; + } else if (m_settings.m_frequencySpec == NoiseFigureSettings::RANGE) { + m_measurementFrequency += 1e6 * (m_settings.m_stopFrequency - m_settings.m_startFrequency) / (m_settings.m_steps - 1); + } else { + m_measurementFrequency += m_settings.m_step * 1e6; + } + m_state = SET_FREQUENCY; + QTimer::singleShot(0, this, SLOT(nextState())); + } + break; + } +} + +void NoiseFigure::powerOn() +{ + QString command = m_settings.m_powerOnCommand.trimmed(); + if (!command.isEmpty()) { + QProcess::execute(command); + } + + QStringList commands = m_settings.m_powerOnSCPI.split("\n"); + processVISA(commands); +} + +void NoiseFigure::powerOff() +{ + QStringList commands = m_settings.m_powerOffSCPI.split("\n"); + processVISA(commands); + + QString command = m_settings.m_powerOffCommand.trimmed(); + if (!command.isEmpty()) { + QProcess::execute(command); + } +} + +bool NoiseFigure::handleMessage(const Message& cmd) +{ + if (MsgConfigureNoiseFigure::match(cmd)) + { + MsgConfigureNoiseFigure& cfg = (MsgConfigureNoiseFigure&) cmd; + qDebug() << "NoiseFigure::handleMessage: MsgConfigureNoiseFigure"; + 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() << "NoiseFigure::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + // Forward to GUI if any + if (m_guiMessageQueue) + { + rep = new DSPSignalNotification(notif); + m_guiMessageQueue->push(rep); + } + + return true; + } + else if (MsgPowerMeasurement::match(cmd)) + { + MsgPowerMeasurement& report = (MsgPowerMeasurement&)cmd; + + if (m_state == MEASURE_ON) + { + m_onPower = report.getPower(); + m_state = POWER_OFF; + nextState(); + } + else if (m_state == MEASURE_OFF) + { + m_offPower = report.getPower(); + m_state = COMPLETE; + nextState(); + } + + return true; + } + else if (MsgStartStop::match(cmd)) + { + if (m_state == IDLE) + { + openVISADevice(); + QTimer::singleShot(0, this, SLOT(nextState())); + } + else + { + // Set maximum step so test stops after current measurement + m_step = m_steps; + } + + return true; + } + else + { + return false; + } +} + +void NoiseFigure::applySettings(const NoiseFigureSettings& settings, bool force) +{ + qDebug() << "NoiseFigure::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_fftSize: " << settings.m_fftSize + << " m_fftCount: " << settings.m_fftCount + << " m_frequencySpec: " << settings.m_frequencySpec + << " m_startFrequency: " << settings.m_startFrequency + << " m_stopFrequency: " << settings.m_stopFrequency + << " m_steps: " << settings.m_steps + << " m_step: " << settings.m_step + << " m_frequencies: " << settings.m_frequencies + << " m_visaDevice: " << settings.m_visaDevice + << " m_powerOnSCPI: " << settings.m_powerOnSCPI + << " m_powerOffSCPI: " << settings.m_powerOffSCPI + << " m_powerOnCommand: " << settings.m_powerOnCommand + << " m_powerOffCommand: " << settings.m_powerOffCommand + << " m_powerDelay: " << settings.m_powerDelay + << " m_enr: " << settings.m_enr + << " 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 (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"); + } + + NoiseFigureBaseband::MsgConfigureNoiseFigureBaseband *msg = NoiseFigureBaseband::MsgConfigureNoiseFigureBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +QByteArray NoiseFigure::serialize() const +{ + return m_settings.serialize(); +} + +bool NoiseFigure::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureNoiseFigure *msg = MsgConfigureNoiseFigure::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureNoiseFigure *msg = MsgConfigureNoiseFigure::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int NoiseFigure::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setNoiseFigureSettings(new SWGSDRangel::SWGNoiseFigureSettings()); + response.getNoiseFigureSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int NoiseFigure::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + NoiseFigureSettings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigureNoiseFigure *msg = MsgConfigureNoiseFigure::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("NoiseFigure::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureNoiseFigure *msgToGUI = MsgConfigureNoiseFigure::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void NoiseFigure::webapiUpdateChannelSettings( + NoiseFigureSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("inputFrequencyOffset")) { + settings.m_inputFrequencyOffset = response.getNoiseFigureSettings()->getInputFrequencyOffset(); + } + if (channelSettingsKeys.contains("fftSize")) { + settings.m_fftSize = response.getNoiseFigureSettings()->getFftSize(); + } + if (channelSettingsKeys.contains("frequencySpec")) { + settings.m_frequencySpec = (NoiseFigureSettings::FrequencySpec)response.getNoiseFigureSettings()->getFrequencySpec(); + } + if (channelSettingsKeys.contains("startFrequency")) { + settings.m_startFrequency = response.getNoiseFigureSettings()->getStartFrequency(); + } + if (channelSettingsKeys.contains("stopFrequency")) { + settings.m_stopFrequency = response.getNoiseFigureSettings()->getStopFrequency(); + } + if (channelSettingsKeys.contains("steps")) { + settings.m_steps = response.getNoiseFigureSettings()->getSteps(); + } + if (channelSettingsKeys.contains("step")) { + settings.m_step = response.getNoiseFigureSettings()->getStep(); + } + if (channelSettingsKeys.contains("frequencies")) { + settings.m_frequencies = *response.getNoiseFigureSettings()->getFrequencies(); + } + if (channelSettingsKeys.contains("visaDevice")) { + settings.m_visaDevice = *response.getNoiseFigureSettings()->getVisaDevice(); + } + if (channelSettingsKeys.contains("powerOnSCPI")) { + settings.m_powerOnSCPI = *response.getNoiseFigureSettings()->getPowerOnScpi(); + } + if (channelSettingsKeys.contains("powerOffSCPI")) { + settings.m_powerOffSCPI = *response.getNoiseFigureSettings()->getPowerOffScpi(); + } + if (channelSettingsKeys.contains("powerOnCommand")) { + settings.m_powerOnCommand = *response.getNoiseFigureSettings()->getPowerOnCommand(); + } + if (channelSettingsKeys.contains("powerOffCommand")) { + settings.m_powerOffCommand = *response.getNoiseFigureSettings()->getPowerOffCommand(); + } + if (channelSettingsKeys.contains("powerDelay")) { + settings.m_powerDelay = response.getNoiseFigureSettings()->getPowerDelay(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getNoiseFigureSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getNoiseFigureSettings()->getTitle(); + } + if (channelSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getNoiseFigureSettings()->getStreamIndex(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getNoiseFigureSettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getNoiseFigureSettings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getNoiseFigureSettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getNoiseFigureSettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getNoiseFigureSettings()->getReverseApiChannelIndex(); + } +} + +void NoiseFigure::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const NoiseFigureSettings& settings) +{ + response.getNoiseFigureSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + + response.getNoiseFigureSettings()->setRgbColor(settings.m_rgbColor); + if (response.getNoiseFigureSettings()->getTitle()) { + *response.getNoiseFigureSettings()->getTitle() = settings.m_title; + } else { + response.getNoiseFigureSettings()->setTitle(new QString(settings.m_title)); + } + + response.getNoiseFigureSettings()->setStreamIndex(settings.m_streamIndex); + response.getNoiseFigureSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getNoiseFigureSettings()->getReverseApiAddress()) { + *response.getNoiseFigureSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getNoiseFigureSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getNoiseFigureSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getNoiseFigureSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getNoiseFigureSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void NoiseFigure::webapiReverseSendSettings(QList& channelSettingsKeys, const NoiseFigureSettings& 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 NoiseFigure::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const NoiseFigureSettings& settings, + bool force +) +{ + swgChannelSettings->setDirection(0); // Single sink (Rx) + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("NoiseFigure")); + swgChannelSettings->setNoiseFigureSettings(new SWGSDRangel::SWGNoiseFigureSettings()); + SWGSDRangel::SWGNoiseFigureSettings *swgNoiseFigureSettings = swgChannelSettings->getNoiseFigureSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgNoiseFigureSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("fftSize") || force) { + swgNoiseFigureSettings->setFftSize(settings.m_fftSize); + } + if (channelSettingsKeys.contains("fftCount") || force) { + swgNoiseFigureSettings->setFftCount(settings.m_fftCount); + } + if (channelSettingsKeys.contains("frequencySpec") || force) { + swgNoiseFigureSettings->setFrequencySpec((int)settings.m_frequencySpec); + } + if (channelSettingsKeys.contains("stopFrequency") || force) { + swgNoiseFigureSettings->setStopFrequency(settings.m_stopFrequency); + } + if (channelSettingsKeys.contains("steps") || force) { + swgNoiseFigureSettings->setSteps(settings.m_steps); + } + if (channelSettingsKeys.contains("step") || force) { + swgNoiseFigureSettings->setStep(settings.m_step); + } + if (channelSettingsKeys.contains("frequencies") || force) { + swgNoiseFigureSettings->setFrequencies(new QString(settings.m_frequencies)); + } + if (channelSettingsKeys.contains("visaDevice") || force) { + swgNoiseFigureSettings->setVisaDevice(new QString(settings.m_visaDevice)); + } + if (channelSettingsKeys.contains("powerOnSCPI") || force) { + swgNoiseFigureSettings->setPowerOnScpi(new QString(settings.m_powerOnSCPI)); + } + if (channelSettingsKeys.contains("powerOffSCPI") || force) { + swgNoiseFigureSettings->setPowerOffScpi(new QString(settings.m_powerOffSCPI)); + } + if (channelSettingsKeys.contains("powerOnCommand") || force) { + swgNoiseFigureSettings->setPowerOnCommand(new QString(settings.m_powerOnCommand)); + } + if (channelSettingsKeys.contains("powerOffCommand") || force) { + swgNoiseFigureSettings->setPowerOffCommand(new QString(settings.m_powerOffCommand)); + } + if (channelSettingsKeys.contains("powerDelay") || force) { + swgNoiseFigureSettings->setPowerDelay(settings.m_powerDelay); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgNoiseFigureSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgNoiseFigureSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("streamIndex") || force) { + swgNoiseFigureSettings->setStreamIndex(settings.m_streamIndex); + } +} + +void NoiseFigure::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "NoiseFigure::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("NoiseFigure::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/channelrx/noisefigure/noisefigure.h b/plugins/channelrx/noisefigure/noisefigure.h new file mode 100644 index 000000000..e0a1d753a --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigure.h @@ -0,0 +1,283 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_NOISEFIGURE_H +#define INCLUDE_NOISEFIGURE_H + +#include + +#include +#include +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelapi.h" +#include "util/message.h" +#include "util/visa.h" + +#include "noisefigurebaseband.h" +#include "noisefiguresettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class DeviceAPI; + +class NoiseFigure : public BasebandSampleSink, public ChannelAPI { + Q_OBJECT +public: + class MsgConfigureNoiseFigure : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const NoiseFigureSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureNoiseFigure* create(const NoiseFigureSettings& settings, bool force) + { + return new MsgConfigureNoiseFigure(settings, force); + } + + private: + NoiseFigureSettings m_settings; + bool m_force; + + MsgConfigureNoiseFigure(const NoiseFigureSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgPowerMeasurement : public Message { + MESSAGE_CLASS_DECLARATION + + public: + double getPower() const { return m_power; } + + static MsgPowerMeasurement* create(double power) + { + return new MsgPowerMeasurement(power); + } + + private: + double m_power; + + MsgPowerMeasurement(double power) : + Message(), + m_power(power) + { + } + }; + + class MsgNFMeasurement : public Message { + MESSAGE_CLASS_DECLARATION + + public: + double getFrequency() const { return m_frequency; } + double getNF() const { return m_nf; } + double getTemp() const { return m_temp; } + double getY() const { return m_y; } + double getENR() const { return m_enr; } + + static MsgNFMeasurement* create(double frequency, double nf, double temp, double y, double enr) + { + return new MsgNFMeasurement(frequency, nf, temp, y, enr); + } + + private: + double m_frequency; // In MHz + double m_nf; // In dB + double m_temp; // In Kelvin + double m_y; // In dB + double m_enr; // In dB + + MsgNFMeasurement(double frequency, double nf, double temp, double y, double enr) : + Message(), + m_frequency(frequency), + m_nf(nf), + m_temp(temp), + m_y(y), + m_enr(enr) + { + } + }; + + // Sent from GUI to start of stop a measurement + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgStartStop* create() + { + return new MsgStartStop(); + } + + private: + + MsgStartStop() : + Message() + { + } + }; + + // Sent to GUI to indicate measurements have finished + class MsgFinished : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + QString getErrorMessage() const { return m_errorMessage; } + + static MsgFinished* create() + { + QString noError; + return new MsgFinished(noError); + } + + static MsgFinished* create(const QString& errorMessage) + { + return new MsgFinished(errorMessage); + } + + private: + QString m_errorMessage; + + MsgFinished(const QString& errorMessage) : + Message(), + m_errorMessage(errorMessage) + { + } + }; + + NoiseFigure(DeviceAPI *deviceAPI); + virtual ~NoiseFigure(); + virtual void destroy() { delete this; } + + using BasebandSampleSink::feed; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual const QString& getURI() const { return getName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int getNbSinkStreams() const { return 1; } + virtual int getNbSourceStreams() const { return 0; } + + virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const + { + (void) streamIndex; + (void) sinkElseSource; + return 0; + } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const NoiseFigureSettings& settings); + + static void webapiUpdateChannelSettings( + NoiseFigureSettings& 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); + }*/ + + void openVISADevice(); + void closeVISADevice(); + void processVISA(QStringList commands); + void powerOn(); + void powerOff(); + double calcENR(double frequency); + + uint32_t getNumberOfDeviceStreams() const; + + static const char * const m_channelIdURI; + static const char * const m_channelId; + +private: + DeviceAPI *m_deviceAPI; + QThread m_thread; + NoiseFigureBaseband* m_basebandSink; + NoiseFigureSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + qint64 m_centerFrequency; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const NoiseFigureSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& channelSettingsKeys, const NoiseFigureSettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const NoiseFigureSettings& settings, + bool force + ); + + enum State { + IDLE, + SET_FREQUENCY, + POWER_ON, + MEASURE_ON, + POWER_OFF, + MEASURE_OFF, + COMPLETE + } m_state; + double m_measurementFrequency; // In Hz + QList m_freqs; // In Hz + int m_step; // Current frequency step + int m_steps; // Number of frequencies to test + double m_onPower; + double m_offPower; + ViSession m_session; + VISA m_visa; + +private slots: + void networkManagerFinished(QNetworkReply *reply); + void nextState(); + +}; + +#endif // INCLUDE_NOISEFIGURE_H diff --git a/plugins/channelrx/noisefigure/noisefigurebaseband.cpp b/plugins/channelrx/noisefigure/noisefigurebaseband.cpp new file mode 100644 index 000000000..e641b35b0 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurebaseband.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" + +#include "noisefigurebaseband.h" + +MESSAGE_CLASS_DEFINITION(NoiseFigureBaseband::MsgConfigureNoiseFigureBaseband, Message) + +NoiseFigureBaseband::NoiseFigureBaseband(NoiseFigure *aisDemod) : + m_sink(aisDemod), + m_running(false), + m_mutex(QMutex::Recursive) +{ + qDebug("NoiseFigureBaseband::NoiseFigureBaseband"); + + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownChannelizer(&m_sink); +} + +NoiseFigureBaseband::~NoiseFigureBaseband() +{ + m_inputMessageQueue.clear(); + + delete m_channelizer; +} + +void NoiseFigureBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); + m_sampleFifo.reset(); +} + +void NoiseFigureBaseband::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &NoiseFigureBaseband::handleData, + Qt::QueuedConnection + ); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; +} + +void NoiseFigureBaseband::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + QObject::disconnect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &NoiseFigureBaseband::handleData + ); + m_running = false; +} + +void NoiseFigureBaseband::setChannel(ChannelAPI *channel) +{ + m_sink.setChannel(channel); +} + +void NoiseFigureBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void NoiseFigureBaseband::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 NoiseFigureBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool NoiseFigureBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureNoiseFigureBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureNoiseFigureBaseband& cfg = (MsgConfigureNoiseFigureBaseband&) cmd; + qDebug() << "NoiseFigureBaseband::handleMessage: MsgConfigureNoiseFigureBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "NoiseFigureBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + setBasebandSampleRate(notif.getSampleRate()); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setChannelization(m_channelizer->getBasebandSampleRate(), 0); + + return true; + } + else + { + return false; + } +} + +void NoiseFigureBaseband::applySettings(const NoiseFigureSettings& settings, bool force) +{ + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +void NoiseFigureBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/noisefigure/noisefigurebaseband.h b/plugins/channelrx/noisefigure/noisefigurebaseband.h new file mode 100644 index 000000000..ca73f0bd5 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurebaseband.h @@ -0,0 +1,98 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_NOISEFIGUREBASEBAND_H +#define INCLUDE_NOISEFIGUREBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "dsp/scopevis.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "noisefiguresink.h" + +class DownChannelizer; +class ChannelAPI; +class NoiseFigure; + +class NoiseFigureBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureNoiseFigureBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const NoiseFigureSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureNoiseFigureBaseband* create(const NoiseFigureSettings& settings, bool force) + { + return new MsgConfigureNoiseFigureBaseband(settings, force); + } + + private: + NoiseFigureSettings m_settings; + bool m_force; + + MsgConfigureNoiseFigureBaseband(const NoiseFigureSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + NoiseFigureBaseband(NoiseFigure *aisDemod); + ~NoiseFigureBaseband(); + 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; } + void startMeasurement() { m_sink.startMeasurement(); } + +private: + SampleSinkFifo m_sampleFifo; + DownChannelizer *m_channelizer; + NoiseFigureSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + NoiseFigureSettings m_settings; + bool m_running; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void calculateOffset(NoiseFigureSink *sink); + void applySettings(const NoiseFigureSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_NOISEFIGUREBASEBAND_H diff --git a/plugins/channelrx/noisefigure/noisefigurecontroldialog.cpp b/plugins/channelrx/noisefigure/noisefigurecontroldialog.cpp new file mode 100644 index 000000000..f9f6ce922 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurecontroldialog.cpp @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "noisefigurecontroldialog.h" + +NoiseFigureControlDialog::NoiseFigureControlDialog(NoiseFigureSettings *settings, QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::NoiseFigureControlDialog) +{ + ui->setupUi(this); + ui->powerOnCommand->setText(settings->m_powerOnCommand); + ui->powerOffCommand->setText(settings->m_powerOffCommand); + ui->device->setText(settings->m_visaDevice); + ui->powerOnSCPI->setPlainText(settings->m_powerOnSCPI); + ui->powerOffSCPI->setPlainText(settings->m_powerOffSCPI); + ui->delay->setValue(settings->m_powerDelay); + VISA visa; + if (!visa.isAvailable()) + { + ui->device->setEnabled(false); + ui->powerOnSCPI->setEnabled(false); + ui->powerOffSCPI->setEnabled(false); + } +} + +NoiseFigureControlDialog::~NoiseFigureControlDialog() +{ + delete ui; +} + +void NoiseFigureControlDialog::accept() +{ + m_settings->m_powerOnCommand = ui->powerOnCommand->text(); + m_settings->m_powerOffCommand = ui->powerOffCommand->text(); + m_settings->m_visaDevice = ui->device->text(); + m_settings->m_powerOnSCPI = ui->powerOnSCPI->toPlainText(); + m_settings->m_powerOffSCPI = ui->powerOffSCPI->toPlainText(); + m_settings->m_powerDelay = ui->delay->value(); + QDialog::accept(); +} diff --git a/plugins/channelrx/noisefigure/noisefigurecontroldialog.h b/plugins/channelrx/noisefigure/noisefigurecontroldialog.h new file mode 100644 index 000000000..1a0fd322f --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurecontroldialog.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_NOISEFIGURECONTROLDIALOG_H +#define INCLUDE_NOISEFIGURECONTROLDIALOG_H + +#include "ui_NoiseFigureControlDialog.h" +#include "noisefiguresettings.h" + +class NoiseFigureControlDialog : public QDialog { + Q_OBJECT + +public: + explicit NoiseFigureControlDialog(NoiseFigureSettings *settings, QWidget* parent = 0); + ~NoiseFigureControlDialog(); + + NoiseFigureSettings *m_settings; + +private slots: + void accept(); + +private: + Ui::NoiseFigureControlDialog* ui; +}; + +#endif // INCLUDE_NOISEFIGURECONTROLDIALOG_H diff --git a/plugins/channelrx/noisefigure/noisefigurecontroldialog.ui b/plugins/channelrx/noisefigure/noisefigurecontroldialog.ui new file mode 100644 index 000000000..563b05655 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurecontroldialog.ui @@ -0,0 +1,223 @@ + + + NoiseFigureControlDialog + + + + 0 + 0 + 514 + 531 + + + + + Liberation Sans + 9 + + + + Noise Source Control Settings + + + + + + + 0 + 0 + + + + + + + Programs / scripts + + + + + + Power on + + + + + + + Program or script to execute to turn on the noise source's power + + + + + + + Program or script to execute to turn off the noise source's power + + + + + + + Power off + + + + + + + + + + + 0 + 0 + + + + VISA / SCPI + + + + + + + + Device + + + + + + + VISA device address to send SCPI commands to. +E.g: +USB0::0x1234::0xabcd::DP8C1234::0::INSTR +TCPIP0::192.168.0.10::inst0::INSTR + + + + + + + + + Power On SCPI + + + + + + + SCPI commands to power on noise source. +Lines beginning with # are comments + + + + + + + Power Off SCPI + + + + + + + SCPI commands to power off noise source + + + + + + + + + + Timing + + + + + + Delay in seconds betwen power on/off and measurement + + + + + + + Delay in seconds between the noise source being powered on or off and when the measurement starts + + + 3 + + + 100.000000000000000 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + powerOnCommand + powerOffCommand + device + powerOnSCPI + powerOffSCPI + delay + + + + + + + buttonBox + accepted() + NoiseFigureControlDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NoiseFigureControlDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/channelrx/noisefigure/noisefigureenrdialog.cpp b/plugins/channelrx/noisefigure/noisefigureenrdialog.cpp new file mode 100644 index 000000000..c979698cc --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigureenrdialog.cpp @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "noisefigureenrdialog.h" + +NoiseFigureENRDialog::NoiseFigureENRDialog(NoiseFigureSettings *settings, QWidget* parent) : + QDialog(parent), + m_settings(settings), + ui(new Ui::NoiseFigureENRDialog) +{ + ui->setupUi(this); + ui->enr->sortByColumn(0, Qt::AscendingOrder); + for (int i = 0; i < m_settings->m_enr.size(); i++) { + addRow( m_settings->m_enr[i]->m_frequency, m_settings->m_enr[i]->m_enr); + } +} + +NoiseFigureENRDialog::~NoiseFigureENRDialog() +{ + delete ui; +} + +void NoiseFigureENRDialog::accept() +{ + QDialog::accept(); + qDeleteAll(m_settings->m_enr); + m_settings->m_enr.clear(); + ui->enr->sortByColumn(0, Qt::AscendingOrder); + for (int i = 0; i < ui->enr->rowCount(); i++) + { + QTableWidgetItem *freqItem = ui->enr->item(i, ENR_COL_FREQ); + QTableWidgetItem *enrItem = ui->enr->item(i, ENR_COL_ENR); + double freqValue = freqItem->data(Qt::DisplayRole).toDouble(); + double enrValue = enrItem->data(Qt::DisplayRole).toDouble(); + + NoiseFigureSettings::ENR *enr = new NoiseFigureSettings::ENR(freqValue, enrValue); + m_settings->m_enr.append(enr); + } +} + +void NoiseFigureENRDialog::addRow(double freq, double enr) +{ + ui->enr->setSortingEnabled(false); + int row = ui->enr->rowCount(); + ui->enr->setRowCount(row + 1); + QTableWidgetItem *freqItem = new QTableWidgetItem(); + QTableWidgetItem *enrItem = new QTableWidgetItem(); + ui->enr->setItem(row, ENR_COL_FREQ, freqItem); + ui->enr->setItem(row, ENR_COL_ENR, enrItem); + freqItem->setData(Qt::DisplayRole, freq); + enrItem->setData(Qt::DisplayRole, enr); + ui->enr->setSortingEnabled(true); +} + +void NoiseFigureENRDialog::on_addRow_clicked() +{ + addRow(0.0, 0.0); +} + +void NoiseFigureENRDialog::on_deleteRow_clicked() +{ + QModelIndexList indexList = ui->enr->selectionModel()->selectedRows(); + if (!indexList.isEmpty()) + { + int row = indexList.at(0).row(); + ui->enr->removeRow(row); + } +} diff --git a/plugins/channelrx/noisefigure/noisefigureenrdialog.h b/plugins/channelrx/noisefigure/noisefigureenrdialog.h new file mode 100644 index 000000000..5442a796d --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigureenrdialog.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_NOISEFIGUREENRDIALOG_H +#define INCLUDE_NOISEFIGUREENRDIALOG_H + +#include "ui_NoiseFigureENRDialog.h" +#include "noisefiguresettings.h" + +class NoiseFigureENRDialog : public QDialog { + Q_OBJECT + +public: + explicit NoiseFigureENRDialog(NoiseFigureSettings *settings, QWidget* parent = 0); + ~NoiseFigureENRDialog(); + + NoiseFigureSettings *m_settings; + + enum ENRCol { + ENR_COL_FREQ, + ENR_COL_ENR + }; + +private slots: + void accept(); + void on_addRow_clicked(); + void on_deleteRow_clicked(); + +private: + Ui::NoiseFigureENRDialog* ui; + void addRow(double freq, double enr); +}; + +#endif // INCLUDE_NOISEFIGUREENRDIALOG_H diff --git a/plugins/channelrx/noisefigure/noisefigureenrdialog.ui b/plugins/channelrx/noisefigure/noisefigureenrdialog.ui new file mode 100644 index 000000000..a0986ffe2 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigureenrdialog.ui @@ -0,0 +1,158 @@ + + + NoiseFigureENRDialog + + + + 0 + 0 + 284 + 496 + + + + + Liberation Sans + 9 + + + + Excess Noise Ratio + + + + + + + 0 + 0 + + + + + + + Enter calibrated ENRs for noise source + + + + + + + ENR (Excess Noise Ratio) vs frequency table. Interpolation is used at frequencies not listed in the table. + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + Freq (MHz) + + + + + ENR (dB) + + + + + + + + + + Add row + + + + + + + + + + + Delete row + + + - + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + buttonBox + accepted() + NoiseFigureENRDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NoiseFigureENRDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/plugins/channelrx/noisefigure/noisefiguregui.cpp b/plugins/channelrx/noisefigure/noisefiguregui.cpp new file mode 100644 index 000000000..a1fae2d9a --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguregui.cpp @@ -0,0 +1,775 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "noisefiguregui.h" + +#include "device/deviceuiset.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "ui_noisefiguregui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "gui/basicchannelsettingsdialog.h" +#include "gui/devicestreamselectiondialog.h" +#include "gui/crightclickenabler.h" +#include "maincore.h" + +#include "noisefigure.h" +#include "noisefiguresink.h" +#include "noisefigurecontroldialog.h" +#include "noisefigureenrdialog.h" + +// Deligate for table to control precision used to display floating point values +class DecimalDelegate : public QStyledItemDelegate { + +public: + DecimalDelegate(int precision = 2) : + m_precision(precision) + { + } + + virtual QString displayText(const QVariant &value, const QLocale &locale) const override + { + return QString::number(value.toDouble(), 'f', m_precision); + } + +private: + int m_precision; + +}; + +void NoiseFigureGUI::resizeTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + // Trailing spaces are for sort arrow + int row = ui->results->rowCount(); + ui->results->setRowCount(row + 1); + ui->results->setItem(row, RESULTS_COL_FREQ, new QTableWidgetItem("2000.000")); + ui->results->setItem(row, RESULTS_COL_NF, new QTableWidgetItem("10.00")); + ui->results->setItem(row, RESULTS_COL_TEMP, new QTableWidgetItem("10000")); + ui->results->setItem(row, RESULTS_COL_Y, new QTableWidgetItem("10.00")); + ui->results->setItem(row, RESULTS_COL_ENR, new QTableWidgetItem("10.00")); + ui->results->resizeColumnsToContents(); + ui->results->removeRow(row); +} + +void NoiseFigureGUI::measurementReceived(NoiseFigure::MsgNFMeasurement& report) +{ + ui->results->setSortingEnabled(false); + int row = ui->results->rowCount(); + ui->results->setRowCount(row + 1); + + QTableWidgetItem *freqItem = new QTableWidgetItem(); + QTableWidgetItem *nfItem = new QTableWidgetItem(); + QTableWidgetItem *tempItem = new QTableWidgetItem(); + QTableWidgetItem *yItem = new QTableWidgetItem(); + QTableWidgetItem *enrItem = new QTableWidgetItem(); + ui->results->setItem(row, RESULTS_COL_FREQ, freqItem); + ui->results->setItem(row, RESULTS_COL_NF, nfItem); + ui->results->setItem(row, RESULTS_COL_TEMP, tempItem); + ui->results->setItem(row, RESULTS_COL_Y, yItem); + ui->results->setItem(row, RESULTS_COL_ENR, enrItem); + + freqItem->setData(Qt::DisplayRole, report.getFrequency()); + nfItem->setData(Qt::DisplayRole, report.getNF()); + tempItem->setData(Qt::DisplayRole, report.getTemp()); + yItem->setData(Qt::DisplayRole, report.getY()); + enrItem->setData(Qt::DisplayRole, report.getENR()); + + ui->results->setSortingEnabled(true); + plotChart(); +} + +void NoiseFigureGUI::plotChart() +{ + QChart *oldChart = m_chart; + + m_chart = new QChart(); + + m_chart->layout()->setContentsMargins(0, 0, 0, 0); + m_chart->setMargins(QMargins(1, 1, 1, 1)); + m_chart->setTheme(QChart::ChartThemeDark); + + // Create reference data series + QLineSeries *ref = nullptr; + if ((m_refData.size() > 0) && (ui->chartSelect->currentIndex() < m_refCols-1)) { + ref = new QLineSeries(); + for (int i = 0; i < m_refData.size() / m_refCols; i++) { + ref->append(m_refData[i*m_refCols], m_refData[i*m_refCols+ui->chartSelect->currentIndex()+1]); + } + QFileInfo fi(m_refFilename); + ref->setName(fi.completeBaseName()); + } else { + m_chart->legend()->hide(); + } + + // Create measurement data series + QLineSeries *series = new QLineSeries(); + series->setName("Measurement"); + for (int i = 0; i < ui->results->rowCount(); i++) + { + double freq = ui->results->item(i, RESULTS_COL_FREQ)->data(Qt::DisplayRole).toDouble(); + double val = ui->results->item(i, ui->chartSelect->currentIndex() + RESULTS_COL_NF)->data(Qt::DisplayRole).toDouble(); + + series->append(freq, val); + } + + QValueAxis *xAxis = new QValueAxis(); + QValueAxis *yAxis = new QValueAxis(); + + m_chart->addAxis(xAxis, Qt::AlignBottom); + m_chart->addAxis(yAxis, Qt::AlignLeft); + + xAxis->setTitleText("Frequency (MHz)"); + yAxis->setTitleText(ui->chartSelect->currentText()); + + m_chart->addSeries(series); + series->attachAxis(xAxis); + series->attachAxis(yAxis); + + if (ref) + { + m_chart->addSeries(ref); + ref->attachAxis(xAxis); + ref->attachAxis(yAxis); + } + + ui->chart->setChart(m_chart); + + delete oldChart; +} + +void NoiseFigureGUI::on_chartSelect_currentIndexChanged(int index) +{ + (void) index; + plotChart(); +} + +// Columns in table reordered +void NoiseFigureGUI::results_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_resultsColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void NoiseFigureGUI::results_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_resultsColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void NoiseFigureGUI::resultsColumnSelectMenu(QPoint pos) +{ + resultsMenu->popup(ui->results->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void NoiseFigureGUI::resultsColumnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->results->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu item +QAction *NoiseFigureGUI::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; +} + +NoiseFigureGUI* NoiseFigureGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + NoiseFigureGUI* gui = new NoiseFigureGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void NoiseFigureGUI::destroy() +{ + delete this; +} + +void NoiseFigureGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray NoiseFigureGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool NoiseFigureGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +// Call when sample rate or FFT size changes +void NoiseFigureGUI::updateBW() +{ + // We use a single bin for measurement + double bw = m_basebandSampleRate / (double)m_settings.m_fftSize; + ui->rfBWText->setText(QString("%1k").arg(bw / 1000.0, 0, 'f', 1)); + m_channelMarker.setBandwidth(bw); + // TODO: Could display total measurement time +} + +bool NoiseFigureGUI::handleMessage(const Message& message) +{ + if (NoiseFigure::MsgConfigureNoiseFigure::match(message)) + { + qDebug("NoiseFigureGUI::handleMessage: NoiseFigure::MsgConfigureNoiseFigure"); + const NoiseFigure::MsgConfigureNoiseFigure& cfg = (NoiseFigure::MsgConfigureNoiseFigure&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_basebandSampleRate = notif.getSampleRate(); + updateBW(); + return true; + } + else if (NoiseFigure::MsgNFMeasurement::match(message)) + { + NoiseFigure::MsgNFMeasurement& report = (NoiseFigure::MsgNFMeasurement&) message; + measurementReceived(report); + return true; + } + else if (NoiseFigure::MsgFinished::match(message)) + { + NoiseFigure::MsgFinished& report = (NoiseFigure::MsgFinished&) message; + ui->startStop->setChecked(false); + m_runningTest = false; + QString errorMessage = report.getErrorMessage(); + if (!errorMessage.isEmpty()) { + QMessageBox::critical(this, "Noise Figure", errorMessage); + } + return true; + } + + return false; +} + +void NoiseFigureGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void NoiseFigureGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void NoiseFigureGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +void NoiseFigureGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void NoiseFigureGUI::on_fftCount_valueChanged(int value) +{ + m_settings.m_fftCount = 10000 * value; + ui->fftCountText->setText(QString("%1k").arg(m_settings.m_fftCount / 1000)); + applySettings(); +} + +void NoiseFigureGUI::updateFreqWidgets() +{ + bool range = m_settings.m_frequencySpec == NoiseFigureSettings::RANGE; + bool step = m_settings.m_frequencySpec == NoiseFigureSettings::STEP; + bool list = m_settings.m_frequencySpec == NoiseFigureSettings::LIST; + ui->startLabel->setVisible(range || step); + ui->start->setVisible(range || step); + ui->stopLabel->setVisible(range || step); + ui->stop->setVisible(range || step); + ui->stepsLabel->setVisible(range); + ui->steps->setVisible(range); + ui->stepLabel->setVisible(step); + ui->step->setVisible(step); + ui->frequenciesLabel->setVisible(list); + ui->frequencies->setVisible(list); +} + +void NoiseFigureGUI::on_frequencySpec_currentIndexChanged(int index) +{ + m_settings.m_frequencySpec = (NoiseFigureSettings::FrequencySpec)index; + updateFreqWidgets(); + applySettings(); +} + +void NoiseFigureGUI::on_start_valueChanged(double value) +{ + m_settings.m_startFrequency = value; + applySettings(); +} + +void NoiseFigureGUI::on_stop_valueChanged(double value) +{ + m_settings.m_stopFrequency = value; + applySettings(); +} + +void NoiseFigureGUI::on_steps_valueChanged(int value) +{ + m_settings.m_steps = value; + applySettings(); +} + +void NoiseFigureGUI::on_step_valueChanged(double value) +{ + m_settings.m_step = value; + applySettings(); +} + +void NoiseFigureGUI::on_frequencies_editingFinished() +{ + m_settings.m_frequencies = ui->frequencies->text().trimmed(); + applySettings(); +} + +void NoiseFigureGUI::on_fftSize_currentIndexChanged(int index) +{ + m_settings.m_fftSize = 1 << (index + 6); + updateBW(); + applySettings(); +} + +void NoiseFigureGUI::on_startStop_clicked() +{ + // Check we have at least on ENR value + if (m_settings.m_enr.size() < 1) + { + QMessageBox::critical(this, "Noise Figure", "You must enter the ENR of the noise source for at least one frequency"); + return; + } + // Clear current results if starting a test + if (!m_runningTest) + { + on_clearResults_clicked(); + m_runningTest = true; + } + // Send message to start/stop test + NoiseFigure::MsgStartStop* message = NoiseFigure::MsgStartStop::create(); + m_noiseFigure->getInputMessageQueue()->push(message); +} + +// Save results in table to a CSV file +void NoiseFigureGUI::on_saveResults_clicked() +{ + // Get filename to save to + QFileDialog fileDialog(nullptr, "Select file to save results to", "", "*.csv"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + qDebug() << "NoiseFigureGUI: Saving results to " << fileNames; + QFile file(fileNames[0]); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Noise Figure", QString("Failed to open file %1").arg(fileNames[0])); + return; + } + QTextStream out(&file); + + // Create a CSV file from the values in the table + out << "Frequency (MHz),NF (dB),Noise Temp (K),Y (dB),ENR (dB)\n"; + for (int i = 0; i < ui->results->rowCount(); i++) + { + for (int j = 0; j < NOISEFIGURE_COLUMNS; j++) + { + double val = ui->results->item(i,j)->data(Qt::DisplayRole).toDouble(); + out << val << ","; + } + out << "\n"; + } + } + } +} + +// Open .csv containing reference data to plot +void NoiseFigureGUI::on_openReference_clicked() +{ + QFileDialog fileDialog(nullptr, "Open file to plot", "", "*.csv"); + if (fileDialog.exec()) + { + QStringList fileNames = fileDialog.selectedFiles(); + if (fileNames.size() > 0) + { + m_refFilename = fileNames[0]; + QFile file(m_refFilename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox::critical(this, "Noise Figure", QString("Failed to open file %1").arg(m_refFilename)); + return; + } + QTextStream in(&file); + + // Parse CSV file + QString headerLine = in.readLine(); + m_refCols = headerLine.split(",").size(); + m_refData.clear(); + QString line; + while (!(line = in.readLine()).isNull()) + { + QStringList cols = line.split(","); + for (int i = 0; i < m_refCols; i++) + { + if (i < cols.size()) { + bool ok; + m_refData.append(cols[i].toDouble(&ok)); + if (!ok) { + qDebug() << "NoiseFigureGUI::on_openReference_clicked: Error parsing " << cols[i] << " as a double"; + } + } else { + m_refData.append(0.0); + } + } + } + plotChart(); + } + } +} + +void NoiseFigureGUI::on_clearReference_clicked() +{ + m_refFilename = ""; + m_refData.clear(); + m_refCols = 0; + plotChart(); +} + +void NoiseFigureGUI::on_clearResults_clicked() +{ + ui->results->setRowCount(0); + plotChart(); +} + +void NoiseFigureGUI::on_enr_clicked() +{ + NoiseFigureENRDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) + { + applySettings(); + } +} + +void NoiseFigureGUI::on_control_clicked() +{ + NoiseFigureControlDialog dialog(&m_settings); + if (dialog.exec() == QDialog::Accepted) + { + applySettings(); + } +} + +void NoiseFigureGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void NoiseFigureGUI::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_noiseFigure->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(); +} + +NoiseFigureGUI::NoiseFigureGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::NoiseFigureGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_basebandSampleRate(1000000), + m_tickCount(0), + m_runningTest(false), + m_chart(nullptr) +{ + 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_noiseFigure = reinterpret_cast(rxChannel); + m_noiseFigure->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::yellow); + m_channelMarker.setBandwidth(m_basebandSampleRate / m_settings.m_fftSize); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle("Noise Figure"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + setTitleColor(m_channelMarker.getColor()); + m_settings.setChannelMarker(&m_channelMarker); + + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + // Resize the table using dummy data + resizeTable(); + // Allow user to reorder columns + ui->results->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->results->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + resultsMenu = new QMenu(ui->results); + for (int i = 0; i < ui->results->horizontalHeader()->count(); i++) + { + QString text = ui->results->horizontalHeaderItem(i)->text(); + resultsMenu->addAction(createCheckableItem(text, i, true, SLOT(resultsColumnSelectMenuChecked()))); + } + ui->results->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->results->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(resultsColumnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->results->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(results_sectionMoved(int, int, int))); + connect(ui->results->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(results_sectionResized(int, int, int))); + ui->results->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->results, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint))); + + ui->results->setItemDelegateForColumn(RESULTS_COL_NF, new DecimalDelegate(2)); + ui->results->setItemDelegateForColumn(RESULTS_COL_TEMP, new DecimalDelegate(0)); + ui->results->setItemDelegateForColumn(RESULTS_COL_Y, new DecimalDelegate(2)); + ui->results->setItemDelegateForColumn(RESULTS_COL_ENR, new DecimalDelegate(2)); + + displaySettings(); + applySettings(true); +} + +void NoiseFigureGUI::customContextMenuRequested(QPoint pos) +{ + QTableWidgetItem *item = ui->results->itemAt(pos); + if (item) + { + QMenu* tableContextMenu = new QMenu(ui->results); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + QAction* copyAction = new QAction("Copy", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + tableContextMenu->popup(ui->results->viewport()->mapToGlobal(pos)); + } +} + +NoiseFigureGUI::~NoiseFigureGUI() +{ + delete ui; +} + +void NoiseFigureGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void NoiseFigureGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + NoiseFigure::MsgConfigureNoiseFigure* message = NoiseFigure::MsgConfigureNoiseFigure::create( m_settings, force); + m_noiseFigure->getInputMessageQueue()->push(message); + } +} + +void NoiseFigureGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + + ui->fftCountText->setText(QString("%1k").arg(m_settings.m_fftCount / 1000)); + ui->fftCount->setValue(m_settings.m_fftCount / 10000); + + ui->frequencySpec->setCurrentIndex((int)m_settings.m_frequencySpec); + updateFreqWidgets(); + ui->start->setValue(m_settings.m_startFrequency); + ui->stop->setValue(m_settings.m_stopFrequency); + ui->steps->setValue(m_settings.m_steps); + ui->step->setValue(m_settings.m_step); + ui->frequencies->setText(m_settings.m_frequencies); + + ui->fftSize->setCurrentIndex(log2(m_settings.m_fftSize) - 6); + updateBW(); + + displayStreamIndex(); + + // Order and size columns + QHeaderView *header = ui->results->horizontalHeader(); + for (int i = 0; i < NOISEFIGURE_COLUMNS; i++) + { + bool hidden = m_settings.m_resultsColumnSizes[i] == 0; + header->setSectionHidden(i, hidden); + resultsMenu->actions().at(i)->setChecked(!hidden); + if (m_settings.m_resultsColumnSizes[i] > 0) { + ui->results->setColumnWidth(i, m_settings.m_resultsColumnSizes[i]); + } + header->moveSection(header->visualIndex(i), m_settings.m_resultsColumnIndexes[i]); + } + + blockApplySettings(false); +} + +void NoiseFigureGUI::displayStreamIndex() +{ + if (m_deviceUISet->m_deviceMIMOEngine) { + setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex)); + } else { + setStreamIndicator("S"); // single channel indicator + } +} + +void NoiseFigureGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void NoiseFigureGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void NoiseFigureGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_noiseFigure->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + ui->channelPowerMeter->levelChanged( + (100.0f + powDbAvg) / 100.0f, + (100.0f + powDbPeak) / 100.0f, + nbMagsqSamples); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); + } + + m_tickCount++; +} diff --git a/plugins/channelrx/noisefigure/noisefiguregui.h b/plugins/channelrx/noisefigure/noisefiguregui.h new file mode 100644 index 000000000..337e39f32 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguregui.h @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_NOISEFIGUREGUI_H +#define INCLUDE_NOISEFIGUREGUI_H + +#include +#include +#include +#include + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "util/messagequeue.h" + +#include "noisefiguresettings.h" +#include "noisefigure.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +class NoiseFigure; +class NoiseFigureGUI; + +namespace Ui { + class NoiseFigureGUI; +} + +using namespace QtCharts; + +class NoiseFigureGUI : public ChannelGUI { + Q_OBJECT + +public: + static NoiseFigureGUI* 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::NoiseFigureGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + NoiseFigureSettings m_settings; + bool m_doApplySettings; + + NoiseFigure* m_noiseFigure; + int m_basebandSampleRate; + uint32_t m_tickCount; + bool m_runningTest; + MessageQueue m_inputMessageQueue; + + QMenu *resultsMenu; // m_refData; + int m_refCols; + + explicit NoiseFigureGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~NoiseFigureGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void displayStreamIndex(); + bool handleMessage(const Message& message); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void resizeTable(); + QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot); + void updateBW(); + void updateFreqWidgets(); + void measurementReceived(NoiseFigure::MsgNFMeasurement& report); + void plotChart(); + + enum MessageCol { + RESULTS_COL_FREQ, + RESULTS_COL_NF, + RESULTS_COL_TEMP, + RESULTS_COL_Y, + RESULTS_COL_ENR + }; + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_fftCount_valueChanged(int value); + void on_frequencySpec_currentIndexChanged(int index); + void on_start_valueChanged(double value); + void on_stop_valueChanged(double value); + void on_steps_valueChanged(int value); + void on_step_valueChanged(double value); + void on_frequencies_editingFinished(); + void on_fftSize_currentIndexChanged(int index); + void on_startStop_clicked(); + void on_saveResults_clicked(); + void on_clearResults_clicked(); + void on_enr_clicked(); + void on_control_clicked(); + void on_chartSelect_currentIndexChanged(int index); + void on_openReference_clicked(); + void on_clearReference_clicked(); + void results_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void results_sectionResized(int logicalIndex, int oldSize, int newSize); + void resultsColumnSelectMenu(QPoint pos); + void resultsColumnSelectMenuChecked(bool checked = false); + void customContextMenuRequested(QPoint point); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void tick(); +}; + +#endif // INCLUDE_NOISEFIGUREGUI_H diff --git a/plugins/channelrx/noisefigure/noisefiguregui.ui b/plugins/channelrx/noisefigure/noisefiguregui.ui new file mode 100644 index 000000000..4898fad03 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguregui.ui @@ -0,0 +1,868 @@ + + + NoiseFigureGUI + + + + 0 + 0 + 404 + 642 + + + + + 0 + 0 + + + + + 352 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + AIS Demodulator + + + + + + + + 0 + 0 + 390 + 151 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Measurement frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + Qt::Horizontal + + + + + + + + + FFT Size + + + + + + + + 50 + 0 + + + + FFT size (number of bins) + + + 64 + + + + 64 + + + + + 128 + + + + + 256 + + + + + 512 + + + + + 1k + + + + + + + + Qt::Vertical + + + + + + + BW + + + + + + + + 30 + 0 + + + + Measurement bandwidth as determined by width of a single FFT bin + + + 10.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + FFTs to average + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Number of FFT outputs to average to calculate power + + + 1 + + + 10 + + + 1 + + + 1 + + + 10 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 2.4k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + 68 + 0 + + + + How to specify frequencies to measure NF at + + + + Range + + + + + Step + + + + + List + + + + + + + + Start + + + + + + + Start frequency in MHz + + + 3 + + + 10000.000000000000000 + + + + + + + Stop + + + + + + + Stop frequency in MHz + + + 3 + + + 10000.000000000000000 + + + + + + + Steps + + + + + + + Number of frequencies to measure at between start and stop inclusive + + + 1 + + + 10000 + + + + + + + Step + + + + + + + Frequency step in MHz + + + 3 + + + 0.001000000000000 + + + 10000.000000000000000 + + + + + + + Freqs + + + + + + + List of frequencies in MHz to measure NF at + + + + + + + + + Qt::Horizontal + + + + + + + + + Start/stop noise figure measurement + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + Save results to a .csv file + + + + + + + :/save.png:/save.png + + + + + + + Clear results + + + + + + + :/bin.png:/bin.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Open ENR dialog + + + + + + + :/listing.png:/listing.png + + + + + + + Open noise source control dialog + + + + + + + :/link.png:/link.png + + + + + + + + + + + 0 + 450 + 391 + 171 + + + + + 0 + 0 + + + + Results + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Received packets + + + QAbstractItemView::NoEditTriggers + + + + Freq (MHz) + + + Frequency in MHz the noise figure was measured at + + + + + NF (dB) + + + Noise figure in dB + + + + + T (K) + + + Noise temperature in Kelvin + + + + + Y (dB) + + + Y factor in dB + + + + + ENR (dB) + + + Excess noise ratio of the noise source in dB + + + + + + + + + + 0 + 160 + 391 + 268 + + + + + 0 + 0 + + + + + 200 + 200 + + + + Chart + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Select data to plot on chart + + + + Noise Figure (dB) + + + + + Noise Temperature (K) + + + + + Y (dB) + + + + + ENR (dB) + + + + + + + + Open .csv file with reference data + + + + + + + :/load.png:/load.png + + + + + + + Clear reference data + + + + + + + :/bin.png:/bin.png + + + + + + + + + + 300 + 250 + + + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + QChartView + QGraphicsView +
QtCharts
+
+
+ + deltaFrequency + fftSize + fftCount + start + stop + steps + startStop + saveResults + clearResults + enr + control + chartSelect + chart + results + + + + + +
diff --git a/plugins/channelrx/noisefigure/noisefigureplugin.cpp b/plugins/channelrx/noisefigure/noisefigureplugin.cpp new file mode 100644 index 000000000..3a71b68bd --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigureplugin.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 "noisefiguregui.h" +#endif +#include "noisefigure.h" +#include "noisefigurewebapiadapter.h" +#include "noisefigureplugin.h" + +const PluginDescriptor NoiseFigurePlugin::m_pluginDescriptor = { + NoiseFigure::m_channelId, + QStringLiteral("Noise Figure"), + QStringLiteral("6.13.1"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +NoiseFigurePlugin::NoiseFigurePlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& NoiseFigurePlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void NoiseFigurePlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerRxChannel(NoiseFigure::m_channelIdURI, NoiseFigure::m_channelId, this); +} + +void NoiseFigurePlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + NoiseFigure *instance = new NoiseFigure(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* NoiseFigurePlugin::createRxChannelGUI( + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) const +{ + (void) deviceUISet; + (void) rxChannel; + return 0; +} +#else +ChannelGUI* NoiseFigurePlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const +{ + return NoiseFigureGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +ChannelWebAPIAdapter* NoiseFigurePlugin::createChannelWebAPIAdapter() const +{ + return new NoiseFigureWebAPIAdapter(); +} diff --git a/plugins/channelrx/noisefigure/noisefigureplugin.h b/plugins/channelrx/noisefigure/noisefigureplugin.h new file mode 100644 index 000000000..45befc07d --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigureplugin.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_NOISEFIGUREPLUGIN_H +#define INCLUDE_NOISEFIGUREPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class NoiseFigurePlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.noisefigure") + +public: + explicit NoiseFigurePlugin(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_NOISEFIGUREPLUGIN_H diff --git a/plugins/channelrx/noisefigure/noisefiguresettings.cpp b/plugins/channelrx/noisefigure/noisefiguresettings.cpp new file mode 100644 index 000000000..c85850181 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguresettings.cpp @@ -0,0 +1,222 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "noisefiguresettings.h" + +NoiseFigureSettings::NoiseFigureSettings() : + m_channelMarker(0) +{ + resetToDefaults(); +} + +#define DEFAULT_FREQUENCIES "430 435 440" +#define DEFAULT_VISA_DEVICE "USB0::0x1AB1::0x0E11::DP8C155102576::0::INSTR" +#define DEFAULT_POWER_ON "#:SOURCE1:VOLTage 28\n:OUTPut:STATe CH1,ON" +#define DEFAULT_POWER_OFF ":OUTPut:STATe CH1,OFF" + +void NoiseFigureSettings::resetToDefaults() +{ + m_inputFrequencyOffset = 0; + m_fftSize = 64; + m_fftCount = 20000.0f; + m_frequencySpec = RANGE; + m_startFrequency = 430.0; + m_stopFrequency = 440.0; + m_steps = 3; + m_step = 5.0f; + m_frequencies = DEFAULT_FREQUENCIES; + m_visaDevice = DEFAULT_VISA_DEVICE; + m_powerOnSCPI = DEFAULT_POWER_ON; + m_powerOffSCPI = DEFAULT_POWER_OFF; + m_powerOnCommand = ""; + m_powerOffCommand = ""; + m_powerDelay = 0.5; + qDeleteAll(m_enr); + m_enr << new ENR(1000.0, 15.0); + m_rgbColor = QColor(0, 100, 200).rgb(); + m_title = "Noise Figure"; + 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 < NOISEFIGURE_COLUMNS; i++) + { + m_resultsColumnIndexes[i] = i; + m_resultsColumnSizes[i] = -1; // Autosize + } +} + +QByteArray NoiseFigureSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, m_fftSize); + s.writeFloat(3, m_fftCount); + + s.writeS32(4, (int)m_frequencySpec); + s.writeDouble(5, m_startFrequency); + s.writeDouble(6, m_stopFrequency); + s.writeS32(7, m_steps); + s.writeDouble(8, m_step); + s.writeString(9, m_frequencies); + + s.writeString(10, m_visaDevice); + s.writeString(11, m_powerOnSCPI); + s.writeString(12, m_powerOffSCPI); + s.writeString(13, m_powerOffCommand); + s.writeString(14, m_powerOffCommand); + s.writeDouble(15, m_powerDelay); + + s.writeBlob(16, serializeENRs(m_enr)); + + s.writeU32(17, m_rgbColor); + s.writeString(18, m_title); + if (m_channelMarker) { + s.writeBlob(19, m_channelMarker->serialize()); + } + s.writeS32(20, m_streamIndex); + s.writeBool(21, m_useReverseAPI); + s.writeString(22, m_reverseAPIAddress); + s.writeU32(23, m_reverseAPIPort); + s.writeU32(24, m_reverseAPIDeviceIndex); + s.writeU32(25, m_reverseAPIChannelIndex); + + for (int i = 0; i < NOISEFIGURE_COLUMNS; i++) { + s.writeS32(100 + i, m_resultsColumnIndexes[i]); + } + for (int i = 0; i < NOISEFIGURE_COLUMNS; i++) { + s.writeS32(200 + i, m_resultsColumnSizes[i]); + } + + return s.final(); +} + +bool NoiseFigureSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + QByteArray blob; + + d.readS32(1, &m_inputFrequencyOffset, 0); + d.readS32(2, &m_fftSize, 64); + d.readFloat(3, &m_fftCount, 10000.0f); + + d.readS32(4, (int*)&m_frequencySpec, NoiseFigureSettings::RANGE); + d.readDouble(5, &m_startFrequency, 430.0); + d.readDouble(6, &m_stopFrequency, 440.0); + d.readS32(7, &m_steps, 3); + d.readDouble(8, &m_step, 5.0); + d.readString(9, &m_frequencies, DEFAULT_FREQUENCIES); + + d.readString(10, &m_visaDevice, DEFAULT_VISA_DEVICE); + d.readString(11, &m_powerOnSCPI, DEFAULT_POWER_ON); + d.readString(12, &m_powerOffSCPI, DEFAULT_POWER_OFF); + d.readString(13, &m_powerOnCommand, ""); + d.readString(14, &m_powerOffCommand, ""); + d.readDouble(15, &m_powerDelay, 0.5); + + d.readBlob(16, &blob); + deserializeENRs(blob, m_enr); + + d.readU32(17, &m_rgbColor, QColor(0, 100, 200).rgb()); + d.readString(18, &m_title, "Noise Figure"); + d.readBlob(19, &bytetmp); + if (m_channelMarker) { + m_channelMarker->deserialize(bytetmp); + } + d.readS32(20, &m_streamIndex, 0); + d.readBool(21, &m_useReverseAPI, false); + d.readString(22, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(23, &utmp, 0); + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + d.readU32(24, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(25, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < NOISEFIGURE_COLUMNS; i++) { + d.readS32(100 + i, &m_resultsColumnIndexes[i], i); + } + for (int i = 0; i < NOISEFIGURE_COLUMNS; i++) { + d.readS32(200 + i, &m_resultsColumnSizes[i], -1); + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +QDataStream& operator<<(QDataStream& out, const NoiseFigureSettings::ENR* enr) +{ + out << enr->m_frequency; + out << enr->m_enr; + return out; +} + +QDataStream& operator>>(QDataStream& in, NoiseFigureSettings::ENR*& enr) +{ + enr = new NoiseFigureSettings::ENR(); + in >> enr->m_frequency; + in >> enr->m_enr; + return in; +} + +QByteArray NoiseFigureSettings::serializeENRs(QList enrs) const +{ + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << enrs; + delete stream; + return data; +} + +void NoiseFigureSettings::deserializeENRs(const QByteArray& data, QList& enrs) +{ + QDataStream *stream = new QDataStream(data); + (*stream) >> enrs; + delete stream; +} diff --git a/plugins/channelrx/noisefigure/noisefiguresettings.h b/plugins/channelrx/noisefigure/noisefiguresettings.h new file mode 100644 index 000000000..a7eea22c3 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguresettings.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_NOISEFIGURESETTINGS_H +#define INCLUDE_NOISEFIGURESETTINGS_H + +#include +#include +#include + +#include "dsp/dsptypes.h" + +class Serializable; + +// Number of columns in the table +#define NOISEFIGURE_COLUMNS 5 + +struct NoiseFigureSettings +{ + struct ENR + { + double m_frequency; //!< Frequency in MHz + double m_enr; //!< ENR in dB + ENR() : + m_frequency(0.0), + m_enr(0.0) + { } + ENR(double frequency, double enr) : + m_frequency(frequency), + m_enr(enr) + { } + }; + + qint32 m_inputFrequencyOffset; + int m_fftSize; + Real m_fftCount; //!< Number of FFT bins to average + + enum FrequencySpec { + RANGE, + STEP, + LIST + } m_frequencySpec; + double m_startFrequency; + double m_stopFrequency; + int m_steps; + double m_step; + QString m_frequencies; + + QString m_visaDevice; + QString m_powerOnSCPI; + QString m_powerOffSCPI; + QString m_powerOnCommand; + QString m_powerOffCommand; + double m_powerDelay; // m_enr; + + quint32 m_rgbColor; + QString m_title; + Serializable *m_channelMarker; + int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; + + int m_resultsColumnIndexes[NOISEFIGURE_COLUMNS];//!< How the columns are ordered in the table + int m_resultsColumnSizes[NOISEFIGURE_COLUMNS]; //!< Size of the columns in the table + + NoiseFigureSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + QByteArray serializeENRs(QList enrs) const; + void deserializeENRs(const QByteArray& data, QList& enrs); +}; + +#endif /* INCLUDE_NOISEFIGURESETTINGS_H */ diff --git a/plugins/channelrx/noisefigure/noisefiguresink.cpp b/plugins/channelrx/noisefigure/noisefiguresink.cpp new file mode 100644 index 000000000..035fe2995 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguresink.cpp @@ -0,0 +1,154 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "dsp/dspengine.h" + +#include "noisefigure.h" +#include "noisefiguresink.h" + +NoiseFigureSink::NoiseFigureSink(NoiseFigure *noiseFigure) : + m_noiseFigure(noiseFigure), + m_channelSampleRate(48000), + m_fftSequence(-1), + m_fft(nullptr), + m_fftCounter(0), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_messageQueueToChannel(nullptr), + m_powerSum(0.0), + m_count(0), + m_enabled(false) +{ + m_magsq = 0.0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, 0, true); +} + +NoiseFigureSink::~NoiseFigureSink() +{ +} + +void NoiseFigureSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + processOneSample(c); + } +} + +void NoiseFigureSink::processOneSample(Complex &ci) +{ + // 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 (note no windowing as input should be broadband noise) + m_fft->transform(); + m_fftCounter = 0; + + // Calculate power in FFT bin selected by input frequency offset + double frequencyResolution = m_channelSampleRate / (double)m_settings.m_fftSize; + int bin; + if (m_settings.m_inputFrequencyOffset >= 0) { + bin = m_settings.m_inputFrequencyOffset / frequencyResolution; + } else { + bin = m_settings.m_fftSize + m_settings.m_inputFrequencyOffset / frequencyResolution; + } + Complex c = m_fft->out()[bin]; + Real v = c.real() * c.real() + c.imag() * c.imag(); + + // Calculate average and peak levels for level meter + Real magsq = v / (m_settings.m_fftSize*m_settings.m_fftSize); + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + m_magsqCount++; + + if (m_enabled) + { + // Average power for measurement + m_powerSum += v; + m_count++; + if (m_count == m_settings.m_fftCount) + { + // Convert average to dB + // This is 10*log10(p/(1/fftSize)^2) optimised to not use log10 in the loop + const Real mult = (10.0f / log2(10.0f)); // ~3.01 + Real ofs = 20.0f * log10f(1.0f / m_settings.m_fftSize); + Real avg = mult * log2f(m_powerSum / m_count) + ofs; + + // Send NF results to channel + if (getMessageQueueToChannel()) + { + NoiseFigure::MsgPowerMeasurement *msg = NoiseFigure::MsgPowerMeasurement::create(avg); + getMessageQueueToChannel()->push(msg); + } + + // Prepare for a new measurement + m_powerSum = 0.0; + m_count = 0; + m_enabled = false; + } + } + } +} + +void NoiseFigureSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "NoiseFigureSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + m_channelSampleRate = channelSampleRate; +} + +void NoiseFigureSink::applySettings(const NoiseFigureSettings& settings, bool force) +{ + qDebug() << "NoiseFigureSink::applySettings:" + << " force: " << force; + + 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; + } + + if ((settings.m_fftCount != m_settings.m_fftCount) || force) + { + m_powerSum = 0.0; + m_count = 0; + } + + m_settings = settings; +} diff --git a/plugins/channelrx/noisefigure/noisefiguresink.h b/plugins/channelrx/noisefigure/noisefiguresink.h new file mode 100644 index 000000000..0f35eb003 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefiguresink.h @@ -0,0 +1,112 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_NOISEFIGURESINK_H +#define INCLUDE_NOISEFIGURESINK_H + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/fftfactory.h" +#include "dsp/fftengine.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" + +#include "noisefiguresettings.h" + +#include +#include +#include + +class ChannelAPI; +class NoiseFigure; + +class NoiseFigureSink : public ChannelSampleSink { +public: + NoiseFigureSink(NoiseFigure *aisDemod); + ~NoiseFigureSink(); + + 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 NoiseFigureSettings& settings, bool force = false); + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + void setChannel(ChannelAPI *channel) { m_channel = channel; } + void startMeasurement() { m_enabled = true; } + + 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; + }; + + NoiseFigure *m_noiseFigure; + NoiseFigureSettings m_settings; + ChannelAPI *m_channel; + int m_channelSampleRate; + + int m_fftSequence; + FFTEngine *m_fft; + int m_fftCounter; + + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MessageQueue *m_messageQueueToChannel; + + MovingAverageUtil m_movingAverage; + + double m_powerSum; + int m_count; + bool m_enabled; + + void processOneSample(Complex &ci); + MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } + void sampleToScope(Complex sample); +}; + +#endif // INCLUDE_NOISEFIGURESINK_H diff --git a/plugins/channelrx/noisefigure/noisefigurewebapiadapter.cpp b/plugins/channelrx/noisefigure/noisefigurewebapiadapter.cpp new file mode 100644 index 000000000..3c5af1a62 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurewebapiadapter.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 "noisefigure.h" +#include "noisefigurewebapiadapter.h" + +NoiseFigureWebAPIAdapter::NoiseFigureWebAPIAdapter() +{} + +NoiseFigureWebAPIAdapter::~NoiseFigureWebAPIAdapter() +{} + +int NoiseFigureWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setNoiseFigureSettings(new SWGSDRangel::SWGNoiseFigureSettings()); + response.getNoiseFigureSettings()->init(); + NoiseFigure::webapiFormatChannelSettings(response, m_settings); + + return 200; +} + +int NoiseFigureWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; + (void) errorMessage; + NoiseFigure::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response); + + return 200; +} diff --git a/plugins/channelrx/noisefigure/noisefigurewebapiadapter.h b/plugins/channelrx/noisefigure/noisefigurewebapiadapter.h new file mode 100644 index 000000000..ea3825ca5 --- /dev/null +++ b/plugins/channelrx/noisefigure/noisefigurewebapiadapter.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_NOISEFIGURE_WEBAPIADAPTER_H +#define INCLUDE_NOISEFIGURE_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "noisefiguresettings.h" + +/** + * Standalone API adapter only for the settings + */ +class NoiseFigureWebAPIAdapter : public ChannelWebAPIAdapter { +public: + NoiseFigureWebAPIAdapter(); + virtual ~NoiseFigureWebAPIAdapter(); + + 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: + NoiseFigureSettings m_settings; +}; + +#endif // INCLUDE_NOISEFIGURE_WEBAPIADAPTER_H diff --git a/plugins/channelrx/noisefigure/readme.md b/plugins/channelrx/noisefigure/readme.md new file mode 100644 index 000000000..770556528 --- /dev/null +++ b/plugins/channelrx/noisefigure/readme.md @@ -0,0 +1,156 @@ +

Noise Figure plugin

+ +

Introduction

+ +This plugin can be used to calculate the noise figure (NF) of a receiver (SDR as well as optionally one or more LNAs). It uses the Y-factor method and requires a calibrated noise source. + +The noise figure of a receiver is important, as it determines the noise floor: Noise floor (dBm) = 10*log10(1000kT)+NF+10*log10(BW) + +The noise figure will vary with frequency and gain settings. Typically, the noise figure will increase with frequency and decrease with gain. When measuring noise figure, AGC should be disabled. + +

Interface

+ +![Noise figure plugin GUI](../../../doc/img/NoiseFigure_plugin.png) + +

1: Measurement frequency shift from center

+ +Use the wheels to adjust the measurement frequency 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. + +Many SDRs have a large DC offset, which can significantly interfer with the NF measurement, so this setting should be used to move the measurement frequency away from the radio's center frequency or any other spurs. + +

2: Channel power

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

3: Level meter in dB

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

4: FFT size

+ +Determines the size (number of points) of the FFT used to measure noise power. Only a single output bin of the FFT is used, which is selected by (1). + +

5: BW

+ +Displays the measurement bandwidth in kHz as determined by (2). + +

6: FFTs to average

+ +Determines the number of FFTs that are used to measure the average power. The more values that are used in the average, the more accurate the result will be, at the expense of increase test time. + +

7: Frequency range

+ +Specifies the frequencies to measure the noise figure at. This can be specified as a: + +* Range - Specify start frequency in MHz, stop frequency in MHz and the number of steps. 100, 200, 5, would measure at 100MHz, 125MHz, 150MHz, 175MHz and 200MHz. +* Step - Specify start frequency in MHz, stop frequency in MHz and the step frequency in MHz. 100, 200, 25, would measure at 100MHz, 125MHz, 150MHz, 175MHz and 200MHz. +* List - Specify a space or comma separated list of frequencies in MHz. "100 125 150 175 200", would measure at 100MHz, 125MHz, 150MHz, 175MHz and 200MHz. + +

8: Start/stop noise figure measurement

+ +Starts or stops the noise figure measurement. When starting a new measurement, existing results are cleared. + +

9: Save results

+ +Saves the results in the table to a CSV file. + +

10: Clear results

+ +Clears the current results from the table and chart. + +

11: Open ENR dialog

+ +Opens the ENR dialog to allow entering the Excess Noise Ratios (ENRs) for noise source. ENR specifies the difference in noise source power output in dB from when the source is powered off compared to when it is powered on. +This typically varies with frequency, so values should be entered for each frequency. When a measurement is attempted at a frequency for which a value is not specified, an interpolated value will be used. +Barycentric Rational Interpolation is used. + +![Noise figure ENR dialog](../../../doc/img/NoiseFigure_plugin_enr.png) + +

12: Open Noise Source Control dialog

+ +Opens the noise source control dialog, to allow setting how the plugin turns the power to the noise source off and on. Two control methods are supported: A program or script can be run to turn the power on or off, +or the VISA library (if available) can be used to send SCPI commands to a programmable power supply or other test equipment. If a VISA library is not found, the VISA and SCPI fields will be disabled. + +The delay setting determines how long after the noise source's power is turned on or off, before the noise figure measurement starts. This should be long enough to allow the noise source output to settle, but +not too long so that tests over a large number of frequencies take a long time to run. The ideal value will vary with the noise source and power supply. + +![Noise source control dialog](../../../doc/img/NoiseFigure_plugin_control.png) + +

13: Results Table

+ +Displays measurement results. + +* Frequency - Frequency of the measurement in MHz. +* NF - Calculated noise figure in dB. +* T - Calculated noise temperature in Kelvin with a reference temperature of 290K. +* Y - Measured Y factor in dB (difference in measured power when the noise source is on and off). +* ENR - Excess noise factor of the noise source in dB. + +![Noise figure results table](../../../doc/img/NoiseFigure_plugin_results.png) + +

14: Results Chart

+ +Plots the results (NF, T or Y) vs frequency as a line chart. + +

15: Open reference data

+ +A set of reference data in CSV format can be loaded for comparisons with the measurement results. The first column of the CSV file should contain frequency and the second the noise figure in dB. The first row should contain a header (E.g. "Frequency,NF" allthough the exact text is ignored). + +![SDRPlay Duo Noise figure comparison](../../../doc/img/NoiseFigure_plugin_duo_comparison.png) + +

16: Clear reference data

+ +Clears the reference data, so the chart only plots the measured data. + +

Usage

+ +A typical h/w setup for measuring the noise figure of a receiver is shown below: + +![H/W setup](../../../doc/img/NoiseFigure_plugin_hw_setup.png) + +A DC blocking capacitor at the output of the noise source for SDRs with a bias tee is recommended. + +The noise source may be a device from the 346 family (E.g. Keysight 346B or NoiseCom NC346), but also can be a lower cost device that is supplied with accurate ENR calibration data. +(Inaccurate ENR values can significantly effect the calculated NF). +The ENR calibration data indicates the difference in power output when the noise source is powered off compared with when it is powered on. As the first setup step, this calibration data should +be entered in to the ENR dialog (11). + +Next, we need to setup how the SDRangel powers on and off the noise source. This is set in the Noise Source Control Dialog (12). +For a 346 device, a programmable power supply outputting 28V would be used. Providing the VISA libraries are installed (see below), we can send SCPI commands to enable and disable the PSU's output. +As an example, for a Rigol DP832, we can set the channel 1 output to be 28V and enable it, with: + + :SOURCE1:VOLTage 28 + :OUTPut:STATe CH1,ON + +And then disable it with: + + :OUTPut:STATe CH1,OFF + +The final settings needed are the frequencies to measure the NF at. This can be set with (7), to step through a range or a list of specific point frequencies. + +To start the measurement, press (8). + +

Examples

+ +Here is a plot comparing the measured noise figure for tuner 1 of a SDRplay Duo, compared to the [datasheet values](https://www.sdrplay.com/wp-content/uploads/2018/06/RSPDuo-Technical-Information-R1P1.pdf). + +![SDRPlay Duo NF comparison](../../../doc/img/NoiseFigure_plugin_duo_comparison.png) + +Here is a plot comparing measured values for a USRP B210 RF A RX2 port to measured values for the TX/RX port. As we can see, there is lower noise on the RX2 port: + +![B210 input NF comparison](../../../doc/img/NoiseFigure_plugin_b210_comparison.png) + +Here is a plot comparing measured values for a Nooelec SMArTee XTR on its own, with a Nooelec Sawbird GOES LNA and with a Sawbird+ GOES LNA. +As can be seen, a LNA makes a massive difference (12dB+) to the overall NF, and allows low cost SDRs to get similar NFs to more expensive devices, +as the total NF is primarily determined by the LNA, if it has a decent amount of gain: + +![LNA comparison](../../../doc/img/NoiseFigure_plugin_lna_comparison.png) + +

VISA libraries

+ +VISA libraries are available for Windows, Linux and MacOS from: + +[Keysight](https://www.keysight.com/us/en/lib/software-detail/computer-software/io-libraries-suite-downloads-2175637.html) +[NI](https://www.ni.com/en-gb/support/downloads/drivers/download.ni-visa.html) diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index aca193d6f..c07266312 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -206,6 +206,7 @@ set(sdrbase_SOURCES util/uid.cpp util/units.cpp util/timeutil.cpp + util/visa.cpp plugin/plugininterface.cpp plugin/pluginapi.cpp @@ -411,6 +412,7 @@ set(sdrbase_HEADERS util/uid.h util/units.h util/timeutil.h + util/visa.h webapi/webapiadapter.h webapi/webapiadapterbase.h diff --git a/sdrbase/util/visa.cpp b/sdrbase/util/visa.cpp new file mode 100644 index 000000000..f51d6668d --- /dev/null +++ b/sdrbase/util/visa.cpp @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "visa.h" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +VISA::VISA() : + m_defaultRM(0), + viOpenDefaultRM(nullptr), + viOpen(nullptr), + viClose(nullptr), + viPrintf(nullptr), + viScanf(nullptr), + m_available(false) +{ +#ifdef _MSC_VER + const char *visaName = "visa32.dll"; // Loads visa64.dll on WIN64 +#else + const char *visaName = "visakt32.so"; // Keysight library +#endif + + visaLibrary = libraryOpen(visaName); + if (visaLibrary) + { + viOpenDefaultRM = (ViStatus (WINAPI *)(ViPSession)) libraryFunc(visaLibrary, "viOpenDefaultRM"); + viOpen = (ViStatus (WINAPI *)(ViSession sesn, ViRsrc name, ViAccessMode mode, ViUInt32 timeout, ViPSession vi)) libraryFunc(visaLibrary, "viOpen"); + viClose = (ViStatus (WINAPI *)(ViObject vi)) libraryFunc(visaLibrary, "viClose"); + viPrintf = (ViStatus (WINAPI *) (ViSession vi, ViString writeFmt, ...)) libraryFunc(visaLibrary, "viPrintf"); + viScanf = (ViStatus (WINAPI *) (ViSession vi, ViString writeFmt, ...)) libraryFunc(visaLibrary, "viScanf"); + + if (viOpenDefaultRM && viOpen && viClose && viPrintf) { + m_available = true; + } + } + else + { + qDebug() << "VISA::VISA: Unable to load " << visaName; + } +} + +ViSession VISA::openDefault() +{ + if (isAvailable() && (m_defaultRM == 0)) + { + viOpenDefaultRM(&m_defaultRM); + return m_defaultRM; + } + else + { + return m_defaultRM; + } +} + +void VISA::closeDefault() +{ + if (isAvailable()) + { + viClose(m_defaultRM); + m_defaultRM = 0; + } +} + +ViSession VISA::open(const QString& device) +{ + ViSession session; + if (isAvailable()) + { + if (VI_SUCCESS == viOpen(m_defaultRM, device.toLatin1().data(), VI_NULL, VI_NULL, &session)) + { + qDebug() << "VISA::open: Opened VISA device: " << device; + return session; + } + else + { + qDebug() << "VISA::open: Failed to open VISA device: " << device; + } + } + return 0; +} + +void VISA::close(ViSession session) +{ + if (isAvailable()) { + viClose(session); + } +} + + +#ifdef _MSC_VER + +void *VISA::libraryOpen(const char *filename) +{ + HMODULE module; + module = LoadLibrary ((LPCSTR)filename); + return module; +} + +void *VISA::libraryFunc(void *library, const char *function) +{ + return GetProcAddress ((HMODULE)library, function); +} + +#else + +void *VISA::libraryOpen(const char *filename) +{ + return dlopen (filename, RTLD_LAZY); +} + +void *VISA::libraryFunc(void *library, const char *function) +{ + return dlsym (library, function); +} + +#endif diff --git a/sdrbase/util/visa.h b/sdrbase/util/visa.h new file mode 100644 index 000000000..153a60de9 --- /dev/null +++ b/sdrbase/util/visa.h @@ -0,0 +1,81 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_VISA_H +#define INCLUDE_VISA_H + +// Minimal implementation of VISA specification (just the bits we need so far) +// https://www.ivifoundation.org/docs/vpp432_2016-02-26.pdf + +#include "export.h" + +typedef char ViChar; +typedef ViChar * ViPChar; +typedef signed long ViInt32; +typedef unsigned long ViUInt32; +typedef ViPChar ViString; + +typedef ViInt32 ViStatus; +typedef ViUInt32 ViObject; +typedef ViObject ViSession; +typedef ViSession * ViPSession; +typedef ViString ViRsrc; +typedef ViUInt32 ViAccessMode; + +#define VI_SUCCESS 0 +#define VI_TRUE 1 +#define VI_FALSE 0 +#define VI_NULL 0 + +// We dynamically load the visa dll, as most users probably do not have it +// Note: Can't seem to call viOpenDefaultRM/viClose in constructor / destructor of global instance +class SDRBASE_API VISA { +public: + + // Default session + ViSession m_defaultRM; + // Function pointers to VISA API for direct calls + ViStatus (*viOpenDefaultRM) (ViPSession vi); + ViStatus (*viOpen) (ViSession sesn, ViRsrc name, ViAccessMode mode, ViUInt32 timeout, ViPSession vi); + ViStatus (*viClose) (ViObject vi); + ViStatus (*viPrintf) (ViSession vi, ViString writeFmt, ...); + ViStatus (*viScanf) (ViSession vi, ViString readFmt, ...); + + VISA(); + + ViSession openDefault(); + void closeDefault(); + ViSession open(const QString& device); + void close(ViSession session); + + // Is the VISA library available + bool isAvailable() const + { + return m_available; + } + +private: + bool m_available; + +protected: + void *visaLibrary; + + void *libraryOpen(const char *filename); + void *libraryFunc(void *library, const char *function); +}; + +#endif // INCLUDE_VISA_H diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index b5ee0c675..a0cb5fce2 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -3936,6 +3936,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setNfmModSettings(new SWGSDRangel::SWGNFMModSettings()); channelSettings->getNfmModSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "NoiseFigureSettings") + { + channelSettings->setNoiseFigureSettings(new SWGSDRangel::SWGNoiseFigureSettings()); + channelSettings->getNoiseFigureSettings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "LocalSinkSettings") { channelSettings->setLocalSinkSettings(new SWGSDRangel::SWGLocalSinkSettings()); @@ -4638,6 +4643,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setIeee802154ModSettings(nullptr); channelSettings.setNfmDemodSettings(nullptr); channelSettings.setNfmModSettings(nullptr); + channelSettings.setNoiseFigureSettings(nullptr); channelSettings.setPacketDemodSettings(nullptr); channelSettings.setPacketModSettings(nullptr); channelSettings.setRemoteSinkSettings(nullptr); @@ -4666,6 +4672,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan channelReport.setDsdDemodReport(nullptr); channelReport.setNfmDemodReport(nullptr); channelReport.setNfmModReport(nullptr); + channelReport.setNoiseFigureReport(nullptr); channelReport.setIeee802154ModReport(nullptr); channelReport.setPacketModReport(nullptr); channelReport.setRemoteSourceReport(nullptr); diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index 4059d7115..6e87ce597 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -49,6 +49,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channel.nfmdemod", "NFMDemodSettings"}, {"de.maintech.sdrangelove.channel.nfm", "NFMDemodSettings"}, // remap {"sdrangel.channeltx.modnfm", "NFMModSettings"}, + {"sdrangel.channel.noisefigure", "NoiseFigureSettings"}, {"sdrangel.demod.localsink", "LocalSinkSettings"}, {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap {"sdrangel.channel.localsource", "LocalSourceSettings"}, @@ -147,6 +148,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"}, {"NFMDemod", "NFMDemodSettings"}, {"NFMMod", "NFMModSettings"}, + {"NoiseFigure", "NoiseFigureSettings"}, {"PacketDemod", "PacketDemodSettings"}, {"PacketMod", "PacketModSettings"}, {"LocalSink", "LocalSinkSettings"}, diff --git a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml index f30a67872..35ba64e62 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml @@ -49,6 +49,8 @@ ChannelReport: $ref: "http://swgserver:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport" NFMModReport: $ref: "http://swgserver:8081/api/swagger/include/NFMMod.yaml#/NFMModReport" + NoiseFigureReport: + $ref: "http://swgserver:8081/api/swagger/include/NoiseFigure.yaml#/NoiseFigureReport" SSBDemodReport: $ref: "http://swgserver:8081/api/swagger/include/SSBDemod.yaml#/SSBDemodReport" RemoteSourceReport: diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index ea6f5de8b..5c3861299 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -69,6 +69,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings" NFMModSettings: $ref: "http://swgserver:8081/api/swagger/include/NFMMod.yaml#/NFMModSettings" + NoiseFigureSettings: + $ref: "http://swgserver:8081/api/swagger/include/NoiseFigure.yaml#/NoiseFigureSettings" LocalSinkSettings: $ref: "http://swgserver:8081/api/swagger/include/LocalSink.yaml#/LocalSinkSettings" LocalSourceSettings: diff --git a/swagger/sdrangel/api/swagger/include/NoiseFigure.yaml b/swagger/sdrangel/api/swagger/include/NoiseFigure.yaml new file mode 100644 index 000000000..1d26b3266 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/NoiseFigure.yaml @@ -0,0 +1,66 @@ +NoiseFigureSettings: + description: NoiseFigure + properties: + inputFrequencyOffset: + type: integer + format: int64 + fftSize: + type: integer + fftCount: + type: integer + frequencySpec: + type: integer + startFrequency: + type: number + format: float + stopFrequency: + type: number + format: float + steps: + type: integer + step: + type: number + format: float + frequencies: + type: string + visaDevice: + type: string + powerOnSCPI: + type: string + powerOffSCPI: + type: string + powerOnCommand: + type: string + powerOffCommand: + type: string + powerDelay: + 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 + +NoiseFigureReport: + description: NoiseFigure + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index d61c58255..b4021c8c1 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -70,6 +70,8 @@ SWGChannelReport::SWGChannelReport() { m_nfm_demod_report_isSet = false; nfm_mod_report = nullptr; m_nfm_mod_report_isSet = false; + noise_figure_report = nullptr; + m_noise_figure_report_isSet = false; ssb_demod_report = nullptr; m_ssb_demod_report_isSet = false; remote_source_report = nullptr; @@ -142,6 +144,8 @@ SWGChannelReport::init() { m_nfm_demod_report_isSet = false; nfm_mod_report = new SWGNFMModReport(); m_nfm_mod_report_isSet = false; + noise_figure_report = new SWGNoiseFigureReport(); + m_noise_figure_report_isSet = false; ssb_demod_report = new SWGSSBDemodReport(); m_ssb_demod_report_isSet = false; remote_source_report = new SWGRemoteSourceReport(); @@ -229,6 +233,9 @@ SWGChannelReport::cleanup() { if(nfm_mod_report != nullptr) { delete nfm_mod_report; } + if(noise_figure_report != nullptr) { + delete noise_figure_report; + } if(ssb_demod_report != nullptr) { delete ssb_demod_report; } @@ -317,6 +324,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport"); + ::SWGSDRangel::setValue(&noise_figure_report, pJson["NoiseFigureReport"], "SWGNoiseFigureReport", "SWGNoiseFigureReport"); + ::SWGSDRangel::setValue(&ssb_demod_report, pJson["SSBDemodReport"], "SWGSSBDemodReport", "SWGSSBDemodReport"); ::SWGSDRangel::setValue(&remote_source_report, pJson["RemoteSourceReport"], "SWGRemoteSourceReport", "SWGRemoteSourceReport"); @@ -418,6 +427,9 @@ SWGChannelReport::asJsonObject() { if((nfm_mod_report != nullptr) && (nfm_mod_report->isSet())){ toJsonValue(QString("NFMModReport"), nfm_mod_report, obj, QString("SWGNFMModReport")); } + if((noise_figure_report != nullptr) && (noise_figure_report->isSet())){ + toJsonValue(QString("NoiseFigureReport"), noise_figure_report, obj, QString("SWGNoiseFigureReport")); + } if((ssb_demod_report != nullptr) && (ssb_demod_report->isSet())){ toJsonValue(QString("SSBDemodReport"), ssb_demod_report, obj, QString("SWGSSBDemodReport")); } @@ -665,6 +677,16 @@ SWGChannelReport::setNfmModReport(SWGNFMModReport* nfm_mod_report) { this->m_nfm_mod_report_isSet = true; } +SWGNoiseFigureReport* +SWGChannelReport::getNoiseFigureReport() { + return noise_figure_report; +} +void +SWGChannelReport::setNoiseFigureReport(SWGNoiseFigureReport* noise_figure_report) { + this->noise_figure_report = noise_figure_report; + this->m_noise_figure_report_isSet = true; +} + SWGSSBDemodReport* SWGChannelReport::getSsbDemodReport() { return ssb_demod_report; @@ -843,6 +865,9 @@ SWGChannelReport::isSet(){ if(nfm_mod_report && nfm_mod_report->isSet()){ isObjectUpdated = true; break; } + if(noise_figure_report && noise_figure_report->isSet()){ + isObjectUpdated = true; break; + } if(ssb_demod_report && ssb_demod_report->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 2ae0d6700..ba3fddb2d 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -41,6 +41,7 @@ #include "SWGIEEE_802_15_4_ModReport.h" #include "SWGNFMDemodReport.h" #include "SWGNFMModReport.h" +#include "SWGNoiseFigureReport.h" #include "SWGPacketModReport.h" #include "SWGRemoteSourceReport.h" #include "SWGSSBDemodReport.h" @@ -135,6 +136,9 @@ public: SWGNFMModReport* getNfmModReport(); void setNfmModReport(SWGNFMModReport* nfm_mod_report); + SWGNoiseFigureReport* getNoiseFigureReport(); + void setNoiseFigureReport(SWGNoiseFigureReport* noise_figure_report); + SWGSSBDemodReport* getSsbDemodReport(); void setSsbDemodReport(SWGSSBDemodReport* ssb_demod_report); @@ -235,6 +239,9 @@ private: SWGNFMModReport* nfm_mod_report; bool m_nfm_mod_report_isSet; + SWGNoiseFigureReport* noise_figure_report; + bool m_noise_figure_report_isSet; + SWGSSBDemodReport* ssb_demod_report; bool m_ssb_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index f368bd81c..54175c4d9 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -88,6 +88,8 @@ SWGChannelSettings::SWGChannelSettings() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = nullptr; m_nfm_mod_settings_isSet = false; + noise_figure_settings = nullptr; + m_noise_figure_settings_isSet = false; local_sink_settings = nullptr; m_local_sink_settings_isSet = false; local_source_settings = nullptr; @@ -186,6 +188,8 @@ SWGChannelSettings::init() { m_nfm_demod_settings_isSet = false; nfm_mod_settings = new SWGNFMModSettings(); m_nfm_mod_settings_isSet = false; + noise_figure_settings = new SWGNoiseFigureSettings(); + m_noise_figure_settings_isSet = false; local_sink_settings = new SWGLocalSinkSettings(); m_local_sink_settings_isSet = false; local_source_settings = new SWGLocalSourceSettings(); @@ -304,6 +308,9 @@ SWGChannelSettings::cleanup() { if(nfm_mod_settings != nullptr) { delete nfm_mod_settings; } + if(noise_figure_settings != nullptr) { + delete noise_figure_settings; + } if(local_sink_settings != nullptr) { delete local_sink_settings; } @@ -422,6 +429,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings"); + ::SWGSDRangel::setValue(&noise_figure_settings, pJson["NoiseFigureSettings"], "SWGNoiseFigureSettings", "SWGNoiseFigureSettings"); + ::SWGSDRangel::setValue(&local_sink_settings, pJson["LocalSinkSettings"], "SWGLocalSinkSettings", "SWGLocalSinkSettings"); ::SWGSDRangel::setValue(&local_source_settings, pJson["LocalSourceSettings"], "SWGLocalSourceSettings", "SWGLocalSourceSettings"); @@ -558,6 +567,9 @@ SWGChannelSettings::asJsonObject() { if((nfm_mod_settings != nullptr) && (nfm_mod_settings->isSet())){ toJsonValue(QString("NFMModSettings"), nfm_mod_settings, obj, QString("SWGNFMModSettings")); } + if((noise_figure_settings != nullptr) && (noise_figure_settings->isSet())){ + toJsonValue(QString("NoiseFigureSettings"), noise_figure_settings, obj, QString("SWGNoiseFigureSettings")); + } if((local_sink_settings != nullptr) && (local_sink_settings->isSet())){ toJsonValue(QString("LocalSinkSettings"), local_sink_settings, obj, QString("SWGLocalSinkSettings")); } @@ -907,6 +919,16 @@ SWGChannelSettings::setNfmModSettings(SWGNFMModSettings* nfm_mod_settings) { this->m_nfm_mod_settings_isSet = true; } +SWGNoiseFigureSettings* +SWGChannelSettings::getNoiseFigureSettings() { + return noise_figure_settings; +} +void +SWGChannelSettings::setNoiseFigureSettings(SWGNoiseFigureSettings* noise_figure_settings) { + this->noise_figure_settings = noise_figure_settings; + this->m_noise_figure_settings_isSet = true; +} + SWGLocalSinkSettings* SWGChannelSettings::getLocalSinkSettings() { return local_sink_settings; @@ -1152,6 +1174,9 @@ SWGChannelSettings::isSet(){ if(nfm_mod_settings && nfm_mod_settings->isSet()){ isObjectUpdated = true; break; } + if(noise_figure_settings && noise_figure_settings->isSet()){ + isObjectUpdated = true; break; + } if(local_sink_settings && local_sink_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index f03b3c711..c5a22be7e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -50,6 +50,7 @@ #include "SWGLocalSourceSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGNFMModSettings.h" +#include "SWGNoiseFigureSettings.h" #include "SWGPacketDemodSettings.h" #include "SWGPacketModSettings.h" #include "SWGRemoteSinkSettings.h" @@ -173,6 +174,9 @@ public: SWGNFMModSettings* getNfmModSettings(); void setNfmModSettings(SWGNFMModSettings* nfm_mod_settings); + SWGNoiseFigureSettings* getNoiseFigureSettings(); + void setNoiseFigureSettings(SWGNoiseFigureSettings* noise_figure_settings); + SWGLocalSinkSettings* getLocalSinkSettings(); void setLocalSinkSettings(SWGLocalSinkSettings* local_sink_settings); @@ -312,6 +316,9 @@ private: SWGNFMModSettings* nfm_mod_settings; bool m_nfm_mod_settings_isSet; + SWGNoiseFigureSettings* noise_figure_settings; + bool m_noise_figure_settings_isSet; + SWGLocalSinkSettings* local_sink_settings; bool m_local_sink_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 22dadfcb3..1db32365a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -172,6 +172,8 @@ #include "SWGNFMModReport.h" #include "SWGNFMModSettings.h" #include "SWGNamedEnum.h" +#include "SWGNoiseFigureReport.h" +#include "SWGNoiseFigureSettings.h" #include "SWGPERTesterActions.h" #include "SWGPERTesterActions_aos.h" #include "SWGPERTesterSettings.h" @@ -748,6 +750,12 @@ namespace SWGSDRangel { if(QString("SWGNamedEnum").compare(type) == 0) { return new SWGNamedEnum(); } + if(QString("SWGNoiseFigureReport").compare(type) == 0) { + return new SWGNoiseFigureReport(); + } + if(QString("SWGNoiseFigureSettings").compare(type) == 0) { + return new SWGNoiseFigureSettings(); + } if(QString("SWGPERTesterActions").compare(type) == 0) { return new SWGPERTesterActions(); } diff --git a/swagger/sdrangel/code/qt5/client/SWGNoiseFigureReport.cpp b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureReport.cpp new file mode 100644 index 000000000..9b1bd5451 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureReport.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 "SWGNoiseFigureReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGNoiseFigureReport::SWGNoiseFigureReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGNoiseFigureReport::SWGNoiseFigureReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGNoiseFigureReport::~SWGNoiseFigureReport() { + this->cleanup(); +} + +void +SWGNoiseFigureReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGNoiseFigureReport::cleanup() { + + +} + +SWGNoiseFigureReport* +SWGNoiseFigureReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGNoiseFigureReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGNoiseFigureReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGNoiseFigureReport::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 +SWGNoiseFigureReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGNoiseFigureReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGNoiseFigureReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGNoiseFigureReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGNoiseFigureReport::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/SWGNoiseFigureReport.h b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureReport.h new file mode 100644 index 000000000..4d612d229 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureReport.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. + */ + +/* + * SWGNoiseFigureReport.h + * + * NoiseFigure + */ + +#ifndef SWGNoiseFigureReport_H_ +#define SWGNoiseFigureReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGNoiseFigureReport: public SWGObject { +public: + SWGNoiseFigureReport(); + SWGNoiseFigureReport(QString* json); + virtual ~SWGNoiseFigureReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNoiseFigureReport* 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 /* SWGNoiseFigureReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGNoiseFigureSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureSettings.cpp new file mode 100644 index 000000000..eb4c1df9e --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureSettings.cpp @@ -0,0 +1,630 @@ +/** + * 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 "SWGNoiseFigureSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGNoiseFigureSettings::SWGNoiseFigureSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGNoiseFigureSettings::SWGNoiseFigureSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + fft_count = 0; + m_fft_count_isSet = false; + frequency_spec = 0; + m_frequency_spec_isSet = false; + start_frequency = 0.0f; + m_start_frequency_isSet = false; + stop_frequency = 0.0f; + m_stop_frequency_isSet = false; + steps = 0; + m_steps_isSet = false; + step = 0.0f; + m_step_isSet = false; + frequencies = nullptr; + m_frequencies_isSet = false; + visa_device = nullptr; + m_visa_device_isSet = false; + power_on_scpi = nullptr; + m_power_on_scpi_isSet = false; + power_off_scpi = nullptr; + m_power_off_scpi_isSet = false; + power_on_command = nullptr; + m_power_on_command_isSet = false; + power_off_command = nullptr; + m_power_off_command_isSet = false; + power_delay = 0.0f; + m_power_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; +} + +SWGNoiseFigureSettings::~SWGNoiseFigureSettings() { + this->cleanup(); +} + +void +SWGNoiseFigureSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + fft_count = 0; + m_fft_count_isSet = false; + frequency_spec = 0; + m_frequency_spec_isSet = false; + start_frequency = 0.0f; + m_start_frequency_isSet = false; + stop_frequency = 0.0f; + m_stop_frequency_isSet = false; + steps = 0; + m_steps_isSet = false; + step = 0.0f; + m_step_isSet = false; + frequencies = new QString(""); + m_frequencies_isSet = false; + visa_device = new QString(""); + m_visa_device_isSet = false; + power_on_scpi = new QString(""); + m_power_on_scpi_isSet = false; + power_off_scpi = new QString(""); + m_power_off_scpi_isSet = false; + power_on_command = new QString(""); + m_power_on_command_isSet = false; + power_off_command = new QString(""); + m_power_off_command_isSet = false; + power_delay = 0.0f; + m_power_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 +SWGNoiseFigureSettings::cleanup() { + + + + + + + + + if(frequencies != nullptr) { + delete frequencies; + } + if(visa_device != nullptr) { + delete visa_device; + } + if(power_on_scpi != nullptr) { + delete power_on_scpi; + } + if(power_off_scpi != nullptr) { + delete power_off_scpi; + } + if(power_on_command != nullptr) { + delete power_on_command; + } + if(power_off_command != nullptr) { + delete power_off_command; + } + + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGNoiseFigureSettings* +SWGNoiseFigureSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGNoiseFigureSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&fft_size, pJson["fftSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&fft_count, pJson["fftCount"], "qint32", ""); + + ::SWGSDRangel::setValue(&frequency_spec, pJson["frequencySpec"], "qint32", ""); + + ::SWGSDRangel::setValue(&start_frequency, pJson["startFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&stop_frequency, pJson["stopFrequency"], "float", ""); + + ::SWGSDRangel::setValue(&steps, pJson["steps"], "qint32", ""); + + ::SWGSDRangel::setValue(&step, pJson["step"], "float", ""); + + ::SWGSDRangel::setValue(&frequencies, pJson["frequencies"], "QString", "QString"); + + ::SWGSDRangel::setValue(&visa_device, pJson["visaDevice"], "QString", "QString"); + + ::SWGSDRangel::setValue(&power_on_scpi, pJson["powerOnSCPI"], "QString", "QString"); + + ::SWGSDRangel::setValue(&power_off_scpi, pJson["powerOffSCPI"], "QString", "QString"); + + ::SWGSDRangel::setValue(&power_on_command, pJson["powerOnCommand"], "QString", "QString"); + + ::SWGSDRangel::setValue(&power_off_command, pJson["powerOffCommand"], "QString", "QString"); + + ::SWGSDRangel::setValue(&power_delay, pJson["powerDelay"], "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 +SWGNoiseFigureSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGNoiseFigureSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_fft_size_isSet){ + obj->insert("fftSize", QJsonValue(fft_size)); + } + if(m_fft_count_isSet){ + obj->insert("fftCount", QJsonValue(fft_count)); + } + if(m_frequency_spec_isSet){ + obj->insert("frequencySpec", QJsonValue(frequency_spec)); + } + if(m_start_frequency_isSet){ + obj->insert("startFrequency", QJsonValue(start_frequency)); + } + if(m_stop_frequency_isSet){ + obj->insert("stopFrequency", QJsonValue(stop_frequency)); + } + if(m_steps_isSet){ + obj->insert("steps", QJsonValue(steps)); + } + if(m_step_isSet){ + obj->insert("step", QJsonValue(step)); + } + if(frequencies != nullptr && *frequencies != QString("")){ + toJsonValue(QString("frequencies"), frequencies, obj, QString("QString")); + } + if(visa_device != nullptr && *visa_device != QString("")){ + toJsonValue(QString("visaDevice"), visa_device, obj, QString("QString")); + } + if(power_on_scpi != nullptr && *power_on_scpi != QString("")){ + toJsonValue(QString("powerOnSCPI"), power_on_scpi, obj, QString("QString")); + } + if(power_off_scpi != nullptr && *power_off_scpi != QString("")){ + toJsonValue(QString("powerOffSCPI"), power_off_scpi, obj, QString("QString")); + } + if(power_on_command != nullptr && *power_on_command != QString("")){ + toJsonValue(QString("powerOnCommand"), power_on_command, obj, QString("QString")); + } + if(power_off_command != nullptr && *power_off_command != QString("")){ + toJsonValue(QString("powerOffCommand"), power_off_command, obj, QString("QString")); + } + if(m_power_delay_isSet){ + obj->insert("powerDelay", QJsonValue(power_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 +SWGNoiseFigureSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGNoiseFigureSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getFftSize() { + return fft_size; +} +void +SWGNoiseFigureSettings::setFftSize(qint32 fft_size) { + this->fft_size = fft_size; + this->m_fft_size_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getFftCount() { + return fft_count; +} +void +SWGNoiseFigureSettings::setFftCount(qint32 fft_count) { + this->fft_count = fft_count; + this->m_fft_count_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getFrequencySpec() { + return frequency_spec; +} +void +SWGNoiseFigureSettings::setFrequencySpec(qint32 frequency_spec) { + this->frequency_spec = frequency_spec; + this->m_frequency_spec_isSet = true; +} + +float +SWGNoiseFigureSettings::getStartFrequency() { + return start_frequency; +} +void +SWGNoiseFigureSettings::setStartFrequency(float start_frequency) { + this->start_frequency = start_frequency; + this->m_start_frequency_isSet = true; +} + +float +SWGNoiseFigureSettings::getStopFrequency() { + return stop_frequency; +} +void +SWGNoiseFigureSettings::setStopFrequency(float stop_frequency) { + this->stop_frequency = stop_frequency; + this->m_stop_frequency_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getSteps() { + return steps; +} +void +SWGNoiseFigureSettings::setSteps(qint32 steps) { + this->steps = steps; + this->m_steps_isSet = true; +} + +float +SWGNoiseFigureSettings::getStep() { + return step; +} +void +SWGNoiseFigureSettings::setStep(float step) { + this->step = step; + this->m_step_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getFrequencies() { + return frequencies; +} +void +SWGNoiseFigureSettings::setFrequencies(QString* frequencies) { + this->frequencies = frequencies; + this->m_frequencies_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getVisaDevice() { + return visa_device; +} +void +SWGNoiseFigureSettings::setVisaDevice(QString* visa_device) { + this->visa_device = visa_device; + this->m_visa_device_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getPowerOnScpi() { + return power_on_scpi; +} +void +SWGNoiseFigureSettings::setPowerOnScpi(QString* power_on_scpi) { + this->power_on_scpi = power_on_scpi; + this->m_power_on_scpi_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getPowerOffScpi() { + return power_off_scpi; +} +void +SWGNoiseFigureSettings::setPowerOffScpi(QString* power_off_scpi) { + this->power_off_scpi = power_off_scpi; + this->m_power_off_scpi_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getPowerOnCommand() { + return power_on_command; +} +void +SWGNoiseFigureSettings::setPowerOnCommand(QString* power_on_command) { + this->power_on_command = power_on_command; + this->m_power_on_command_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getPowerOffCommand() { + return power_off_command; +} +void +SWGNoiseFigureSettings::setPowerOffCommand(QString* power_off_command) { + this->power_off_command = power_off_command; + this->m_power_off_command_isSet = true; +} + +float +SWGNoiseFigureSettings::getPowerDelay() { + return power_delay; +} +void +SWGNoiseFigureSettings::setPowerDelay(float power_delay) { + this->power_delay = power_delay; + this->m_power_delay_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getRgbColor() { + return rgb_color; +} +void +SWGNoiseFigureSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getTitle() { + return title; +} +void +SWGNoiseFigureSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getStreamIndex() { + return stream_index; +} +void +SWGNoiseFigureSettings::setStreamIndex(qint32 stream_index) { + this->stream_index = stream_index; + this->m_stream_index_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGNoiseFigureSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGNoiseFigureSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGNoiseFigureSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGNoiseFigureSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGNoiseFigureSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGNoiseFigureSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGNoiseFigureSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGNoiseFigureSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_size_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_count_isSet){ + isObjectUpdated = true; break; + } + if(m_frequency_spec_isSet){ + isObjectUpdated = true; break; + } + if(m_start_frequency_isSet){ + isObjectUpdated = true; break; + } + if(m_stop_frequency_isSet){ + isObjectUpdated = true; break; + } + if(m_steps_isSet){ + isObjectUpdated = true; break; + } + if(m_step_isSet){ + isObjectUpdated = true; break; + } + if(frequencies && *frequencies != QString("")){ + isObjectUpdated = true; break; + } + if(visa_device && *visa_device != QString("")){ + isObjectUpdated = true; break; + } + if(power_on_scpi && *power_on_scpi != QString("")){ + isObjectUpdated = true; break; + } + if(power_off_scpi && *power_off_scpi != QString("")){ + isObjectUpdated = true; break; + } + if(power_on_command && *power_on_command != QString("")){ + isObjectUpdated = true; break; + } + if(power_off_command && *power_off_command != QString("")){ + isObjectUpdated = true; break; + } + if(m_power_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/SWGNoiseFigureSettings.h b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureSettings.h new file mode 100644 index 000000000..0f222d6c7 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGNoiseFigureSettings.h @@ -0,0 +1,191 @@ +/** + * 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. + */ + +/* + * SWGNoiseFigureSettings.h + * + * NoiseFigure + */ + +#ifndef SWGNoiseFigureSettings_H_ +#define SWGNoiseFigureSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGNoiseFigureSettings: public SWGObject { +public: + SWGNoiseFigureSettings(); + SWGNoiseFigureSettings(QString* json); + virtual ~SWGNoiseFigureSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGNoiseFigureSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + qint32 getFftSize(); + void setFftSize(qint32 fft_size); + + qint32 getFftCount(); + void setFftCount(qint32 fft_count); + + qint32 getFrequencySpec(); + void setFrequencySpec(qint32 frequency_spec); + + float getStartFrequency(); + void setStartFrequency(float start_frequency); + + float getStopFrequency(); + void setStopFrequency(float stop_frequency); + + qint32 getSteps(); + void setSteps(qint32 steps); + + float getStep(); + void setStep(float step); + + QString* getFrequencies(); + void setFrequencies(QString* frequencies); + + QString* getVisaDevice(); + void setVisaDevice(QString* visa_device); + + QString* getPowerOnScpi(); + void setPowerOnScpi(QString* power_on_scpi); + + QString* getPowerOffScpi(); + void setPowerOffScpi(QString* power_off_scpi); + + QString* getPowerOnCommand(); + void setPowerOnCommand(QString* power_on_command); + + QString* getPowerOffCommand(); + void setPowerOffCommand(QString* power_off_command); + + float getPowerDelay(); + void setPowerDelay(float power_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 fft_size; + bool m_fft_size_isSet; + + qint32 fft_count; + bool m_fft_count_isSet; + + qint32 frequency_spec; + bool m_frequency_spec_isSet; + + float start_frequency; + bool m_start_frequency_isSet; + + float stop_frequency; + bool m_stop_frequency_isSet; + + qint32 steps; + bool m_steps_isSet; + + float step; + bool m_step_isSet; + + QString* frequencies; + bool m_frequencies_isSet; + + QString* visa_device; + bool m_visa_device_isSet; + + QString* power_on_scpi; + bool m_power_on_scpi_isSet; + + QString* power_off_scpi; + bool m_power_off_scpi_isSet; + + QString* power_on_command; + bool m_power_on_command_isSet; + + QString* power_off_command; + bool m_power_off_command_isSet; + + float power_delay; + bool m_power_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 /* SWGNoiseFigureSettings_H_ */