/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2023 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 "freqscanner.h" #include #include #include #include #include #include #include #include #include "SWGChannelSettings.h" #include "SWGWorkspaceInfo.h" #include "SWGFreqScannerSettings.h" #include "SWGChannelReport.h" #include "device/deviceset.h" #include "dsp/dspengine.h" #include "dsp/dspcommands.h" #include "dsp/devicesamplesource.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" #include "settings/serializable.h" #include "channel/channelwebapiutils.h" #include "maincore.h" #include "dsp/spectrumvis.h" MESSAGE_CLASS_DEFINITION(FreqScanner::MsgConfigureFreqScanner, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportChannels, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStartScan, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStopScan, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgScanComplete, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgScanResult, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgStatus, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportActiveFrequency, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportActivePower, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportScanning, Message) MESSAGE_CLASS_DEFINITION(FreqScanner::MsgReportScanRange, Message) const char * const FreqScanner::m_channelIdURI = "sdrangel.channel.freqscanner"; const char * const FreqScanner::m_channelId = "FreqScanner"; FreqScanner::FreqScanner(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), m_thread(nullptr), m_basebandSink(nullptr), m_running(false), m_basebandSampleRate(0), m_availableChannelHandler({}), m_scanDeviceSetIndex(0), m_scanChannelIndex(0), m_state(IDLE), m_timeoutTimer(this) { setObjectName(m_channelId); applySettings(m_settings, QStringList(), true); m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); QObject::connect( m_networkManager, &QNetworkAccessManager::finished, this, &FreqScanner::networkManagerFinished ); QObject::connect( this, &ChannelAPI::indexInDeviceSetChanged, this, &FreqScanner::handleIndexInDeviceSetChanged ); start(); QObject::connect(&m_availableChannelHandler, &AvailableChannelOrFeatureHandler::channelsOrFeaturesChanged, this, &FreqScanner::channelsChanged); m_availableChannelHandler.scanAvailableChannelsAndFeatures(); m_timeoutTimer.callOnTimeout(this, &FreqScanner::timeout); } FreqScanner::~FreqScanner() { qDebug("FreqScanner::~FreqScanner"); QObject::disconnect( m_networkManager, &QNetworkAccessManager::finished, this, &FreqScanner::networkManagerFinished ); delete m_networkManager; m_deviceAPI->removeChannelSinkAPI(this); m_deviceAPI->removeChannelSink(this); stop(); } void FreqScanner::setDeviceAPI(DeviceAPI *deviceAPI) { if (deviceAPI != m_deviceAPI) { m_deviceAPI->removeChannelSinkAPI(this); m_deviceAPI->removeChannelSink(this); m_deviceAPI = deviceAPI; m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); } } uint32_t FreqScanner::getNumberOfDeviceStreams() const { return m_deviceAPI->getNbSourceStreams(); } void FreqScanner::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; if (m_running) { m_basebandSink->feed(begin, end); } } void FreqScanner::start() { QMutexLocker m_lock(&m_mutex); if (m_running) { return; } qDebug("FreqScanner::start"); m_thread = new QThread(); m_basebandSink = new FreqScannerBaseband(this); m_basebandSink->setFifoLabel(QString("%1 [%2:%3]") .arg(m_channelId) .arg(m_deviceAPI->getDeviceSetIndex()) .arg(getIndexInDeviceSet()) ); m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); m_basebandSink->setChannel(this); m_basebandSink->moveToThread(m_thread); QObject::connect( m_thread, &QThread::finished, m_basebandSink, &QObject::deleteLater ); QObject::connect( m_thread, &QThread::finished, m_thread, &QThread::deleteLater ); m_thread->start(); DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency); m_basebandSink->getInputMessageQueue()->push(dspMsg); FreqScannerBaseband::MsgConfigureFreqScannerBaseband *msg = FreqScannerBaseband::MsgConfigureFreqScannerBaseband::create(m_settings, QStringList(), true); m_basebandSink->getInputMessageQueue()->push(msg); m_running = true; } void FreqScanner::stop() { QMutexLocker m_lock(&m_mutex); if (!m_running) { return; } qDebug("FreqScanner::stop"); m_running = false; m_thread->exit(); m_thread->wait(); } bool FreqScanner::handleMessage(const Message& cmd) { if (MsgConfigureFreqScanner::match(cmd)) { MsgConfigureFreqScanner& cfg = (MsgConfigureFreqScanner&) cmd; qDebug() << "FreqScanner::handleMessage: MsgConfigureFreqScanner"; applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce()); return true; } else if (DSPSignalNotification::match(cmd)) { DSPSignalNotification& notif = (DSPSignalNotification&) cmd; int newSampleRate = notif.getSampleRate(); if ((newSampleRate != m_basebandSampleRate) && (m_state != IDLE)) { // Restart scan if sample rate changes startScan(); } m_basebandSampleRate = newSampleRate; m_centerFrequency = notif.getCenterFrequency(); qDebug() << "FreqScanner::handleMessage: DSPSignalNotification"; // Forward to the sink if (m_running) { DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy m_basebandSink->getInputMessageQueue()->push(rep); } // Forward to GUI if any if (m_guiMessageQueue) { m_guiMessageQueue->push(new DSPSignalNotification(notif)); } return true; } else if (MsgStartScan::match(cmd)) { muteAll(m_settings); startScan(); return true; } else if (MsgStopScan::match(cmd)) { stopScan(); return true; } else if (MsgScanResult::match(cmd)) { MsgScanResult& result = (MsgScanResult&)cmd; const QList& results = result.getScanResults(); processScanResults(result.getFFTStartTime(), results); return true; } else { return false; } } void FreqScanner::startScan() { // Start scan m_state = START_SCAN; } void FreqScanner::stopScan() { // Stop scan m_state = IDLE; m_timeoutTimer.stop(); if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgStatus::create("")); } } void FreqScanner::setDeviceCenterFrequency(qint64 frequency) { DSPDeviceSourceEngine* deviceSourceEngine = getDeviceAPI()->getDeviceSourceEngine(); DSPDeviceMIMOEngine *deviceMIMOEngine = getDeviceAPI()->getDeviceMIMOEngine(); if (deviceSourceEngine) // Rx device { // For RTL SDR, setCenterFrequency takes ~50ms, which means tuneTime can be 0 getDeviceAPI()->getSampleSource()->setCenterFrequency(frequency); } else if (deviceMIMOEngine) { // MIMO device - I/Q stream is the same as this channel getDeviceAPI()->getSampleMIMO()->setSourceCenterFrequency(frequency, m_settings.m_streamIndex); } m_minFFTStartTime = QDateTime::currentDateTime().addMSecs(m_settings.m_tuneTime); } void FreqScanner::initScan() { // if (m_scanChannelIndex < 0) { // Always false // applyChannelSetting(m_settings.m_channel); // } ChannelWebAPIUtils::setAudioMute(m_scanDeviceSetIndex, m_scanChannelIndex, true); if (m_centerFrequency != m_stepStartFrequency) { setDeviceCenterFrequency(m_stepStartFrequency); } m_scanResults.clear(); if (m_guiMessageQueue) { m_guiMessageQueue->push(FreqScanner::MsgReportScanning::create()); } m_state = SCAN_FOR_MAX_POWER; } void FreqScanner::processScanResults(const QDateTime& fftStartTime, const QList& results) { switch (m_state) { case IDLE: break; case START_SCAN: { // Create ordered list of frequencies to scan QList frequencies; for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { if (m_settings.m_frequencySettings[i].m_enabled) { frequencies.append(m_settings.m_frequencySettings[i].m_frequency); } } std::sort(frequencies.begin(), frequencies.end()); if ((frequencies.size() > 0) && (m_settings.m_channelBandwidth > 0) && (m_basebandSampleRate > 0)) { // Calculate how many channels can be scanned in one go int fftSize; int binsPerChannel; calcScannerSampleRate(m_settings.m_channelBandwidth, m_basebandSampleRate, m_scannerSampleRate, fftSize, binsPerChannel); // Align first frequency so we cover as many channels as possible, while channel guard band // Can we adjust this to avoid DC bin? m_stepStartFrequency = frequencies.front() + m_scannerSampleRate / 2 - m_scannerSampleRate * 0.125f; m_stepStopFrequency = frequencies.back(); // If all frequencies fit within usable bandwidth, we can have the first frequency more central int totalBW = frequencies.back() - frequencies.front() + 2 * m_settings.m_channelBandwidth; if (totalBW < m_scannerSampleRate * 0.75f) { int spareBWEachSide = (m_scannerSampleRate - totalBW) / 2; int spareChannelsEachSide = spareBWEachSide / m_settings.m_channelBandwidth; int offset = spareChannelsEachSide * m_settings.m_channelBandwidth; m_stepStartFrequency -= offset; } initScan(); } } break; case SCAN_FOR_MAX_POWER: if (fftStartTime >= m_minFFTStartTime) { if (results.size() > 0) { m_scanResults.append(results); } // Calculate next center frequency bool complete = false; // Have all frequencies been scanned? bool freqInRange = false; qint64 nextCenterFrequency = m_centerFrequency; float usableBW = m_scannerSampleRate * 0.75f; do { if (nextCenterFrequency + usableBW / 2 > m_stepStopFrequency) { nextCenterFrequency = m_stepStartFrequency; complete = true; } else { nextCenterFrequency += usableBW; complete = false; } // Are any frequencies in this new range? for (int i = 0; i < m_settings.m_frequencySettings.size(); i++) { if (m_settings.m_frequencySettings[i].m_enabled && (m_settings.m_frequencySettings[i].m_frequency >= nextCenterFrequency - usableBW / 2) && (m_settings.m_frequencySettings[i].m_frequency < nextCenterFrequency + usableBW / 2)) { freqInRange = true; break; } } } while (!complete && !freqInRange); if (complete) { if (m_scanResults.size() > 0) { // Send scan results to GUI for display in table if (m_guiMessageQueue) { FreqScanner::MsgScanResult* msg = FreqScanner::MsgScanResult::create(QDateTime()); QList& guiResults = msg->getScanResults(); guiResults.append(m_scanResults); m_guiMessageQueue->push(msg); } int frequency = -1; FreqScannerSettings::FrequencySettings *frequencySettings = nullptr; FreqScannerSettings::FrequencySettings *activeFrequencySettings = nullptr; if (m_settings.m_priority == FreqScannerSettings::MAX_POWER) { Real maxPower = -200.0f; // Find frequency with max power that exceeds thresholds for (int i = 0; i < m_scanResults.size(); i++) { frequencySettings = m_settings.getFrequencySettings(m_scanResults[i].m_frequency); Real threshold = m_settings.getThreshold(frequencySettings); if (m_scanResults[i].m_power >= threshold) { if (!activeFrequencySettings || (m_scanResults[i].m_power > maxPower)) { frequency = m_scanResults[i].m_frequency; maxPower = m_scanResults[i].m_power; activeFrequencySettings = frequencySettings; } } } } else { // Find first frequency in list above threshold for (int i = 0; i < m_scanResults.size(); i++) { frequencySettings = m_settings.getFrequencySettings(m_scanResults[i].m_frequency); Real threshold = m_settings.getThreshold(frequencySettings); if (m_scanResults[i].m_power >= threshold) { frequency = m_scanResults[i].m_frequency; activeFrequencySettings = frequencySettings; break; } } } if (m_settings.m_mode != FreqScannerSettings::SCAN_ONLY) { // Were any frequencies found to be active? //if (maxPower >= m_settings.m_threshold) if (activeFrequencySettings) { // Tune device/channel to frequency int offset; if ((frequency < m_centerFrequency - usableBW / 2) || (frequency >= m_centerFrequency + usableBW / 2)) { nextCenterFrequency = frequency; offset = 0; } else { nextCenterFrequency = m_centerFrequency; offset = frequency - m_centerFrequency; } // Ensure we have minimum offset from DC if (offset >= 0) { while (offset < m_settings.m_channelFrequencyOffset) { nextCenterFrequency -= m_settings.m_channelBandwidth; offset += m_settings.m_channelBandwidth; } } else { while (abs(offset) < m_settings.m_channelFrequencyOffset) { nextCenterFrequency += m_settings.m_channelBandwidth; offset -= m_settings.m_channelBandwidth; } } //qDebug() << "Tuning to active freq:" << frequency << "m_centerFrequency" << m_centerFrequency << "nextCenterFrequency" << nextCenterFrequency << "offset: " << offset << "deviceset: R" << m_scanDeviceSetIndex << ":" << m_scanChannelIndex; QString channel = m_settings.m_channel; if (!activeFrequencySettings->m_channel.isEmpty()) { channel = activeFrequencySettings->m_channel; } applyChannelSetting(channel); // Tune the channel ChannelWebAPIUtils::setFrequencyOffset(m_scanDeviceSetIndex, m_scanChannelIndex, offset); // Unmute the channel ChannelWebAPIUtils::setAudioMute(m_scanDeviceSetIndex, m_scanChannelIndex, false); // Apply squelch if (!activeFrequencySettings->m_squelch.isEmpty()) { bool ok; Real squelch = activeFrequencySettings->m_squelch.toFloat(&ok); if (ok) { ChannelWebAPIUtils::patchChannelSetting(m_scanDeviceSetIndex, m_scanChannelIndex, "squelch", squelch); } } m_activeFrequency = frequency; if (m_settings.m_mode == FreqScannerSettings::SINGLE) { // Scan complete if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgScanComplete::create()); } m_state = IDLE; } else { // Wait for transmission to finish m_state = WAIT_FOR_END_TX; } // Becareful to only do this at the end here, as it can recursively call handleMessage with new settings if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgReportActiveFrequency::create(m_activeFrequency)); } } else { if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgStatus::create("Scanning...")); } } } } } if (nextCenterFrequency != m_centerFrequency) { setDeviceCenterFrequency(nextCenterFrequency); } if (complete) { m_scanResults.clear(); } } break; case WAIT_FOR_END_TX: for (int i = 0; i < results.size(); i++) { if (results[i].m_frequency == m_activeFrequency) { if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgReportActivePower::create(results[i].m_power)); } // Wait until power drops below threshold FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency); Real threshold = m_settings.getThreshold(frequencySettings); if (results[i].m_power < threshold) { m_timeoutTimer.setSingleShot(true); m_timeoutTimer.start((int)(m_settings.m_retransmitTime * 1000.0)); m_state = WAIT_FOR_RETRANSMISSION; break; } } } break; case WAIT_FOR_RETRANSMISSION: for (int i = 0; i < results.size(); i++) { if (results[i].m_frequency == m_activeFrequency) { if (m_guiMessageQueue) { m_guiMessageQueue->push(MsgReportActivePower::create(results[i].m_power)); } // Check if power has returned to being above threshold FreqScannerSettings::FrequencySettings *frequencySettings = m_settings.getFrequencySettings(m_activeFrequency); Real threshold = m_settings.getThreshold(frequencySettings); if (results[i].m_power >= threshold) { m_timeoutTimer.stop(); m_state = WAIT_FOR_END_TX; } } } break; } } void FreqScanner::timeout() { // Power hasn't returned above threshold - Restart scan initScan(); } void FreqScanner::calcScannerSampleRate(int channelBW, int basebandSampleRate, int& scannerSampleRate, int& fftSize, int& binsPerChannel) { const int maxFFTSize = 16384; const int minBinsPerChannel = 8; // Base FFT size on that used for main spectrum std::vector& deviceSets = MainCore::instance()->getDeviceSets(); DeviceSet* deviceSet = deviceSets[m_deviceAPI->getDeviceSetIndex()]; const SpectrumSettings& spectrumSettings = deviceSet->m_spectrumVis->getSettings(); fftSize = spectrumSettings.m_fftSize; // But ensure we have several bins per channel // Adjust sample rate, to ensure we don't get massive FFT size scannerSampleRate = basebandSampleRate; if (scannerSampleRate < channelBW) { channelBW = scannerSampleRate; // Prevent divide by 0 } while (fftSize / (scannerSampleRate / channelBW) < minBinsPerChannel) { if (fftSize == maxFFTSize) { scannerSampleRate /= 2; } else { fftSize *= 2; } } binsPerChannel = fftSize / (scannerSampleRate / (float)channelBW); } void FreqScanner::setCenterFrequency(qint64 frequency) { FreqScannerSettings settings = m_settings; settings.m_inputFrequencyOffset = frequency; applySettings(settings, {"inputFrequencyOffset"}, false); if (m_guiMessageQueue) // forward to GUI if any { MsgConfigureFreqScanner *msgToGUI = MsgConfigureFreqScanner::create(settings, {"inputFrequencyOffset"}, false); m_guiMessageQueue->push(msgToGUI); } } // Mute all channels void FreqScanner::muteAll(const FreqScannerSettings& settings) { QStringList channels; channels.append(settings.m_channel); for (int i = 0; i < settings.m_frequencySettings.size(); i++) { QString channel = settings.m_frequencySettings[i].m_channel; if (!channel.isEmpty() && !channels.contains(channel)) { channels.append(channel); } } for (const auto& channel : channels) { unsigned int deviceSetIndex, channelIndex; if (MainCore::getDeviceAndChannelIndexFromId(channel, deviceSetIndex, channelIndex)) { ChannelWebAPIUtils::setAudioMute(deviceSetIndex, channelIndex, true); } } } void FreqScanner::applyChannelSetting(const QString& channel) { if (!MainCore::getDeviceAndChannelIndexFromId(channel, m_scanDeviceSetIndex, m_scanChannelIndex)) { qDebug() << "FreqScanner::applySettings: Failed to parse channel" << channel; } } void FreqScanner::applySettings(const FreqScannerSettings& settings, const QStringList& settingsKeys, bool force) { qDebug() << "FreqScanner::applySettings:" << settings.getDebugString(settingsKeys, force) << " force: " << force; if (settingsKeys.contains("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); //FIXME:scanAvailableChannels(); // re-scan emit streamIndexChanged(settings.m_streamIndex); } } if (m_running) { FreqScannerBaseband::MsgConfigureFreqScannerBaseband *msg = FreqScannerBaseband::MsgConfigureFreqScannerBaseband::create(settings, settingsKeys, force); m_basebandSink->getInputMessageQueue()->push(msg); } if (settings.m_useReverseAPI) { bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) || settingsKeys.contains("reverseAPIAddress") || settingsKeys.contains("reverseAPIPort") || settingsKeys.contains("reverseAPIDeviceIndex") || settingsKeys.contains("reverseAPIChannelIndex"); webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); } if (settingsKeys.contains("frequencySettings") || settingsKeys.contains("priority") || settingsKeys.contains("measurement") || settingsKeys.contains("mode") || settingsKeys.contains("channelBandwidth") || force) { // Restart scan if any settings change if (m_state != IDLE) { startScan(); } } if (force) { m_settings = settings; } else { m_settings.applySettings(settingsKeys, settings); } } QByteArray FreqScanner::serialize() const { return m_settings.serialize(); } bool FreqScanner::deserialize(const QByteArray& data) { if (m_settings.deserialize(data)) { MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(m_settings, QStringList(), true); m_inputMessageQueue.push(msg); return true; } else { m_settings.resetToDefaults(); MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(m_settings, QStringList(), true); m_inputMessageQueue.push(msg); return false; } } int FreqScanner::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage) { (void) errorMessage; response.setFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings()); response.getFreqScannerSettings()->init(); webapiFormatChannelSettings(response, m_settings); return 200; } int FreqScanner::webapiWorkspaceGet( SWGSDRangel::SWGWorkspaceInfo& response, QString& errorMessage) { (void) errorMessage; response.setIndex(m_settings.m_workspaceIndex); return 200; } int FreqScanner::webapiSettingsPutPatch( bool force, const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response, QString& errorMessage) { (void) errorMessage; FreqScannerSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); MsgConfigureFreqScanner *msg = MsgConfigureFreqScanner::create(settings, channelSettingsKeys, force); m_inputMessageQueue.push(msg); qDebug("FreqScanner::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); if (m_guiMessageQueue) // forward to GUI if any { MsgConfigureFreqScanner *msgToGUI = MsgConfigureFreqScanner::create(settings, channelSettingsKeys, force); m_guiMessageQueue->push(msgToGUI); } webapiFormatChannelSettings(response, settings); return 200; } int FreqScanner::webapiReportGet( SWGSDRangel::SWGChannelReport& response, QString& errorMessage) { (void) errorMessage; response.setFreqScannerReport(new SWGSDRangel::SWGFreqScannerReport()); response.getFreqScannerReport()->init(); webapiFormatChannelReport(response); return 200; } void FreqScanner::webapiUpdateChannelSettings( FreqScannerSettings& settings, const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response) { if (channelSettingsKeys.contains("channelFrequencyOffset")) { settings.m_channelFrequencyOffset = response.getFreqScannerSettings()->getChannelFrequencyOffset(); } if (channelSettingsKeys.contains("channelBandwidth")) { settings.m_channelBandwidth = response.getFreqScannerSettings()->getChannelBandwidth(); } if (channelSettingsKeys.contains("threshold")) { settings.m_threshold = response.getFreqScannerSettings()->getThreshold(); } if (channelSettingsKeys.contains("frequencies")) { settings.m_frequencySettings.clear(); QList *frequencies = response.getFreqScannerSettings()->getFrequencies(); if (frequencies) { for (const auto frequency : *frequencies) { FreqScannerSettings::FrequencySettings freqSetting; freqSetting.m_frequency = frequency->getFrequency(); if (frequency->getNotes()) { freqSetting.m_notes = *frequency->getNotes(); } if (frequency->getChannel()) { freqSetting.m_channel = *frequency->getChannel(); } if (frequency->getChannelBandwidth()) { freqSetting.m_channelBandwidth = *frequency->getChannelBandwidth(); } if (frequency->getThreshold()) { freqSetting.m_threshold = *frequency->getThreshold(); } if (frequency->getSquelch()) { freqSetting.m_squelch = *frequency->getSquelch(); } settings.m_frequencySettings.append(freqSetting); } } } if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getFreqScannerSettings()->getRgbColor(); } if (channelSettingsKeys.contains("title")) { settings.m_title = *response.getFreqScannerSettings()->getTitle(); } if (channelSettingsKeys.contains("streamIndex")) { settings.m_streamIndex = response.getFreqScannerSettings()->getStreamIndex(); } if (channelSettingsKeys.contains("useReverseAPI")) { settings.m_useReverseAPI = response.getFreqScannerSettings()->getUseReverseApi() != 0; } if (channelSettingsKeys.contains("reverseAPIAddress")) { settings.m_reverseAPIAddress = *response.getFreqScannerSettings()->getReverseApiAddress(); } if (channelSettingsKeys.contains("reverseAPIPort")) { settings.m_reverseAPIPort = response.getFreqScannerSettings()->getReverseApiPort(); } if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { settings.m_reverseAPIDeviceIndex = response.getFreqScannerSettings()->getReverseApiDeviceIndex(); } if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { settings.m_reverseAPIChannelIndex = response.getFreqScannerSettings()->getReverseApiChannelIndex(); } if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) { settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getFreqScannerSettings()->getChannelMarker()); } if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) { settings.m_rollupState->updateFrom(channelSettingsKeys, response.getFreqScannerSettings()->getRollupState()); } } QList *FreqScanner::createFrequencyList(const FreqScannerSettings& settings) { QList *frequencies = new QList(); for (int i = 0; i < settings.m_frequencySettings.size(); i++) { SWGSDRangel::SWGFreqScannerFrequency *frequency = new SWGSDRangel::SWGFreqScannerFrequency(); frequency->init(); frequency->setFrequency(settings.m_frequencySettings[i].m_frequency); frequency->setEnabled(settings.m_frequencySettings[i].m_enabled); if (!settings.m_frequencySettings[i].m_notes.isEmpty()) { frequency->setNotes(new QString(settings.m_frequencySettings[i].m_notes)); } if (!settings.m_frequencySettings[i].m_channel.isEmpty()) { frequency->setChannel(new QString(settings.m_frequencySettings[i].m_channel)); } if (!settings.m_frequencySettings[i].m_channelBandwidth.isEmpty()) { frequency->setChannelBandwidth(new QString(settings.m_frequencySettings[i].m_channelBandwidth)); } if (!settings.m_frequencySettings[i].m_threshold.isEmpty()) { frequency->setThreshold(new QString(settings.m_frequencySettings[i].m_threshold)); } if (!settings.m_frequencySettings[i].m_squelch.isEmpty()) { frequency->setSquelch(new QString(settings.m_frequencySettings[i].m_squelch)); } frequencies->append(frequency); } return frequencies; } void FreqScanner::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreqScannerSettings& settings) { response.getFreqScannerSettings()->setChannelFrequencyOffset(settings.m_channelFrequencyOffset); response.getFreqScannerSettings()->setChannelBandwidth(settings.m_channelBandwidth); response.getFreqScannerSettings()->setThreshold(settings.m_threshold); QList *frequencies = createFrequencyList(settings); if (response.getFreqScannerSettings()->getFrequencies()) { *response.getFreqScannerSettings()->getFrequencies() = *frequencies; } else { response.getFreqScannerSettings()->setFrequencies(frequencies); } response.getFreqScannerSettings()->setRgbColor(settings.m_rgbColor); if (response.getFreqScannerSettings()->getTitle()) { *response.getFreqScannerSettings()->getTitle() = settings.m_title; } else { response.getFreqScannerSettings()->setTitle(new QString(settings.m_title)); } response.getFreqScannerSettings()->setStreamIndex(settings.m_streamIndex); response.getFreqScannerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); if (response.getFreqScannerSettings()->getReverseApiAddress()) { *response.getFreqScannerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; } else { response.getFreqScannerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); } response.getFreqScannerSettings()->setReverseApiPort(settings.m_reverseAPIPort); response.getFreqScannerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); response.getFreqScannerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); if (settings.m_channelMarker) { if (response.getFreqScannerSettings()->getChannelMarker()) { settings.m_channelMarker->formatTo(response.getFreqScannerSettings()->getChannelMarker()); } else { SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker(); settings.m_channelMarker->formatTo(swgChannelMarker); response.getFreqScannerSettings()->setChannelMarker(swgChannelMarker); } } if (settings.m_rollupState) { if (response.getFreqScannerSettings()->getRollupState()) { settings.m_rollupState->formatTo(response.getFreqScannerSettings()->getRollupState()); } else { SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState(); settings.m_rollupState->formatTo(swgRollupState); response.getFreqScannerSettings()->setRollupState(swgRollupState); } } } void FreqScanner::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) { response.getFreqScannerReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); } void FreqScanner::webapiReverseSendSettings(const QStringList& channelSettingsKeys, const FreqScannerSettings& 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 FreqScanner::webapiFormatChannelSettings( const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings *swgChannelSettings, const FreqScannerSettings& settings, bool force ) { swgChannelSettings->setDirection(0); // Single sink (Rx) swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); swgChannelSettings->setChannelType(new QString("FreqScanner")); swgChannelSettings->setFreqScannerSettings(new SWGSDRangel::SWGFreqScannerSettings()); SWGSDRangel::SWGFreqScannerSettings *swgFreqScannerSettings = swgChannelSettings->getFreqScannerSettings(); // transfer data that has been modified. When force is on transfer all data except reverse API data if (channelSettingsKeys.contains("channelFrequencyOffset") || force) { swgFreqScannerSettings->setChannelFrequencyOffset(settings.m_channelFrequencyOffset); } if (channelSettingsKeys.contains("channelBandwidth") || force) { swgFreqScannerSettings->setChannelBandwidth(settings.m_channelBandwidth); } if (channelSettingsKeys.contains("threshold") || force) { swgFreqScannerSettings->setThreshold(settings.m_threshold); } if (channelSettingsKeys.contains("frequencies") || force) { QList *frequencies = createFrequencyList(settings); if (swgFreqScannerSettings->getFrequencies()) { *swgFreqScannerSettings->getFrequencies() = *frequencies; } else { swgFreqScannerSettings->setFrequencies(frequencies); } } if (channelSettingsKeys.contains("rgbColor") || force) { swgFreqScannerSettings->setRgbColor(settings.m_rgbColor); } if (channelSettingsKeys.contains("title") || force) { swgFreqScannerSettings->setTitle(new QString(settings.m_title)); } if (channelSettingsKeys.contains("streamIndex") || force) { swgFreqScannerSettings->setStreamIndex(settings.m_streamIndex); } if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force)) { SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker(); settings.m_channelMarker->formatTo(swgChannelMarker); swgFreqScannerSettings->setChannelMarker(swgChannelMarker); } if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force)) { SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState(); settings.m_rollupState->formatTo(swgRollupState); swgFreqScannerSettings->setRollupState(swgRollupState); } } void FreqScanner::networkManagerFinished(QNetworkReply *reply) { QNetworkReply::NetworkError replyError = reply->error(); if (replyError) { qWarning() << "FreqScanner::networkManagerFinished:" << " error(" << (int) replyError << "): " << replyError << ": " << reply->errorString(); } else { QString answer = reply->readAll(); answer.chop(1); // remove last \n qDebug("FreqScanner::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); } reply->deleteLater(); } void FreqScanner::handleIndexInDeviceSetChanged(int index) { if (!m_running || (index < 0)) { return; } QString fifoLabel = QString("%1 [%2:%3]") .arg(m_channelId) .arg(m_deviceAPI->getDeviceSetIndex()) .arg(index); m_basebandSink->setFifoLabel(fifoLabel); } void FreqScanner::channelsChanged(const QStringList& renameFrom, const QStringList& renameTo) { m_availableChannels = m_availableChannelHandler.getAvailableChannelOrFeatureList(); notifyUpdateChannels(renameFrom, renameTo); } void FreqScanner::notifyUpdateChannels(const QStringList& renameFrom, const QStringList& renameTo) { if (getMessageQueueToGUI()) { MsgReportChannels* msgToGUI = MsgReportChannels::create(renameFrom, renameTo); msgToGUI->getChannels() = m_availableChannels; getMessageQueueToGUI()->push(msgToGUI); } }