diff --git a/CHANGELOG b/CHANGELOG index 808e0c97c..40ff5e044 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +sdrangel (7.3.0-1) unstable; urgency=medium + + * New Direction Of Arrival with 2 sources MI plugin. Implements #444 + * Upgraded codec2 to v1.0.3. Fixes #1256 + * FreeDV mod: improved output power + * FreeDV plugins: fixed spanLog2 GUI connection + * Fixed Interferometer and BeamSteeringCW absolute center frequency display + * Fixed Interferometer and BeamSteeringCW MIMO channel plugins + * Fixed Mac build. PR #1257 + + -- Edouard Griffiths, F4EXB Thu, 26 May 2022 03:12:00 +0200 + sdrangel (7.2.1-1) unstable; urgency=medium * AMBE feature: fixed many errors preventing build on Windows and MacOS. Issue #1254 diff --git a/CMakeLists.txt b/CMakeLists.txt index c9afd5942..9b791e238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # configure version set(sdrangel_VERSION_MAJOR "7") -set(sdrangel_VERSION_MINOR "2") -set(sdrangel_VERSION_PATCH "1") +set(sdrangel_VERSION_MINOR "3") +set(sdrangel_VERSION_PATCH "0") set(sdrangel_VERSION_SUFFIX "") # SDRAngel cmake options diff --git a/cmake/ci/build_codec2.sh b/cmake/ci/build_codec2.sh index 15ca607db..dc5b6e364 100644 --- a/cmake/ci/build_codec2.sh +++ b/cmake/ci/build_codec2.sh @@ -9,7 +9,7 @@ mkdir -p drowe67 && cd drowe67 git clone https://github.com/drowe67/codec2.git cd codec2 -git reset --hard "v0.9.2" +git reset --hard "v1.0.3" mkdir -p build && cd build cmake .. sudo make install diff --git a/debian/changelog b/debian/changelog index dd9b651e2..47b755139 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +sdrangel (7.3.0-1) unstable; urgency=medium + + * New Direction Of Arrival with 2 sources MI plugin. Implements #444 + * Upgraded codec2 to v1.0.3. Fixes #1256 + * FreeDV mod: improved output power + * FreeDV plugins: fixed spanLog2 GUI connection + * Fixed Interferometer and BeamSteeringCW absolute center frequency display + * Fixed Interferometer and BeamSteeringCW MIMO channel plugins + * Fixed Mac build. PR #1257 + + -- Edouard Griffiths, F4EXB Sun, 29 May 2022 10:05:21 +0200 + sdrangel (7.2.1-1) unstable; urgency=medium * AMBE feature: fixed many errors preventing build on Windows and MacOS. Issue #1254 diff --git a/doc/img/DOA2_plugin.png b/doc/img/DOA2_plugin.png new file mode 100644 index 000000000..9fe5c9319 Binary files /dev/null and b/doc/img/DOA2_plugin.png differ diff --git a/doc/img/DOA2_plugin.xcf b/doc/img/DOA2_plugin.xcf new file mode 100644 index 000000000..02ac84aa7 Binary files /dev/null and b/doc/img/DOA2_plugin.xcf differ diff --git a/doc/img/DOA2_plugin_DOA.png b/doc/img/DOA2_plugin_DOA.png new file mode 100644 index 000000000..2e7a654ed Binary files /dev/null and b/doc/img/DOA2_plugin_DOA.png differ diff --git a/doc/img/DOA2_plugin_DOA.xcf b/doc/img/DOA2_plugin_DOA.xcf new file mode 100644 index 000000000..3e7eba414 Binary files /dev/null and b/doc/img/DOA2_plugin_DOA.xcf differ diff --git a/doc/img/DOA2_plugin_compass.png b/doc/img/DOA2_plugin_compass.png new file mode 100644 index 000000000..3eeeb4a0b Binary files /dev/null and b/doc/img/DOA2_plugin_compass.png differ diff --git a/doc/img/DOA2_plugin_compass.xcf b/doc/img/DOA2_plugin_compass.xcf new file mode 100644 index 000000000..dafd013aa Binary files /dev/null and b/doc/img/DOA2_plugin_compass.xcf differ diff --git a/doc/img/DOA2_plugin_full.png b/doc/img/DOA2_plugin_full.png new file mode 100644 index 000000000..f3a97572a Binary files /dev/null and b/doc/img/DOA2_plugin_full.png differ diff --git a/doc/img/DOA2_plugin_settings.png b/doc/img/DOA2_plugin_settings.png new file mode 100644 index 000000000..15d2b3213 Binary files /dev/null and b/doc/img/DOA2_plugin_settings.png differ diff --git a/doc/img/DOA2_plugin_settings.xcf b/doc/img/DOA2_plugin_settings.xcf new file mode 100644 index 000000000..744138f90 Binary files /dev/null and b/doc/img/DOA2_plugin_settings.xcf differ diff --git a/doc/img/DOA2_plugin_xy.png b/doc/img/DOA2_plugin_xy.png new file mode 100644 index 000000000..84603c3eb Binary files /dev/null and b/doc/img/DOA2_plugin_xy.png differ diff --git a/doc/img/interferometer_doa_general.png b/doc/img/interferometer_doa_general.png new file mode 100644 index 000000000..5e4545be5 Binary files /dev/null and b/doc/img/interferometer_doa_general.png differ diff --git a/doc/img/mimo_steering_angle.odg b/doc/img/mimo_steering_angle.odg new file mode 100644 index 000000000..0f9a624ac Binary files /dev/null and b/doc/img/mimo_steering_angle.odg differ diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 07cdbff27..b3f3d007a 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -8,7 +8,7 @@ # but make the configuration dirty # which version/tag/checkout to use -set(CODEC2_TAG "v0.9.2") +set(CODEC2_TAG "v1.0.3") set(CM256CC_TAG c0e92b92aca3d1d36c990b642b937c64d363c559) set(MBELIB_TAG fe83b32c6a60cdd7bce8cecf3c7a0b9ec87a7667) set(SERIALDV_TAG "v1.1.4") diff --git a/plugins/channelmimo/CMakeLists.txt b/plugins/channelmimo/CMakeLists.txt index 5b951ebc6..0465d259a 100644 --- a/plugins/channelmimo/CMakeLists.txt +++ b/plugins/channelmimo/CMakeLists.txt @@ -1,6 +1,7 @@ project(channelmimo) add_subdirectory(beamsteeringcwmod) +add_subdirectory(doa2) if (NOT SERVER_MODE) add_subdirectory(interferometer) diff --git a/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodplugin.cpp b/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodplugin.cpp index 5dfeb6ec4..9a02d526d 100644 --- a/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodplugin.cpp +++ b/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodplugin.cpp @@ -29,7 +29,7 @@ const PluginDescriptor BeamSteeringCWModPlugin::m_pluginDescriptor = { BeamSteeringCWMod::m_channelId, QStringLiteral("BeamSteeringCWMod"), - QStringLiteral("7.0.0"), + QStringLiteral("7.3.0"), QStringLiteral("(c) Edouard Griffiths, F4EXB"), QStringLiteral("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelmimo/doa2/CMakeLists.txt b/plugins/channelmimo/doa2/CMakeLists.txt new file mode 100644 index 000000000..42cc6d2f0 --- /dev/null +++ b/plugins/channelmimo/doa2/CMakeLists.txt @@ -0,0 +1,69 @@ +project(doa2) + +set(doa2_SOURCES + doa2.cpp + doa2corr.cpp + doa2settings.cpp + doa2baseband.cpp + doa2streamsink.cpp + doa2plugin.cpp + doa2webapiadapter.cpp +) + +set(doa2_HEADERS + doa2.h + doa2corr.h + doa2settings.h + doa2baseband.h + doa2streamsink.h + doa2plugin.h + doa2webapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${Boost_INCLUDE_DIR} +) + +if (NOT SERVER_MODE) + set(doa2_SOURCES + ${doa2_SOURCES} + doa2gui.cpp + doa2compass.cpp + doa2gui.ui + ) + set(doa2_HEADERS + ${doa2_HEADERS} + doa2gui.h + doa2compass.cpp + ) + + set(TARGET_NAME doa2) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME doa2srv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${doa2_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) + +# Install debug symbols +if (WIN32) + install(FILES $ CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} ) +endif() diff --git a/plugins/channelmimo/doa2/doa2.cpp b/plugins/channelmimo/doa2/doa2.cpp new file mode 100644 index 000000000..8fe43811e --- /dev/null +++ b/plugins/channelmimo/doa2/doa2.cpp @@ -0,0 +1,684 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGWorkspaceInfo.h" +#include "SWGChannelReport.h" + +#include "device/deviceapi.h" +#include "dsp/hbfilterchainconverter.h" +#include "dsp/dspcommands.h" +#include "feature/feature.h" +#include "util/db.h" +#include "maincore.h" + +#include "doa2baseband.h" +#include "doa2.h" + +MESSAGE_CLASS_DEFINITION(DOA2::MsgConfigureDOA2, Message) +MESSAGE_CLASS_DEFINITION(DOA2::MsgBasebandNotification, Message) + +const char* const DOA2::m_channelIdURI = "sdrangel.channel.doa2"; +const char* const DOA2::m_channelId = "DOA2"; +const int DOA2::m_fftSize = 4096; + +DOA2::DOA2(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamMIMO), + m_deviceAPI(deviceAPI), + m_guiMessageQueue(nullptr), + m_frequencyOffset(0), + m_deviceSampleRate(48000), + m_deviceCenterFrequency(435000000) +{ + setObjectName(m_channelId); + + m_thread = new QThread(this); + m_basebandSink = new DOA2Baseband(m_fftSize); + m_basebandSink->setScopeSink(&m_scopeSink); + m_basebandSink->moveToThread(m_thread); + m_deviceAPI->addMIMOChannel(this); + m_deviceAPI->addMIMOChannelAPI(this); + + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &DOA2::networkManagerFinished + ); +} + +DOA2::~DOA2() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &DOA2::networkManagerFinished + ); + delete m_networkManager; + + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeMIMOChannel(this); + delete m_basebandSink; + delete m_thread; +} + +void DOA2::setDeviceAPI(DeviceAPI *deviceAPI) +{ + if (deviceAPI != m_deviceAPI) + { + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeMIMOChannel(this); + m_deviceAPI = deviceAPI; + m_deviceAPI->addMIMOChannel(this); + m_deviceAPI->addChannelSinkAPI(this); + } +} + +void DOA2::startSinks() +{ + if (m_deviceSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_deviceSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); + + DOA2Baseband::MsgConfigureChannelizer *msg = DOA2Baseband::MsgConfigureChannelizer::create( + m_settings.m_log2Decim, m_settings.m_filterChainHash); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +void DOA2::stopSinks() +{ + m_thread->exit(); + m_thread->wait(); +} + +void DOA2::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex) +{ + m_basebandSink->feed(begin, end, sinkIndex); +} + +void DOA2::pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int sourceIndex) +{ + (void) begin; + (void) nbSamples; + (void) sourceIndex; +} + +void DOA2::applySettings(const DOA2Settings& settings, bool force) +{ + qDebug() << "DOA2::applySettings: " + << "m_correlationType: " << settings.m_correlationType + << "m_filterChainHash: " << settings.m_filterChainHash + << "m_log2Decim: " << settings.m_log2Decim + << "m_phase: " << settings.m_phase + << "m_antennaAz:" << settings.m_antennaAz + << "m_basebandDistance: " << settings.m_basebandDistance + << "m_squelchdB: " << settings.m_squelchdB + << "m_fftAveragingIndex: "<< settings.m_fftAveragingIndex + << "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 + << "m_title: " << settings.m_title; + + QList reverseAPIKeys; + + if ((m_settings.m_correlationType != settings.m_correlationType) || force) { + reverseAPIKeys.append("correlationType"); + } + if ((m_settings.m_filterChainHash != settings.m_filterChainHash) || force) { + reverseAPIKeys.append("filterChainHash"); + } + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) { + reverseAPIKeys.append("log2Decim"); + } + if ((m_settings.m_phase != settings.m_phase) || force) { + reverseAPIKeys.append("phase"); + } + if ((m_settings.m_title != settings.m_title) || force) { + reverseAPIKeys.append("title"); + } + if ((m_settings.m_antennaAz != settings.m_antennaAz) || force) { + reverseAPIKeys.append("antennaAz"); + } + if ((m_settings.m_basebandDistance != settings.m_basebandDistance) || force) { + reverseAPIKeys.append("basebandDistance"); + } + + if ((m_settings.m_squelchdB != settings.m_squelchdB) || force) + { + reverseAPIKeys.append("squelchdB"); + m_basebandSink->setMagThreshold(CalcDb::powerFromdB(settings.m_squelchdB)); + } + + if ((m_settings.m_fftAveragingIndex != settings.m_fftAveragingIndex) || force) + { + reverseAPIKeys.append("m_fftAveragingIndex"); + m_basebandSink->setFFTAveraging(DOA2Settings::getAveragingValue(settings.m_fftAveragingIndex)); + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) + || (m_settings.m_filterChainHash != settings.m_filterChainHash) || force) + { + DOA2Baseband::MsgConfigureChannelizer *msg = DOA2Baseband::MsgConfigureChannelizer::create( + settings.m_log2Decim, settings.m_filterChainHash); + m_basebandSink->getInputMessageQueue()->push(msg); + } + + if ((m_settings.m_correlationType != settings.m_correlationType) || force) + { + DOA2Baseband::MsgConfigureCorrelation *msg = DOA2Baseband::MsgConfigureCorrelation::create( + settings.m_correlationType); + m_basebandSink->getInputMessageQueue()->push(msg); + } + + if ((m_settings.m_phase != settings.m_phase) || force) { + m_basebandSink->setPhase(settings.m_phase); + } + + QList pipes; + MainCore::instance()->getMessagePipes().getMessagePipes(this, "settings", pipes); + + if (pipes.size() > 0) { + sendChannelSettings(pipes, reverseAPIKeys, settings, force); + } + + m_settings = settings; +} + +void DOA2::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +bool DOA2::handleMessage(const Message& cmd) +{ + if (MsgConfigureDOA2::match(cmd)) + { + MsgConfigureDOA2& cfg = (MsgConfigureDOA2&) cmd; + qDebug() << "DOA2::handleMessage: MsgConfigureDOA2"; + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else if (DSPMIMOSignalNotification::match(cmd)) + { + DSPMIMOSignalNotification& notif = (DSPMIMOSignalNotification&) cmd; + + qDebug() << "DOA2::handleMessage: DSPMIMOSignalNotification:" + << " inputSampleRate: " << notif.getSampleRate() + << " centerFrequency: " << notif.getCenterFrequency() + << " sourceElseSink: " << notif.getSourceOrSink() + << " streamIndex: " << notif.getIndex(); + + if (notif.getSourceOrSink()) // deals with source messages only + { + m_deviceSampleRate = notif.getSampleRate(); + m_deviceCenterFrequency = notif.getCenterFrequency(); + calculateFrequencyOffset(); // This is when device sample rate changes + + // Notify baseband sink of input sample rate change + DOA2Baseband::MsgSignalNotification *sig = DOA2Baseband::MsgSignalNotification::create( + m_deviceSampleRate, notif.getCenterFrequency(), notif.getIndex() + ); + qDebug() << "DOA2::handleMessage: DSPMIMOSignalNotification: push to sink"; + m_basebandSink->getInputMessageQueue()->push(sig); + + if (getMessageQueueToGUI()) + { + qDebug() << "DOA2::handleMessage: DSPMIMOSignalNotification: push to GUI"; + MsgBasebandNotification *msg = MsgBasebandNotification::create( + notif.getSampleRate(), notif.getCenterFrequency()); + getMessageQueueToGUI()->push(msg); + } + } + + return true; + } + else + { + return false; + } +} + +QByteArray DOA2::serialize() const +{ + return m_settings.serialize(); +} + +bool DOA2::deserialize(const QByteArray& data) +{ + (void) data; + if (m_settings.deserialize(data)) + { + MsgConfigureDOA2 *msg = MsgConfigureDOA2::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureDOA2 *msg = MsgConfigureDOA2::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void DOA2::validateFilterChainHash(DOA2Settings& settings) +{ + unsigned int s = 1; + + for (unsigned int i = 0; i < settings.m_log2Decim; i++) { + s *= 3; + } + + settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash; +} + +void DOA2::calculateFrequencyOffset() +{ + double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Decim, m_settings.m_filterChainHash); + m_frequencyOffset = m_deviceSampleRate * shiftFactor; +} + +void DOA2::applyChannelSettings(uint32_t log2Decim, uint32_t filterChainHash) +{ + DOA2Baseband::MsgConfigureChannelizer *msg = DOA2Baseband::MsgConfigureChannelizer::create(log2Decim, filterChainHash); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +float DOA2::getPhi() const +{ + return m_basebandSink ? m_basebandSink->getPhi() : 0.0f; +} + +float DOA2::getPositiveDOA() const +{ + return std::acos(getPhi()/M_PI)*(180/M_PI); +} + +int DOA2::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setDoa2Settings(new SWGSDRangel::SWGDOA2Settings()); + response.getDoa2Settings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DOA2::webapiWorkspaceGet( + SWGSDRangel::SWGWorkspaceInfo& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setIndex(m_settings.m_workspaceIndex); + return 200; +} + +int DOA2::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + DOA2Settings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigureDOA2 *msg = MsgConfigureDOA2::create(settings, force); + m_inputMessageQueue.push(msg); + + if (getMessageQueueToGUI()) // forward to GUI if any + { + MsgConfigureDOA2 *msgToGUI = MsgConfigureDOA2::create(settings, force); + getMessageQueueToGUI()->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int DOA2::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setDoa2Report(new SWGSDRangel::SWGDOA2Report()); + response.getDoa2Report()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void DOA2::webapiUpdateChannelSettings( + DOA2Settings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDoa2Settings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDoa2Settings()->getTitle(); + } + if (channelSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getDoa2Settings()->getLog2Decim(); + } + + if (channelSettingsKeys.contains("filterChainHash")) + { + settings.m_filterChainHash = response.getDoa2Settings()->getFilterChainHash(); + validateFilterChainHash(settings); + } + + if (channelSettingsKeys.contains("phase")) { + settings.m_phase = response.getDoa2Settings()->getPhase(); + } + if (channelSettingsKeys.contains("antennaAz")) { + settings.m_antennaAz = response.getDoa2Settings()->getAntennaAz(); + } + if (channelSettingsKeys.contains("basebandDistance")) { + settings.m_basebandDistance = response.getDoa2Settings()->getBasebandDistance(); + } + if (channelSettingsKeys.contains("squelchdB")) { + settings.m_squelchdB = response.getDoa2Settings()->getSquelchdB(); + } + if (channelSettingsKeys.contains("fftAveragingValue")) { + settings.m_fftAveragingIndex = DOA2Settings::getAveragingIndex(response.getDoa2Settings()->getFftAveragingValue()); + } + + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getDoa2Settings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getDoa2Settings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getDoa2Settings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getDoa2Settings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getDoa2Settings()->getReverseApiChannelIndex(); + } + if (settings.m_scopeGUI && channelSettingsKeys.contains("scopeConfig")) { + settings.m_scopeGUI->updateFrom(channelSettingsKeys, response.getDoa2Settings()->getScopeConfig()); + } + if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) { + settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getDoa2Settings()->getChannelMarker()); + } + if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) { + settings.m_rollupState->updateFrom(channelSettingsKeys, response.getDoa2Settings()->getRollupState()); + } +} + +void DOA2::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DOA2Settings& settings) +{ + response.getDoa2Settings()->setRgbColor(settings.m_rgbColor); + + if (response.getDoa2Settings()->getTitle()) { + *response.getDoa2Settings()->getTitle() = settings.m_title; + } else { + response.getDoa2Settings()->setTitle(new QString(settings.m_title)); + } + + response.getDoa2Settings()->setLog2Decim(settings.m_log2Decim); + response.getDoa2Settings()->setFilterChainHash(settings.m_filterChainHash); + response.getDoa2Settings()->setPhase(settings.m_phase); + response.getDoa2Settings()->setAntennaAz(settings.m_antennaAz); + response.getDoa2Settings()->setBasebandDistance(settings.m_basebandDistance); + response.getDoa2Settings()->setSquelchdB(settings.m_squelchdB); + response.getDoa2Settings()->setFftAveragingValue(DOA2Settings::getAveragingValue(settings.m_fftAveragingIndex)); + response.getDoa2Settings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getDoa2Settings()->getReverseApiAddress()) { + *response.getDoa2Settings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getDoa2Settings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getDoa2Settings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getDoa2Settings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getDoa2Settings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); + + if (settings.m_scopeGUI) + { + if (response.getDoa2Settings()->getScopeConfig()) + { + settings.m_scopeGUI->formatTo(response.getDoa2Settings()->getScopeConfig()); + } + else + { + SWGSDRangel::SWGGLScope *swgGLScope = new SWGSDRangel::SWGGLScope(); + settings.m_scopeGUI->formatTo(swgGLScope); + response.getDoa2Settings()->setScopeConfig(swgGLScope); + } + } + + if (settings.m_channelMarker) + { + if (response.getDoa2Settings()->getChannelMarker()) + { + settings.m_channelMarker->formatTo(response.getDoa2Settings()->getChannelMarker()); + } + else + { + SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker(); + settings.m_channelMarker->formatTo(swgChannelMarker); + response.getDoa2Settings()->setChannelMarker(swgChannelMarker); + } + } + + if (settings.m_rollupState) + { + if (response.getDoa2Settings()->getRollupState()) + { + settings.m_rollupState->formatTo(response.getDoa2Settings()->getRollupState()); + } + else + { + SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState(); + settings.m_rollupState->formatTo(swgRollupState); + response.getDoa2Settings()->setRollupState(swgRollupState); + } + } +} + +void DOA2::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + float phi = normalizeAngle(getPhi() * (180/M_PI), 180.0f); + response.getDoa2Report()->setPhi(phi); + + float hwl = 1.5e8 / (m_deviceCenterFrequency + m_frequencyOffset); + float cosTheta = (getPhi() * hwl * 1000.0) / (M_PI * m_settings.m_basebandDistance); + // float blindAngle = ((cosTheta < -1.0) || (cosTheta > 1.0) ? 0 : std::acos(hwl * 1000.0 / m_settings.m_basebandDistance)) * (180/M_PI); + float doaAngle = std::acos(cosTheta < -1.0 ? -1.0 : cosTheta > 1.0 ? 1.0 : cosTheta) * (180/M_PI); + qDebug("DOA2::webapiFormatChannelReport: phi: %f cosT: %f DOAngle: %f", getPhi(), cosTheta, doaAngle); + float posAngle = normalizeAngle(m_settings.m_antennaAz - doaAngle, 360.0f); // DOA angles are trigonometric but displayed angles are clockwise + float negAngle = normalizeAngle(m_settings.m_antennaAz + doaAngle, 360.0f); + response.getDoa2Report()->setPosAz(posAngle); + response.getDoa2Report()->setNegAz(negAngle); + + response.getDoa2Report()->setFftSize(m_fftSize); + int channelSampleRate = m_deviceSampleRate / (1<setChannelSampleRate(channelSampleRate); +} + +void DOA2::webapiReverseSendSettings(QList& channelSettingsKeys, const DOA2Settings& 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); + reply->setParent(buffer); + + delete swgChannelSettings; +} + +void DOA2::sendChannelSettings( + const QList& pipes, + QList& channelSettingsKeys, + const DOA2Settings& settings, + bool force) +{ + for (const auto& pipe : pipes) + { + MessageQueue *messageQueue = qobject_cast(pipe->m_element); + + if (messageQueue) + { + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force); + MainCore::MsgChannelSettings *msg = MainCore::MsgChannelSettings::create( + this, + channelSettingsKeys, + swgChannelSettings, + force + ); + messageQueue->push(msg); + } + } +} + +void DOA2::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const DOA2Settings& settings, + bool force +) +{ + swgChannelSettings->setDirection(2); // MIMO sink + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("DOA2")); + swgChannelSettings->setDoa2Settings(new SWGSDRangel::SWGDOA2Settings()); + SWGSDRangel::SWGDOA2Settings *swgDOA2Settings = swgChannelSettings->getDoa2Settings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("rgbColor") || force) { + swgDOA2Settings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgDOA2Settings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("log2Decim") || force) { + swgDOA2Settings->setLog2Decim(settings.m_log2Decim); + } + if (channelSettingsKeys.contains("filterChainHash") || force) { + swgDOA2Settings->setFilterChainHash(settings.m_filterChainHash); + } + if (channelSettingsKeys.contains("phase") || force) { + swgDOA2Settings->setPhase(settings.m_phase); + } + if (channelSettingsKeys.contains("antennaAz") || force) { + swgDOA2Settings->setAntennaAz(settings.m_antennaAz); + } + if (channelSettingsKeys.contains("basebandDistance") || force) { + swgDOA2Settings->setBasebandDistance(settings.m_basebandDistance); + } + if (channelSettingsKeys.contains("squelchdB") || force) { + swgDOA2Settings->setSquelchdB(settings.m_squelchdB); + } + if (channelSettingsKeys.contains("fftAveragingValue") || force) { + swgDOA2Settings->setFftAveragingValue(DOA2Settings::getAveragingValue(settings.m_fftAveragingIndex)); + } + + if (settings.m_scopeGUI) + { + if (channelSettingsKeys.contains("scopeConfig") || force) { + settings.m_scopeGUI->formatTo(swgDOA2Settings->getScopeConfig()); + } + } + + if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force)) + { + SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker(); + settings.m_channelMarker->formatTo(swgChannelMarker); + swgDOA2Settings->setChannelMarker(swgChannelMarker); + } + + if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force)) + { + SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState(); + settings.m_rollupState->formatTo(swgRollupState); + swgDOA2Settings->setRollupState(swgRollupState); + } +} + +void DOA2::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "DOA2::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("DOA2::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} + +float DOA2::normalizeAngle(float angle, float max) +{ + if (angle < 0) { return max + angle; } + if (angle > max) { return angle - max; } + return angle; +} diff --git a/plugins/channelmimo/doa2/doa2.h b/plugins/channelmimo/doa2/doa2.h new file mode 100644 index 000000000..d3ce71340 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2.h @@ -0,0 +1,203 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2_H +#define INCLUDE_DOA2_H + +#include +#include + +#include "dsp/mimochannel.h" +#include "dsp/scopevis.h" +#include "channel/channelapi.h" +#include "util/messagequeue.h" +#include "util/message.h" + +#include "doa2settings.h" + +class QThread; +class DeviceAPI; +class DOA2Baseband; +class QNetworkReply; +class QNetworkAccessManager; +class ObjectPipe; + +class DOA2: public MIMOChannel, public ChannelAPI +{ +public: + class MsgConfigureDOA2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DOA2Settings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDOA2* create(const DOA2Settings& settings, bool force) + { + return new MsgConfigureDOA2(settings, force); + } + + private: + DOA2Settings m_settings; + bool m_force; + + MsgConfigureDOA2(const DOA2Settings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgBasebandNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgBasebandNotification* create(int sampleRate, qint64 centerFrequency) { + return new MsgBasebandNotification(sampleRate, centerFrequency); + } + + int getSampleRate() const { return m_sampleRate; } + qint64 getCenterFrequency() const { return m_centerFrequency; } + + private: + + MsgBasebandNotification(int sampleRate, qint64 centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + + int m_sampleRate; + qint64 m_centerFrequency; + }; + + DOA2(DeviceAPI *deviceAPI); + virtual ~DOA2(); + virtual void destroy() { delete this; } + virtual void setDeviceAPI(DeviceAPI *deviceAPI); + virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; } + + virtual void startSinks(); //!< thread start() + virtual void stopSinks(); //!< thread exit() and wait() + virtual void startSources() {} + virtual void stopSources() {} + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex); + virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int sourceIndex); + virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); } + virtual QString getMIMOName() { return objectName(); } + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual QString getIdentifier() const { return objectName(); } + virtual void getTitle(QString& title) { title = "DOA 2 sources"; } + virtual qint64 getCenterFrequency() const { return m_frequencyOffset; } + virtual void setCenterFrequency(qint64) {} + uint32_t getDeviceSampleRate() const { return m_deviceSampleRate; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int getNbSinkStreams() const { return 2; } + virtual int getNbSourceStreams() const { return 0; } + + virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const + { + (void) streamIndex; + (void) sinkElseSource; + return m_frequencyOffset; + } + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; } + + ScopeVis *getScopeVis() { return &m_scopeSink; } + void applyChannelSettings(uint32_t log2Decim, uint32_t filterChainHash); + float getPhi() const; + float getPositiveDOA() const; + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + virtual int webapiWorkspaceGet( + SWGSDRangel::SWGWorkspaceInfo& query, + QString& errorMessage); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const DOA2Settings& settings); + + static void webapiUpdateChannelSettings( + DOA2Settings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response); + + static const char* const m_channelIdURI; + static const char* const m_channelId; + static const int m_fftSize; + +private: + DeviceAPI *m_deviceAPI; + QThread *m_thread; + ScopeVis m_scopeSink; + DOA2Baseband* m_basebandSink; + DOA2Settings m_settings; + MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + int64_t m_frequencyOffset; + uint32_t m_deviceSampleRate; + qint64 m_deviceCenterFrequency; + int m_count0, m_count1; + + virtual bool handleMessage(const Message& cmd); //!< Processing of a message. Returns true if message has actually been processed + void applySettings(const DOA2Settings& settings, bool force = false); + static void validateFilterChainHash(DOA2Settings& settings); + void calculateFrequencyOffset(); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiReverseSendSettings(QList& channelSettingsKeys, const DOA2Settings& settings, bool force); + void sendChannelSettings( + const QList& pipes, + QList& channelSettingsKeys, + const DOA2Settings& settings, + bool force + ); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const DOA2Settings& settings, + bool force + ); + static float normalizeAngle(float angle, float max); + +private slots: + void handleInputMessages(); + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_DOA2_H diff --git a/plugins/channelmimo/doa2/doa2baseband.cpp b/plugins/channelmimo/doa2/doa2baseband.cpp new file mode 100644 index 000000000..8ae581f25 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2baseband.cpp @@ -0,0 +1,303 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/downchannelizer.h" +#include "dsp/basebandsamplesink.h" +#include "dsp/scopevis.h" +#include "dsp/dspcommands.h" + +#include "doa2baseband.h" +#include "doa2settings.h" + + +MESSAGE_CLASS_DEFINITION(DOA2Baseband::MsgConfigureChannelizer, Message) +MESSAGE_CLASS_DEFINITION(DOA2Baseband::MsgSignalNotification, Message) +MESSAGE_CLASS_DEFINITION(DOA2Baseband::MsgConfigureCorrelation, Message) + +DOA2Baseband::DOA2Baseband(int fftSize) : + m_correlator(fftSize), + m_correlationType(DOA2Settings::CorrelationFFT), + m_fftSize(fftSize), + m_samplesCount(0), + m_magSum(0.0f), + m_wphSum(0.0f), + m_phi(0.0f), + m_magThreshold(0.0f), + m_fftAvg(1), + m_fftAvgCount(0), + m_scopeSink(nullptr), + m_mutex(QMutex::Recursive) +{ + m_sampleMIFifo.init(2, 96000 * 8); + m_vbegin.resize(2); + + for (int i = 0; i < 2; i++) + { + m_sinks[i].setStreamIndex(i); + m_channelizers[i] = new DownChannelizer(&m_sinks[i]); + m_sizes[i] = 0; + } + + QObject::connect( + &m_sampleMIFifo, + &SampleMIFifo::dataSyncReady, + this, + &DOA2Baseband::handleData, + Qt::QueuedConnection + ); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_lastStream = 0; +} + +DOA2Baseband::~DOA2Baseband() +{ + for (int i = 0; i < 2; i++) + { + delete m_channelizers[i]; + } +} + +void DOA2Baseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleMIFifo.reset(); + + for (int i = 0; i < 2; i++) { + m_sinks[i].reset(); + } +} + +void DOA2Baseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex) +{ + if (streamIndex > 1) { + return; + } + + if (streamIndex == m_lastStream) { + qWarning("DOA2Baseband::feed: twice same stream in a row: %u", streamIndex); + } + + m_lastStream = streamIndex; + m_vbegin[streamIndex] = begin; + m_sizes[streamIndex] = end - begin; + + if (streamIndex == 1) + { + if (m_sizes[0] != m_sizes[1]) + { + qWarning("DOA2Baseband::feed: unequal sizes: [0]: %d [1]: %d", m_sizes[0], m_sizes[1]); + m_sampleMIFifo.writeSync(m_vbegin, std::min(m_sizes[0], m_sizes[1])); + } + else + { + m_sampleMIFifo.writeSync(m_vbegin, m_sizes[0]); + } + } +} + +void DOA2Baseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + const std::vector& data = m_sampleMIFifo.getData(); + + unsigned int ipart1begin; + unsigned int ipart1end; + unsigned int ipart2begin; + unsigned int ipart2end; + + while ((m_sampleMIFifo.fillSync() > 0) && (m_inputMessageQueue.size() == 0)) + { + m_sampleMIFifo.readSync(ipart1begin, ipart1end, ipart2begin, ipart2end); + + if (ipart1begin != ipart1end) { // first part of FIFO data + processFifo(data, ipart1begin, ipart1end); + } + + if (ipart2begin != ipart2end) { // second part of FIFO data (used when block wraps around) + processFifo(data, ipart2begin, ipart2end); + } + } +} + +void DOA2Baseband::processFifo(const std::vector& data, unsigned int ibegin, unsigned int iend) +{ + for (unsigned int stream = 0; stream < 2; stream++) { + m_channelizers[stream]->feed(data[stream].begin() + ibegin, data[stream].begin() + iend); + } + + run(); +} + +void DOA2Baseband::run() +{ + if (m_correlator.performCorr(m_sinks[0].getData(), m_sinks[0].getSize(), m_sinks[1].getData(), m_sinks[1].getSize())) + { + if (m_correlationType == DOA2Settings::CorrelationType::CorrelationFFT) { + processDOA(m_correlator.m_xcorr.begin(), m_correlator.m_processed); + } + + if (m_scopeSink) + { + std::vector vbegin; + vbegin.push_back(m_correlator.m_tcorr.begin()); + m_scopeSink->feed(vbegin, m_correlator.m_processed); + } + } + + for (int i = 0; i < 2; i++) + { + std::copy( + m_sinks[i].getData().begin() + m_correlator.m_processed, + m_sinks[i].getData().begin() + m_correlator.m_processed + m_correlator.m_remaining[i], + m_sinks[i].getData().begin() + ); + + m_sinks[i].setDataStart(m_correlator.m_remaining[i]); + } +} + +void DOA2Baseband::handleInputMessages() +{ + qDebug("DOA2Baseband::handleInputMessage"); + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool DOA2Baseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureChannelizer::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + int log2Decim = cfg.getLog2Decim(); + int filterChainHash = cfg.getFilterChainHash(); + + qDebug() << "DOA2Baseband::handleMessage: MsgConfigureChannelizer:" + << " log2Decim: " << log2Decim + << " filterChainHash: " << filterChainHash; + + for (int i = 0; i < 2; i++) + { + m_channelizers[i]->setDecimation(log2Decim, filterChainHash); + m_sinks[i].reset(); + } + + return true; + } + else if (MsgSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgSignalNotification& cfg = (MsgSignalNotification&) cmd; + int inputSampleRate = cfg.getInputSampleRate(); + qint64 centerFrequency = cfg.getCenterFrequency(); + int streamIndex = cfg.getStreamIndex(); + + qDebug() << "DOA2Baseband::handleMessage: MsgSignalNotification:" + << " inputSampleRate: " << inputSampleRate + << " centerFrequency: " << centerFrequency + << " streamIndex: " << streamIndex; + + if (streamIndex < 2) + { + m_channelizers[streamIndex]->setBasebandSampleRate(inputSampleRate); + m_sinks[streamIndex].reset(); + } + + return true; + } + else if (MsgConfigureCorrelation::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureCorrelation& cfg = (MsgConfigureCorrelation&) cmd; + m_correlationType = cfg.getCorrelationType(); + + qDebug() << "DOA2Baseband::handleMessage: MsgConfigureCorrelation:" + << " correlationType: " << m_correlationType; + + m_correlator.setCorrType(m_correlationType); + + return true; + } + else + { + qDebug("DOA2Baseband::handleMessage: unhandled: %s", cmd.getIdentifier()); + return false; + } +} + +void DOA2Baseband ::setBasebandSampleRate(unsigned int sampleRate) +{ + for (int istream = 0; istream < 2; istream++) + { + m_channelizers[istream]->setBasebandSampleRate(sampleRate); + m_sinks[istream].reset(); + } +} + +void DOA2Baseband::setFFTAveraging(int nbFFT) +{ + qDebug("DOA2Baseband::setFFTAveraging: %d", nbFFT); + m_fftAvg = nbFFT < 1 ? 1 : nbFFT; + m_fftAvgCount = 0; + m_magSum = 0; + m_wphSum = 0; + m_samplesCount = 0; +} + +void DOA2Baseband::processDOA(const std::vector::iterator& begin, int nbSamples) +{ + const std::vector::iterator end = begin + nbSamples; + + for (std::vector::iterator it = begin; it != end; ++it) + { + float ph = std::arg(*it); + double mag = std::norm(*it); + + if (mag > m_magThreshold) + { + m_magSum += mag; + m_wphSum += mag*ph; + } + + if (++m_samplesCount == m_fftSize) + { + if (m_wphSum != 0) + { + if (++m_fftAvgCount == m_fftAvg) + { + m_phi = m_wphSum / m_magSum; + m_fftAvgCount = 0; + } + } + + m_magSum = 0; + m_wphSum = 0; + m_samplesCount = 0; + } + } +} diff --git a/plugins/channelmimo/doa2/doa2baseband.h b/plugins/channelmimo/doa2/doa2baseband.h new file mode 100644 index 000000000..47c0f7dac --- /dev/null +++ b/plugins/channelmimo/doa2/doa2baseband.h @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2BASEBAND_H +#define INCLUDE_DOA2BASEBAND_H + +#include +#include + +#include "dsp/samplemififo.h" +#include "util/messagequeue.h" +#include "doa2streamsink.h" +#include "doa2corr.h" + +class DownChannelizer; +class BasebandSampleSink; +class ScopeVis; + +class DOA2Baseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getLog2Decim() const { return m_log2Decim; } + int getFilterChainHash() const { return m_filterChainHash; } + + static MsgConfigureChannelizer* create(unsigned int log2Decim, unsigned int filterChainHash) { + return new MsgConfigureChannelizer(log2Decim, filterChainHash); + } + + private: + unsigned int m_log2Decim; + unsigned int m_filterChainHash; + + MsgConfigureChannelizer(unsigned int log2Decim, unsigned int filterChainHash) : + Message(), + m_log2Decim(log2Decim), + m_filterChainHash(filterChainHash) + { } + }; + + class MsgConfigureCorrelation : public Message { + MESSAGE_CLASS_DECLARATION + + public: + DOA2Settings::CorrelationType getCorrelationType() const { return m_correlationType; } + + static MsgConfigureCorrelation *create(DOA2Settings::CorrelationType correlationType) { + return new MsgConfigureCorrelation(correlationType); + } + + private: + DOA2Settings::CorrelationType m_correlationType; + + MsgConfigureCorrelation(DOA2Settings::CorrelationType correlationType) : + Message(), + m_correlationType(correlationType) + {} + }; + + class MsgSignalNotification : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getInputSampleRate() const { return m_inputSampleRate; } + qint64 getCenterFrequency() const { return m_centerFrequency; } + int getStreamIndex() const { return m_streamIndex; } + + static MsgSignalNotification* create(int inputSampleRate, qint64 centerFrequency, int streamIndex) { + return new MsgSignalNotification(inputSampleRate, centerFrequency, streamIndex); + } + private: + int m_inputSampleRate; + qint64 m_centerFrequency; + int m_streamIndex; + + MsgSignalNotification(int inputSampleRate, qint64 centerFrequency, int streamIndex) : + Message(), + m_inputSampleRate(inputSampleRate), + m_centerFrequency(centerFrequency), + m_streamIndex(streamIndex) + { } + }; + + DOA2Baseband(int fftSize); + ~DOA2Baseband(); + void reset(); + + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + + void setScopeSink(ScopeVis *scopeSink) { m_scopeSink = scopeSink; } + void setPhase(int phase) { m_correlator.setPhase(phase); } + + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex); + void setBasebandSampleRate(unsigned int sampleRate); + float getPhi() const { return m_phi; } + void setMagThreshold(float threshold) { m_magThreshold = threshold * SDR_RX_SCALED * SDR_RX_SCALED; } + void setFFTAveraging(int nbFFT); + +private: + void processFifo(const std::vector& data, unsigned int ibegin, unsigned int iend); + void run(); + bool handleMessage(const Message& cmd); + void processDOA(const std::vector::iterator& begin, int nbSamples); + + DOA2Correlator m_correlator; + DOA2Settings::CorrelationType m_correlationType; + int m_fftSize; + int m_samplesCount; //!< Number of samples processed by DOA + float m_magSum; //!< Squared magnitudes accumulator + float m_wphSum; //!< Phase difference accumulator (averaging weighted by squared magnitude) + float m_phi; //!< Resulting calculated phase difference + double m_magThreshold; //!< Squared magnitude scaled threshold + int m_fftAvg; //!< Average over a certain number of FFTs + int m_fftAvgCount; //!< FFT averaging counter + SampleMIFifo m_sampleMIFifo; + std::vector m_vbegin; + int m_sizes[2]; + DOA2StreamSink m_sinks[2]; + DownChannelizer *m_channelizers[2]; + ScopeVis *m_scopeSink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + QMutex m_mutex; + unsigned int m_lastStream; + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_DOA2BASEBAND_H diff --git a/plugins/channelmimo/doa2/doa2compass.cpp b/plugins/channelmimo/doa2/doa2compass.cpp new file mode 100644 index 000000000..6dc3e7015 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2compass.cpp @@ -0,0 +1,278 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "doa2compass.h" + +DOA2Compass::DOA2Compass(QWidget *parent) + : QWidget(parent) +{ + connect(this, SIGNAL(canvasReplot(void)), this, SLOT(canvasReplot_slot(void))); + + m_sizeMin = 200; + m_sizeMax = 600; + m_offset = 2; + m_size = m_sizeMin - 2*m_offset; + m_drawLegend = false; + + setMinimumSize(m_sizeMin, m_sizeMin); + setMaximumSize(m_sizeMax, m_sizeMax); + resize(m_sizeMin, m_sizeMin); + + setFocusPolicy(Qt::NoFocus); + + m_azPos = 0.0; + m_azNeg = 0.0; + m_azAnt = 0.0; + m_blindAngle = 0.0; + m_blindColor = QColor(48, 48, 48); +} + +DOA2Compass::~DOA2Compass() +{ +} + +void DOA2Compass::canvasReplot_slot(void) +{ + update(); +} + +void DOA2Compass::resizeEvent(QResizeEvent *event) +{ + m_size = qMin(width(),height()) - 2*m_offset; + QWidget::resizeEvent(event); +} + +void DOA2Compass::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + QBrush bgGround(palette().button().color()); + + QPen whitePen(Qt::white); + QPen blackPen(Qt::black); + QPen redPen(Qt::red); + QPen bluePen(Qt::blue); + QPen greenPen(Qt::green); + + whitePen.setWidth(1); + blackPen.setWidth(2); + redPen.setWidth(2); + bluePen.setWidth(2); + greenPen.setWidth(2); + + painter.setRenderHint(QPainter::Antialiasing); + painter.translate(width() / 2, height() / 2); + + // draw background + { + painter.setPen(whitePen); + painter.setBrush(bgGround); + painter.drawEllipse(-m_size/2, -m_size/2, m_size, m_size); + } + + // draw blind angle + if (m_blindAngle != 0) + { + painter.setBrush(m_blindColor); + painter.setPen(Qt::NoPen); + painter.rotate(m_azAnt - 90); + painter.drawPie(-m_size/2, -m_size/2, m_size, m_size, -m_blindAngle*16, m_blindAngle*32); + painter.rotate(180); + painter.drawPie(-m_size/2, -m_size/2, m_size, m_size, -m_blindAngle*16, m_blindAngle*32); + painter.rotate(-m_azAnt - 90); + painter.setBrush(Qt::NoBrush); + painter.setPen(whitePen); + painter.drawEllipse(-m_size/2, -m_size/2, m_size, m_size); + } + + // draw compass lines + { + int nyawLines = 36; + float rotAng = 360.0 / nyawLines; + int yawLineLeng = m_size/25; + double fx1, fy1, fx2, fy2; + int fontSize = 8; + QString s; + + blackPen.setWidth(1); + painter.setPen(whitePen); + painter.setFont(font()); + + for(int i=0; i. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2COMPASS_H +#define INCLUDE_DOA2COMPASS_H + +#include + +class DOA2Compass : public QWidget +{ + Q_OBJECT + +public: + DOA2Compass(QWidget *parent = nullptr); + ~DOA2Compass(); + + /// + /// \brief Set all data (yaw, alt, height) + /// + /// \param azPos - forward (positive angles side relative to antennas direction) azimuth (in degrees) + /// \param azNeg - reverse (negatve angles side relative to antennas direction) azimuth (in degrees) + /// \param azAnt - antennas azimuth from 1 (connected to stream 0) to 2 (connected to stream 1) + /// + void setData(double azPos, double azNeg, double azAnt) { + m_azPos = azPos; + m_azNeg = azNeg; + m_azAnt = azAnt; + + if( m_azPos < 0 ) m_azPos = 360 + m_azPos; + if( m_azPos > 360 ) m_azPos = m_azPos - 360; + + if( m_azNeg < 0 ) m_azNeg = 360 + m_azNeg; + if( m_azNeg > 360 ) m_azNeg = m_azNeg - 360; + + if( azAnt < 0 ) azAnt = 360 + azAnt; + if( azAnt > 360 ) azAnt = azAnt - 360; + + emit canvasReplot(); + } + + /// + /// \brief Set forward azimoth (in degree) + /// \param val - forward azimoth (in degree) + /// + void setAzPos(double val) + { + m_azPos = val; + if( m_azPos < 0 ) m_azPos = 360 + m_azPos; + if( m_azPos > 360 ) m_azPos = m_azPos - 360; + + emit canvasReplot(); + } + + /// + /// \brief Set reverse azimoth (in degree) + /// \param val - reverse azimoth (in degree) + /// + void setAzNeg(double val) + { + m_azNeg = val; + if( m_azNeg < 0 ) m_azNeg = 360 + m_azNeg; + if( m_azNeg > 360 ) m_azNeg = m_azNeg - 360; + + emit canvasReplot(); + } + + /// + /// \brief Set antennas azimoth (in degree) + /// \param val - antennas azimoth (in degree) + /// + void setAzAnt(double val) + { + m_azAnt = val; + if( m_azAnt < 0 ) m_azAnt = 360 + m_azAnt; + if( m_azAnt > 360 ) m_azAnt = m_azAnt - 360; + + emit canvasReplot(); + } + + /// + /// \brief Set half blind angle (in degree) + /// \param val - half blind angle (in degree) + /// + void setBlindAngle(double val) + { + m_blindAngle = val; + if( m_blindAngle < 0 ) m_blindAngle = 360 + m_blindAngle; + if( m_blindAngle > 360 ) m_blindAngle = m_blindAngle - 360; + + emit canvasReplot(); + } + + /// + /// \brief Set blind scector color + /// \param val - blind sector color + /// + void setBlindColor(const QColor& color) + { + m_blindColor = color; + emit canvasReplot(); + } + + /// + /// \brief Draw legend in the center of the compass + /// \param drawLegend - true to draw legend else false + /// + void drawLegend(bool drawLegend) + { + m_drawLegend = drawLegend; + emit canvasReplot(); + } + + /// + /// \brief Get forward azimuth + /// \return forward azimuth (in degree) + /// + double getAzPos() const {return m_azPos; } + + /// + /// \brief Get reverse azimuth + /// \return reverse azimuth (in degree) + /// + double getAzNeg() const {return m_azNeg; } + + /// + /// \brief Get antennas azimuth + /// \return antennas azimuth (in degree) + /// + double getAzAnt() const {return m_azAnt; } + +signals: + void canvasReplot(void); + +protected slots: + void canvasReplot_slot(void); + +protected: + void paintEvent(QPaintEvent *event); + void resizeEvent(QResizeEvent *event); + +protected: + int m_sizeMin, m_sizeMax; ///< widget min/max size (in pixel) + int m_size, m_offset; ///< widget size and offset size + bool m_drawLegend; ///< draw legend in the center + + double m_azPos; ///< forward (+) azimuth (in degree) + double m_azNeg; ///< reverse (-) azimuth (in degree) + double m_azAnt; ///< antennas azimuth from 1 (connected to stream 0) to 2 (connected to stream 1) + double m_blindAngle; //!< half the angle around antenna direction where DOA cannot be processed (when baseline distance exceeds half wavelength) + QColor m_blindColor; +}; + +#endif // INCLUDE_DOA2COMPASS_H diff --git a/plugins/channelmimo/doa2/doa2corr.cpp b/plugins/channelmimo/doa2/doa2corr.cpp new file mode 100644 index 000000000..edc35a002 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2corr.cpp @@ -0,0 +1,389 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/dspengine.h" +#include "dsp/fftfactory.h" +#include "dsp/fftengine.h" + +#include "doa2corr.h" + +std::complex s2cNorm(const Sample& s) +{ + float x = s.real() / SDR_RX_SCALEF; + float y = s.imag() / SDR_RX_SCALEF; + float m = sqrt(x*x + y*y); + return std::complex{x/m, y/m}; +} + +Sample sFirst(const Sample& a, const Sample& b) { + (void) b; + return a; +} + +Sample sSecond(const Sample& a, const Sample& b) { + (void) a; + return b; +} + +Sample sSecondInv(const Sample& a, const Sample& b) { + (void) a; + return Sample{-b.real(), -b.imag()}; +} + +Sample invfft2s(const std::complex& a) { //!< Complex float to Sample for 1 side time correlation + Sample s; + s.setReal(a.real()/2.0f); + s.setImag(a.imag()/2.0f); + return s; +} + +DOA2Correlator::DOA2Correlator(int fftSize) : + m_corrType(DOA2Settings::CorrelationFFT), + m_fftSize(fftSize) +{ + setPhase(0); + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + m_window.create(FFTWindow::Function::Hanning, fftSize); + + for (int i = 0; i < 2; i++) { + m_fftSequences[i] = fftFactory->getEngine(fftSize, false, &m_fft[i]); + } + + m_invFFTSequence = fftFactory->getEngine(fftSize, true, &m_invFFT); + + m_dataj = new std::complex[2*fftSize]; // receives actual FFT result hence twice the data FFT size + m_tcorr.resize(fftSize); + m_xcorr.resize(fftSize); + m_tcorrSize = fftSize; + m_xcorrSize = fftSize; +} + +DOA2Correlator::~DOA2Correlator() +{ + FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory(); + fftFactory->releaseEngine(m_fftSize, true, m_invFFTSequence); + delete[] m_dataj; + + for (int i = 0; i < 2; i++) { + fftFactory->releaseEngine(m_fftSize, false, m_fftSequences[i]); + } +} + +bool DOA2Correlator::performCorr( + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1 +) +{ + bool results = false; + + if (m_phase == 0) + { + switch (m_corrType) + { + case DOA2Settings::Correlation0: + results = performOpCorr(data0, size0, data1, size1, sFirst); + break; + case DOA2Settings::Correlation1: + results = performOpCorr(data0, size0, data1, size1, sSecond); + break; + case DOA2Settings::CorrelationFFT: + results = performFFTProd(data0, size0, data1, size1); + break; + default: + break; + } + } + else if ((m_phase == -180) || (m_phase == 180)) + { + if (m_corrType == DOA2Settings::CorrelationFFT) + { + if (size1 > m_data1p.size()) { + m_data1p.resize(size1); + } + + std::transform( + data1.begin(), + data1.begin() + size1, + m_data1p.begin(), + [](const Sample& s) -> Sample { + return Sample{-s.real(), -s.imag()}; + } + ); + } + + switch (m_corrType) + { + case DOA2Settings::Correlation0: + results = performOpCorr(data0, size0, data1, size1, sFirst); + break; + case DOA2Settings::Correlation1: + results = performOpCorr(data0, size0, data1, size1, sSecondInv); + break; + case DOA2Settings::CorrelationFFT: + results = performFFTProd(data0, size0, m_data1p, size1); + break; + default: + break; + } + } + else + { + if (size1 > m_data1p.size()) { + m_data1p.resize(size1); + } + + std::transform( + data1.begin(), + data1.begin() + size1, + m_data1p.begin(), + [this](const Sample& s) -> Sample { + Sample t; + int64_t sx = s.real(); + int64_t sy = s.imag(); + int64_t x = sx*m_cos + sy*m_sin; + int64_t y = sy*m_cos - sx*m_sin; + t.setReal(x>>(SDR_RX_SAMP_SZ-1)); + t.setImag(y>>(SDR_RX_SAMP_SZ-1)); + return t; + } + ); + + switch (m_corrType) + { + case DOA2Settings::Correlation0: + results = performOpCorr(data0, size0, m_data1p, size1, sFirst); + break; + case DOA2Settings::Correlation1: + results = performOpCorr(data0, size0, m_data1p, size1, sSecond); + break; + case DOA2Settings::CorrelationFFT: + results = performFFTProd(data0, size0, m_data1p, size1); + break; + default: + break; + } + } + + return results; +} + +bool DOA2Correlator::performOpCorr( + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1, + Sample sampleOp(const Sample& a, const Sample& b) +) +{ + unsigned int size = std::min(size0, size1); + adjustTCorrSize(size); + adjustXCorrSize(size); + + std::transform( + data0.begin(), + data0.begin() + size, + data1.begin(), + m_tcorr.begin(), + sampleOp + ); + + m_processed = size; + m_remaining[0] = size0 - size; + m_remaining[1] = size1 - size; + return true; +} + +bool DOA2Correlator::performFFTProd( + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1 +) +{ + unsigned int size = std::min(size0, size1); + int nfft = 0; + SampleVector::const_iterator begin0 = data0.begin(); + SampleVector::const_iterator begin1 = data1.begin(); + adjustTCorrSize(size); + adjustXCorrSize(size); + + while (size >= m_fftSize) + { + // FFT[0] + std::transform( + begin0, + begin0 + m_fftSize, + m_fft[0]->in(), + s2cNorm + ); + m_window.apply(m_fft[0]->in()); + m_fft[0]->transform(); + + // FFT[1] + std::transform( + begin1, + begin1 + m_fftSize, + m_fft[1]->in(), + s2cNorm + ); + m_window.apply(m_fft[1]->in()); + m_fft[1]->transform(); + + // conjugate FFT[1] + std::transform( + m_fft[1]->out(), + m_fft[1]->out() + m_fftSize, + m_dataj, + [](const std::complex& c) -> std::complex { + return std::conj(c); + } + ); + + // product of FFT[1]* with FFT[0] and store in both results + std::transform( + m_fft[0]->out(), + m_fft[0]->out() + m_fftSize, + m_dataj, + m_invFFT->in(), + [this](std::complex& a, const std::complex& b) -> std::complex { + return (a*b); + } + ); + + // copy to complex vector for DOA with re-orderong + std::copy( + m_invFFT->in(), + m_invFFT->in() + m_fftSize/2, + m_xcorr.begin() + nfft*m_fftSize + m_fftSize/2 + ); + std::copy( + m_invFFT->in() + m_fftSize/2, + m_invFFT->in() + m_fftSize, + m_xcorr.begin() + nfft*m_fftSize + ); + + // convert and scale to FFT size for scope time domain display + std::transform( + m_xcorr.begin() + nfft*m_fftSize, + m_xcorr.begin() + nfft*m_fftSize + m_fftSize, + m_tcorr.begin() + nfft*m_fftSize, + [](const std::complex& a) -> Sample { + Sample s; + s.setReal(a.real()/2.0f); + s.setImag(a.imag()/2.0f); + return s; + } + ); + + // copy product to time domain - re-order, convert and scale to FFT size + // std::transform( + // m_invFFT->in(), + // m_invFFT->in() + m_fftSize/2, + // m_tcorr.begin() + nfft*m_fftSize + m_fftSize/2, + // [](const std::complex& a) -> Sample { + // Sample s; + // s.setReal(a.real()/2.0f); + // s.setImag(a.imag()/2.0f); + // return s; + // } + // ); + // std::transform( + // m_invFFT->in() + m_fftSize/2, + // m_invFFT->in() + m_fftSize, + // m_tcorr.begin() + nfft*m_fftSize, + // [](const std::complex& a) -> Sample { + // Sample s; + // s.setReal(a.real()/2.0f); + // s.setImag(a.imag()/2.0f); + // return s; + // } + // ); + + size -= m_fftSize; + begin0 += m_fftSize; + begin1 += m_fftSize; + nfft++; + } + + // update the samples counters + m_processed = nfft*m_fftSize; + m_remaining[0] = size0 - nfft*m_fftSize; + m_remaining[1] = size1 - nfft*m_fftSize; + + return nfft > 0; +} + +void DOA2Correlator::adjustTCorrSize(int size) +{ + int nFFTSize = (size/m_fftSize)*m_fftSize; + + if (nFFTSize > m_tcorrSize) + { + m_tcorr.resize(nFFTSize); + m_tcorrSize = nFFTSize; + } +} + +void DOA2Correlator::adjustXCorrSize(int size) +{ + int nFFTSize = (size/m_fftSize)*m_fftSize; + + if (nFFTSize > m_xcorrSize) + { + m_xcorr.resize(nFFTSize); + m_xcorrSize = nFFTSize; + } +} + +void DOA2Correlator::setPhase(int phase) +{ + m_phase = phase; + + if (phase == 0) + { + m_sin = 0; + m_cos = 1<<(SDR_RX_SAMP_SZ-1); + } + else if (phase == 90) + { + m_sin = 1<<(SDR_RX_SAMP_SZ-1); + m_cos = 0; + } + else if (phase == -90) + { + m_sin = -(1<<(SDR_RX_SAMP_SZ-1)); + m_cos = 0; + } + else if ((phase == -180) || (phase == 180)) + { + m_sin = 0; + m_cos = -(1<<(SDR_RX_SAMP_SZ-1)); + } + else + { + m_phase = phase % 180; + double d_sin = sin(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1)); + double d_cos = cos(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1)); + m_sin = d_sin; + m_cos = d_cos; + } +} diff --git a/plugins/channelmimo/doa2/doa2corr.h b/plugins/channelmimo/doa2/doa2corr.h new file mode 100644 index 000000000..1fb414e68 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2corr.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2CORR_H +#define INCLUDE_DOA2CORR_H + +#include +#include + +#include "dsp/dsptypes.h" +#include "dsp/fftwindow.h" +#include "util/message.h" + +#include "doa2settings.h" + +class FFTEngine; + +class DOA2Correlator : public QObject { + Q_OBJECT +public: + DOA2Correlator(int fftSize); + ~DOA2Correlator(); + + void setCorrType(DOA2Settings::CorrelationType corrType) { m_corrType = corrType; } + DOA2Settings::CorrelationType getCorrType() const { return m_corrType; } + bool performCorr( //!< Returns true if results were produced + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1 + ); + int getFullFFTSize() const { return 2*m_fftSize; } + void setPhase(int phase); + + SampleVector m_tcorr; //!< correlation result (time or spectrum inverse FFT) - Sample vector expected + std::vector m_xcorr; //!< correlation result of inverse FFT of FFT prod with conjugate (performFFTProd) for DOA processing + int m_processed; //!< number of samples processed at the end of correlation + int m_remaining[2]; //!< number of samples remaining per member at the end of correlation + +signals: + void dataReady(int start, int stop); + +private: + bool performOpCorr( //!< Returns true if results were produced + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1, + Sample sampleOp(const Sample& a, const Sample& b) + ); + bool performFFTProd( //!< Returns true if results were produced + const SampleVector& data0, + unsigned int size0, + const SampleVector& data1, + unsigned int size1 + ); + void adjustTCorrSize(int size); + void adjustXCorrSize(int size); + + DOA2Settings::CorrelationType m_corrType; + unsigned int m_fftSize; //!< FFT length + FFTEngine *m_fft[2]; //!< FFT engines + FFTEngine *m_invFFT; //!< Inverse FFT engine + unsigned int m_fftSequences[2]; //!< FFT engines sequences + unsigned int m_invFFTSequence; //!< Inverse FFT engine sequence + FFTWindow m_window; //!< FFT window + std::complex *m_dataj; //!< conjuate of FFT transform + SampleVector m_data1p; //!< data1 with phase correction + int m_tcorrSize; //!< time correlations vector size + int m_xcorrSize; //!< DOA correlations vector size + int m_phase; //!< phase correction + int64_t m_sin; //!< scaled sine of phase correction + int64_t m_cos; //!< scaled cosine of phase correction +}; + +#endif // INCLUDE_DOA2CORR_H diff --git a/plugins/channelmimo/doa2/doa2gui.cpp b/plugins/channelmimo/doa2/doa2gui.cpp new file mode 100644 index 000000000..15fbd3d48 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2gui.cpp @@ -0,0 +1,487 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "device/deviceuiset.h" +#include "gui/basicchannelsettingsdialog.h" +#include "dsp/hbfilterchainconverter.h" +#include "dsp/scopevis.h" +#include "dsp/spectrumvis.h" +#include "maincore.h" + +#include "doa2gui.h" +#include "doa2.h" +#include "ui_doa2gui.h" + +DOA2GUI* DOA2GUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *channelMIMO) +{ + DOA2GUI* gui = new DOA2GUI(pluginAPI, deviceUISet, channelMIMO); + return gui; +} + +void DOA2GUI::destroy() +{ + delete this; +} + +void DOA2GUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DOA2GUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DOA2GUI::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + displaySettings(); + applySettings(true); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +MessageQueue* DOA2GUI::getInputMessageQueue() +{ + return &m_inputMessageQueue; +} + +bool DOA2GUI::handleMessage(const Message& message) +{ + if (DOA2::MsgBasebandNotification::match(message)) + { + DOA2::MsgBasebandNotification& notif = (DOA2::MsgBasebandNotification&) message; + m_sampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + displayRateAndShift(); + updateAbsoluteCenterFrequency(); + setFFTAveragingToolitp(); + return true; + } + else if (DOA2::MsgConfigureDOA2::match(message)) + { + const DOA2::MsgConfigureDOA2& notif = (const DOA2::MsgConfigureDOA2&) message; + m_settings = notif.getSettings(); + ui->scopeGUI->updateSettings(); + m_channelMarker.updateSettings(static_cast(m_settings.m_channelMarker)); + displaySettings(); + return true; + } + else + { + return false; + } +} + +DOA2GUI::DOA2GUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *channelMIMO, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::DOA2GUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_sampleRate(48000), + m_centerFrequency(435000000), + m_tickCount(0) +{ + setAttribute(Qt::WA_DeleteOnClose, true); + m_helpURL = "plugins/channelmimo/doa2/readme.md"; + RollupContents *rollupContents = getRollupContents(); + ui->setupUi(rollupContents); + setSizePolicy(rollupContents->sizePolicy()); + rollupContents->arrangeRollups(); + connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_doa2 = (DOA2*) channelMIMO; + m_scopeVis = m_doa2->getScopeVis(); + m_scopeVis->setGLScope(ui->glScope); + m_doa2->setMessageQueueToGUI(getInputMessageQueue()); + m_sampleRate = m_doa2->getDeviceSampleRate(); + + ui->glScope->setTraceModulo(DOA2::m_fftSize); + + ui->glScope->connectTimer(MainCore::instance()->getMasterTimer()); + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + m_channelMarker.blockSignals(true); + m_channelMarker.addStreamIndex(1); + m_channelMarker.setColor(m_settings.m_rgbColor); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle("DOA 2 source"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setRollupState(&m_rollupState); + m_settings.setScopeGUI(ui->scopeGUI); + + m_deviceUISet->addChannelMarker(&m_channelMarker); + + ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); + + m_scopeVis->setTraceChunkSize(DOA2::m_fftSize); // Set scope trace length unit to FFT size + ui->scopeGUI->traceLengthChange(); + + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + + displaySettings(); + makeUIConnections(); + displayRateAndShift(); + applySettings(true); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + ui->halfWLLabel->setText(QString("%1/2").arg(QChar(0xBB, 0x03))); + ui->azUnits->setText(QString("%1").arg(QChar(0260))); +} + +DOA2GUI::~DOA2GUI() +{ + delete ui; +} + +void DOA2GUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DOA2GUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + DOA2::MsgConfigureDOA2* message = DOA2::MsgConfigureDOA2::create(m_settings, force); + m_doa2->getInputMessageQueue()->push(message); + } +} + +void DOA2GUI::displaySettings() +{ + ui->correlationType->setCurrentIndex((int) m_settings.m_correlationType); + + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(0); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setBandwidth(m_sampleRate); + m_channelMarker.setMovable(false); // do not let user move the center arbitrarily + 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()); + setTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + ui->decimationFactor->setCurrentIndex(m_settings.m_log2Decim); + applyDecimation(); + ui->phaseCorrection->setValue(m_settings.m_phase); + ui->phaseCorrectionText->setText(tr("%1").arg(m_settings.m_phase)); + ui->compass->setAzAnt(m_settings.m_antennaAz); + ui->antAz->setValue(m_settings.m_antennaAz); + ui->baselineDistance->setValue(m_settings.m_basebandDistance); + ui->squelch->setValue(m_settings.m_squelchdB); + ui->squelchText->setText(tr("%1").arg(m_settings.m_squelchdB, 3)); + ui->fftAveraging->setCurrentIndex(m_settings.m_fftAveragingIndex); + setFFTAveragingToolitp(); + getRollupContents()->restoreState(m_rollupState); + updateAbsoluteCenterFrequency(); + blockApplySettings(false); +} + +void DOA2GUI::displayRateAndShift() +{ + int shift = m_shiftFrequencyFactor * m_sampleRate; + double channelSampleRate = ((double) m_sampleRate) / (1<offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift))); + ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5))); + m_channelMarker.setCenterFrequency(shift); + m_channelMarker.setBandwidth(channelSampleRate); + m_scopeVis->setLiveRate(channelSampleRate); +} + +void DOA2GUI::setFFTAveragingToolitp() +{ + float channelSampleRate = ((float) m_sampleRate) / (1<fftAveraging->setToolTip(QString("Number of averaging FFTs (avg time: %1s)").arg(s)); +} + +void DOA2GUI::setNumberStr(float v, int decimalPlaces, QString& s) +{ + if (v < 1e-6) { + s = tr("%1n").arg(v*1e9, 0, 'f', decimalPlaces); + } else if (v < 1e-3) { + s = tr("%1µ").arg(v*1e6, 0, 'f', decimalPlaces); + } else if (v < 1.0) { + s = tr("%1m").arg(v*1e3, 0, 'f', decimalPlaces); + } else if (v < 1e3) { + s = tr("%1").arg(v, 0, 'f', decimalPlaces); + } else if (v < 1e6) { + s = tr("%1k").arg(v*1e-3, 0, 'f', decimalPlaces); + } else if (v < 1e9) { + s = tr("%1M").arg(v*1e-6, 0, 'f', decimalPlaces); + } else { + s = tr("%1G").arg(v*1e-9, 0, 'f', decimalPlaces); + } +} + +void DOA2GUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DOA2GUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DOA2GUI::handleSourceMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DOA2GUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; + + RollupContents *rollupContents = getRollupContents(); + + if (rollupContents->hasExpandableWidgets()) { + setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Expanding); + } else { + setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); + } + + int h = rollupContents->height() + getAdditionalHeight(); + resize(width(), h); + + rollupContents->saveState(m_rollupState); + applySettings(); +} + +void DOA2GUI::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.setDefaultTitle(m_displayedName); + + 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); + setTitle(m_channelMarker.getTitle()); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + + resetContextMenuType(); +} + +void DOA2GUI::on_decimationFactor_currentIndexChanged(int index) +{ + m_settings.m_log2Decim = index; + updateScopeFScale(); + applyDecimation(); +} + +void DOA2GUI::on_position_valueChanged(int value) +{ + m_settings.m_filterChainHash = value; + applyPosition(); +} + +void DOA2GUI::on_phaseCorrection_valueChanged(int value) +{ + m_settings.m_phase = value; + ui->phaseCorrectionText->setText(tr("%1").arg(value)); + applySettings(); +} + +void DOA2GUI::on_correlationType_currentIndexChanged(int index) +{ + m_settings.m_correlationType = (DOA2Settings::CorrelationType) index; + updateScopeFScale(); + applySettings(); +} + +void DOA2GUI::on_antAz_valueChanged(int value) +{ + m_settings.m_antennaAz = value; + ui->compass->setAzAnt(value); + updateDOA(); + applySettings(); +} + +void DOA2GUI::on_baselineDistance_valueChanged(int value) +{ + m_settings.m_basebandDistance = value < 1 ? 1 : value; + updateDOA(); + applySettings(); +} + +void DOA2GUI::on_squelch_valueChanged(int value) +{ + m_settings.m_squelchdB = value; + ui->squelchText->setText(tr("%1").arg(m_settings.m_squelchdB, 3)); + applySettings(); +} + +void DOA2GUI::on_fftAveraging_currentIndexChanged(int index) +{ + qDebug("DOA2GUI::on_averaging_currentIndexChanged: %d", index); + m_settings.m_fftAveragingIndex = index; + applySettings(); + setFFTAveragingToolitp(); +} + +void DOA2GUI::on_centerPosition_clicked() +{ + uint32_t filterChainHash = 1; + uint32_t mul = 1; + + for (uint32_t i = 1; i < m_settings.m_log2Decim; i++) + { + mul *= 3; + filterChainHash += mul; + } + + m_settings.m_filterChainHash = filterChainHash; + ui->position->setValue(m_settings.m_filterChainHash); + applyPosition(); +} + +void DOA2GUI::applyDecimation() +{ + uint32_t maxHash = 1; + + for (uint32_t i = 0; i < m_settings.m_log2Decim; i++) { + maxHash *= 3; + } + + ui->position->setMaximum(maxHash-1); + ui->position->setValue(m_settings.m_filterChainHash); + m_settings.m_filterChainHash = ui->position->value(); + applyPosition(); +} + +void DOA2GUI::applyPosition() +{ + ui->filterChainIndex->setText(tr("%1").arg(m_settings.m_filterChainHash)); + QString s; + m_shiftFrequencyFactor = HBFilterChainConverter::convertToString(m_settings.m_log2Decim, m_settings.m_filterChainHash, s); + ui->filterChainText->setText(s); + + displayRateAndShift(); + updateAbsoluteCenterFrequency(); + applySettings(); +} + +void DOA2GUI::tick() +{ + if (++m_tickCount == 20) // once per second + { + updateDOA(); + m_tickCount = 0; + } +} + +void DOA2GUI::makeUIConnections() +{ + QObject::connect(ui->decimationFactor, QOverload::of(&QComboBox::currentIndexChanged), this, &DOA2GUI::on_decimationFactor_currentIndexChanged); + QObject::connect(ui->position, &QSlider::valueChanged, this, &DOA2GUI::on_position_valueChanged); + QObject::connect(ui->phaseCorrection, &QSlider::valueChanged, this, &DOA2GUI::on_phaseCorrection_valueChanged); + QObject::connect(ui->correlationType, QOverload::of(&QComboBox::currentIndexChanged), this, &DOA2GUI::on_correlationType_currentIndexChanged); + QObject::connect(ui->antAz, QOverload::of(&QSpinBox::valueChanged), this, &DOA2GUI::on_antAz_valueChanged); + QObject::connect(ui->baselineDistance, QOverload::of(&QSpinBox::valueChanged), this, &DOA2GUI::on_baselineDistance_valueChanged); + QObject::connect(ui->squelch, &QDial::valueChanged, this, &DOA2GUI::on_squelch_valueChanged); + QObject::connect(ui->fftAveraging, QOverload::of(&QComboBox::currentIndexChanged), this, &DOA2GUI::on_fftAveraging_currentIndexChanged); + QObject::connect(ui->centerPosition, &QPushButton::clicked, this, &DOA2GUI::on_centerPosition_clicked); +} + +void DOA2GUI::updateAbsoluteCenterFrequency() +{ + qint64 cf = m_centerFrequency + m_shiftFrequencyFactor * m_sampleRate; + setStatusFrequency(cf); + m_hwl = 1.5e+8 / cf; + ui->halfWLText->setText(tr("%1").arg(m_hwl*1000, 5, 'f', 0)); + updateScopeFScale(); +} + +void DOA2GUI::updateScopeFScale() +{ + if (m_settings.m_correlationType == DOA2Settings::CorrelationType::CorrelationFFT) { + ui->glScope->setXScaleFreq(true); + } else { + ui->glScope->setXScaleFreq(false); + } + + ui->glScope->setXScaleCenterFrequency(m_centerFrequency); + ui->glScope->setXScaleFrequencySpan(m_sampleRate / (1<getPhi() * m_hwl * 1000.0) / (M_PI * m_settings.m_basebandDistance); + float blindAngle = (m_settings.m_basebandDistance > m_hwl * 1000.0) ? std::acos((m_hwl * 1000.0) / m_settings.m_basebandDistance) * (180/M_PI) : 0; + ui->compass->setBlindAngle(blindAngle); + float doaAngle = std::acos(cosTheta < -1.0 ? -1.0 : cosTheta > 1.0 ? 1.0 : cosTheta) * (180/M_PI); + float posAngle = ui->antAz->value() - doaAngle; // DOA angles are trigonometric but displayed angles are clockwise + float negAngle = ui->antAz->value() + doaAngle; + ui->compass->setAzPos(posAngle); + ui->compass->setAzNeg(negAngle); + ui->posText->setText(tr("%1").arg(ui->compass->getAzPos(), 3, 'f', 0, QLatin1Char('0'))); + ui->negText->setText(tr("%1").arg(ui->compass->getAzNeg(), 3, 'f', 0, QLatin1Char('0'))); +} diff --git a/plugins/channelmimo/doa2/doa2gui.h b/plugins/channelmimo/doa2/doa2gui.h new file mode 100644 index 000000000..db099aaa4 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2gui.h @@ -0,0 +1,115 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2GUI_H +#define INCLUDE_DOA2GUI_H + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" +#include "settings/rollupstate.h" + +#include "doa2settings.h" + +class PluginAPI; +class DeviceUISet; +class MIMOChannel; +class DOA2; +class ScopeVis; + +namespace Ui { + class DOA2GUI; +} + +class DOA2GUI : public ChannelGUI { + Q_OBJECT +public: + static DOA2GUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *mimoChannel); + + virtual void destroy(); + virtual void resetToDefaults(); + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual MessageQueue* getInputMessageQueue(); + virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; }; + virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; }; + virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; }; + virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; }; + virtual QString getTitle() const { return m_settings.m_title; }; + virtual QColor getTitleColor() const { return m_settings.m_rgbColor; }; + virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; } + virtual bool getHidden() const { return m_settings.m_hidden; } + virtual ChannelMarker& getChannelMarker() { return m_channelMarker; } + virtual int getStreamIndex() const { return -1; } + virtual void setStreamIndex(int streamIndex) { (void) streamIndex; } + +private: + Ui::DOA2GUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + RollupState m_rollupState; + DOA2Settings m_settings; + int m_sampleRate; + qint64 m_centerFrequency; + double m_shiftFrequencyFactor; //!< Channel frequency shift factor + bool m_doApplySettings; + MovingAverageUtil m_channelPowerAvg; + DOA2 *m_doa2; + ScopeVis* m_scopeVis; + MessageQueue m_inputMessageQueue; + uint32_t m_tickCount; + double m_hwl; //!< Half wavelength at center frequency + + explicit DOA2GUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *rxChannel, QWidget* parent = nullptr); + virtual ~DOA2GUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void applyDecimation(); + void applyPosition(); + void displaySettings(); + void displayRateAndShift(); + void setFFTAveragingToolitp(); + static void setNumberStr(float v, int decimalPlaces, QString& s); + bool handleMessage(const Message& message); + void makeUIConnections(); + void updateAbsoluteCenterFrequency(); + void updateScopeFScale(); + void updateDOA(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void handleSourceMessages(); + void on_decimationFactor_currentIndexChanged(int index); + void on_position_valueChanged(int value); + void on_phaseCorrection_valueChanged(int value); + void on_correlationType_currentIndexChanged(int index); + void on_antAz_valueChanged(int value); + void on_baselineDistance_valueChanged(int value); + void on_squelch_valueChanged(int value); + void on_fftAveraging_currentIndexChanged(int index); + void on_centerPosition_clicked(); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void tick(); +}; + +#endif // INCLUDE_DOA2GUI_H diff --git a/plugins/channelmimo/doa2/doa2gui.ui b/plugins/channelmimo/doa2/doa2gui.ui new file mode 100644 index 000000000..91adcd24a --- /dev/null +++ b/plugins/channelmimo/doa2/doa2gui.ui @@ -0,0 +1,960 @@ + + + DOA2GUI + + + + 0 + 0 + 720 + 642 + + + + + 0 + 0 + + + + + 720 + 0 + + + + + Liberation Sans + 9 + + + + DOA 2 Sources + + + + + 0 + 10 + 718 + 81 + + + + + 718 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 3 + + + + + + + Dec + + + + + + + + 55 + 16777215 + + + + Decimation factor + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + + + + + 50 + 0 + + + + Effective channel rate (kS/s) + + + 0000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + 0 + + + + Filter chain stages left to right (L: low, C: center, H: high) + + + LLLLLL + + + + + + + Qt::Vertical + + + + + + + Corr + + + + + + + Scope correlation type + + + + A + + + + + B + + + + + FFT + + + + + + + + Ph + + + + + + + Phase correction on stream 1 + + + -180 + + + 180 + + + 1 + + + Qt::Horizontal + + + + + + + + 28 + 0 + + + + Phase correction on stream 1 in degrees + + + -180 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 85 + 0 + + + + Offset frequency with thousands separator (Hz) + + + -9,999,999 Hz + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 10 + + + + + Pos + + + + + + + Center frequency position + + + 2 + + + 1 + + + Qt::Horizontal + + + + + + + + 24 + 0 + + + + Filter chain hash code + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Center in passband + + + C + + + + + + + + + + + + + 0 + 98 + 718 + 334 + + + + + 0 + 0 + + + + + 718 + 0 + + + + Scope + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 200 + 300 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + 0 + 432 + 718 + 202 + + + + + 0 + 0 + + + + + 718 + 0 + + + + DOA + + + + 2 + + + 2 + + + 2 + + + + + + + + + + 0 + 0 + + + + + 359 + 200 + + + + + Liberation Sans + 9 + + + + + + + + + + 6 + + + + + + + Pos + + + + + + + Port side (positive) arrival angle (degrees) + + + 000 + + + + + + + + 10 + 0 + + + + + + + + + + + Neg + + + + + + + Starboard side (negative) arrival angle (degrees) + + + 000 + + + + + + + + 10 + 0 + + + + + + + + + + + Ant + + + + + + + + 60 + 0 + + + + Antennas line azimuth (degrees) + + + true + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 359 + + + + + + + d + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + L/2 + + + + + + + + 40 + 0 + + + + Half wavelength (mm) + + + 00000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 10 + 0 + + + + + + + + + + + D + + + + + + + Baseline distance (mm) + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 99999 + + + 500 + + + + + + + mm + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Sq + + + + + + + + 24 + 24 + + + + Squelch threshold (dB) + + + -140 + + + 0 + + + 1 + + + 1 + + + -50 + + + + + + + + 28 + 0 + + + + Squelch threshold (dB) + + + -100 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + dB + + + + + + + + 10 + 0 + + + + + + + + + + + FFT avg + + + + + + + + 55 + 0 + + + + + 45 + 16777215 + + + + Number of averaging FFTs + + + + 1 + + + + + 2 + + + + + 5 + + + + + 10 + + + + + 20 + + + + + 50 + + + + + 100 + + + + + 200 + + + + + 500 + + + + + 1k + + + + + 2k + + + + + 5k + + + + + 10k + + + + + 20k + + + + + 50k + + + + + 1e5 + + + + + 2e5 + + + + + 5e5 + + + + + 1M + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + RollupContents + QWidget +
gui/rollupcontents.h
+ 1 +
+ + GLScope + QWidget +
gui/glscope.h
+ 1 +
+ + GLScopeGUI + QWidget +
gui/glscopegui.h
+ 1 +
+ + DOA2Compass + QWidget +
doa2compass.h
+ 1 +
+
+ + + + +
diff --git a/plugins/channelmimo/doa2/doa2plugin.cpp b/plugins/channelmimo/doa2/doa2plugin.cpp new file mode 100644 index 000000000..5461a0cd4 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2plugin.cpp @@ -0,0 +1,94 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "doa2plugin.h" + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "doa2gui.h" +#endif +#include "doa2.h" +#include "doa2webapiadapter.h" +#include "doa2plugin.h" + +const PluginDescriptor DOA2Plugin::m_pluginDescriptor = { + DOA2::m_channelId, + QStringLiteral("DOA 2 sources"), + QStringLiteral("7.3.0"), + QStringLiteral("(c) Edouard Griffiths, F4EXB"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +DOA2Plugin::DOA2Plugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DOA2Plugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DOA2Plugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register channel MIMO + m_pluginAPI->registerMIMOChannel(DOA2::m_channelIdURI, DOA2::m_channelId, this); +} + +void DOA2Plugin::createMIMOChannel(DeviceAPI *deviceAPI, MIMOChannel **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + DOA2 *instance = new DOA2(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* DOA2Plugin::createMIMOChannelGUI( + DeviceUISet *deviceUISet, + MIMOChannel *mimoChannel) const +{ + (void) deviceUISet; + (void) mimoChannel; + return nullptr; +} +#else +ChannelGUI* DOA2Plugin::createMIMOChannelGUI(DeviceUISet *deviceUISet, MIMOChannel *mimoChannel) const +{ + return DOA2GUI::create(m_pluginAPI, deviceUISet, mimoChannel); +} +#endif + +ChannelWebAPIAdapter* DOA2Plugin::createChannelWebAPIAdapter() const +{ + return new DOA2WebAPIAdapter(); +} diff --git a/plugins/channelmimo/doa2/doa2plugin.h b/plugins/channelmimo/doa2/doa2plugin.h new file mode 100644 index 000000000..384e6d22c --- /dev/null +++ b/plugins/channelmimo/doa2/doa2plugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELMIMO_DOA2_DOA2PLUGIN_H_ +#define PLUGINS_CHANNELMIMO_DOA2_DOA2PLUGIN_H_ + + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class MIMOChannel; + +class DOA2Plugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channelmimo.doa2") + +public: + explicit DOA2Plugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void createMIMOChannel(DeviceAPI *deviceAPI, MIMOChannel **bs, ChannelAPI **cs) const; + virtual ChannelGUI* createMIMOChannelGUI(DeviceUISet *deviceUISet, MIMOChannel *mimoChannel) const; + virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif /* PLUGINS_CHANNELMIMO_DOA2_DOA2PLUGIN_H_ */ diff --git a/plugins/channelmimo/doa2/doa2settings.cpp b/plugins/channelmimo/doa2/doa2settings.cpp new file mode 100644 index 000000000..0d9ffc637 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2settings.cpp @@ -0,0 +1,222 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "doa2settings.h" + +DOA2Settings::DOA2Settings() : + m_channelMarker(nullptr), + m_scopeGUI(nullptr), + m_rollupState(nullptr) +{ + resetToDefaults(); +} + +void DOA2Settings::resetToDefaults() +{ + m_correlationType = CorrelationFFT; + m_rgbColor = QColor(250, 120, 120).rgb(); + m_title = "DOA 2 sources"; + m_log2Decim = 0; + m_filterChainHash = 0; + m_phase = 0; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; + m_workspaceIndex = 0; + m_hidden = false; + m_antennaAz = 0; + m_basebandDistance = 500; + m_squelchdB = -50; + m_fftAveragingIndex = 0; +} + +QByteArray DOA2Settings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(2, (int) m_correlationType); + s.writeU32(3, m_rgbColor); + s.writeString(4, m_title); + s.writeU32(5, m_log2Decim); + s.writeU32(6, m_filterChainHash); + s.writeBool(7, m_useReverseAPI); + s.writeString(8, m_reverseAPIAddress); + s.writeU32(9, m_reverseAPIPort); + s.writeU32(10, m_reverseAPIDeviceIndex); + s.writeU32(11, m_reverseAPIChannelIndex); + s.writeS32(12, m_phase); + s.writeS32(13,m_workspaceIndex); + s.writeBlob(14, m_geometryBytes); + s.writeBool(15, m_hidden); + s.writeS32(16, m_antennaAz); + s.writeU32(17, m_basebandDistance); + s.writeS32(18, m_squelchdB); + s.writeS32(19, m_fftAveragingIndex); + + if (m_scopeGUI) { + s.writeBlob(21, m_scopeGUI->serialize()); + } + if (m_channelMarker) { + s.writeBlob(22, m_channelMarker->serialize()); + } + if (m_rollupState) { + s.writeBlob(23, m_rollupState->serialize()); + } + + return s.final(); +} + +bool DOA2Settings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + int tmp; + quint32 utmp; + + d.readS32(2, &tmp, 0); + m_correlationType = (CorrelationType) tmp; + d.readU32(3, &m_rgbColor); + d.readString(4, &m_title, "DOA 2 sources"); + d.readU32(5, &utmp, 0); + m_log2Decim = utmp > 6 ? 6 : utmp; + d.readU32(6, &m_filterChainHash, 0); + d.readBool(7, &m_useReverseAPI, false); + d.readString(8, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(9, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(10, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(11, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + d.readS32(12, &tmp, 0); + m_phase = tmp < -180 ? -180 : tmp > 180 ? 180 : tmp; + d.readS32(13, &m_workspaceIndex); + d.readBlob(14, &m_geometryBytes); + d.readBool(15, &m_hidden, false); + d.readS32(16, &tmp, 0); + m_antennaAz = tmp < 0 ? 0 : tmp > 359 ? 359 : tmp; + d.readU32(17, &utmp, 500); + m_basebandDistance = utmp == 0 ? 1 : utmp; + d.readS32(18, &m_squelchdB, -50); + d.readS32(19, &tmp, 0); + m_fftAveragingIndex = tmp < 0 ? + 0 : + tmp > 3*m_averagingMaxExponent + 3 ? + 3*m_averagingMaxExponent + 3: + tmp; + + if (m_scopeGUI) + { + d.readBlob(21, &bytetmp); + m_scopeGUI->deserialize(bytetmp); + } + + if (m_channelMarker) + { + d.readBlob(22, &bytetmp); + m_channelMarker->deserialize(bytetmp); + } + + if (m_rollupState) + { + d.readBlob(23, &bytetmp); + m_rollupState->deserialize(bytetmp); + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +int DOA2Settings::getAveragingValue(int averagingIndex) +{ + if (averagingIndex <= 0) { + return 1; + } + + int v = averagingIndex - 1; + int m = pow(10.0, v/3 > m_averagingMaxExponent ? m_averagingMaxExponent : v/3); + int x = 1; + + if (v % 3 == 0) { + x = 2; + } else if (v % 3 == 1) { + x = 5; + } else if (v % 3 == 2) { + x = 10; + } + + return x * m; +} + +int DOA2Settings::getAveragingIndex(int averagingValue) +{ + if (averagingValue <= 1) { + return 0; + } + + int v = averagingValue; + int j = 0; + + for (int i = 0; i <= m_averagingMaxExponent; i++) + { + if (v < 20) + { + if (v < 2) { + j = 0; + } else if (v < 5) { + j = 1; + } else if (v < 10) { + j = 2; + } else { + j = 3; + } + + return 3*i + j; + } + + v /= 10; + } + + return 3*m_averagingMaxExponent + 3; +} diff --git a/plugins/channelmimo/doa2/doa2settings.h b/plugins/channelmimo/doa2/doa2settings.h new file mode 100644 index 000000000..78dbcc46c --- /dev/null +++ b/plugins/channelmimo/doa2/doa2settings.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2SETTINGS_H +#define INCLUDE_DOA2SETTINGS_H + +#include +#include + +class Serializable; + +struct DOA2Settings +{ + enum CorrelationType + { + Correlation0, + Correlation1, + CorrelationFFT, + }; + + CorrelationType m_correlationType; + quint32 m_rgbColor; + QString m_title; + uint32_t m_log2Decim; + uint32_t m_filterChainHash; + int m_phase; + int m_antennaAz; + uint32_t m_basebandDistance; //!< in millimeters + int m_squelchdB; + int m_fftAveragingIndex; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; + int m_workspaceIndex; + QByteArray m_geometryBytes; + bool m_hidden; + + Serializable *m_channelMarker; + Serializable *m_scopeGUI; + Serializable *m_rollupState; + + DOA2Settings(); + void resetToDefaults(); + void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; } + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + static int getAveragingValue(int averagingIndex); + static int getAveragingIndex(int averagingValue); + static const int m_averagingMaxExponent = 5; //!< Max 1M (10 * 10^5) +}; + +#endif // INCLUDE_DOA2SETTINGS_H diff --git a/plugins/channelmimo/doa2/doa2streamsink.cpp b/plugins/channelmimo/doa2/doa2streamsink.cpp new file mode 100644 index 000000000..0c83eb2a5 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2streamsink.cpp @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "doa2streamsink.h" + +DOA2StreamSink::DOA2StreamSink() : + m_streamIndex(0), + m_dataSize(0), + m_bufferSize(0), + m_dataStart(0) +{} + +DOA2StreamSink::~DOA2StreamSink() +{} + +void DOA2StreamSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_dataSize = (end - begin) + m_dataStart; + + if (m_dataSize > m_bufferSize) + { + m_data.resize(m_dataSize); + m_bufferSize = m_dataSize; + } + + std::copy(begin, end, m_data.begin() + m_dataStart); +} + +void DOA2StreamSink::reset() +{ + m_dataStart = 0; +} diff --git a/plugins/channelmimo/doa2/doa2streamsink.h b/plugins/channelmimo/doa2/doa2streamsink.h new file mode 100644 index 000000000..01829b992 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2streamsink.h @@ -0,0 +1,51 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DOA2STREAMSINK_H_ +#define SDRBASE_DOA2STREAMSINK_H_ + +#include "dsp/channelsamplesink.h" + + +class DOA2StreamSink : public ChannelSampleSink +{ +public: + DOA2StreamSink(); + virtual ~DOA2StreamSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void reset(); + unsigned int getStreamIndex() const { return m_streamIndex; } + void setStreamIndex(unsigned int streamIndex) { m_streamIndex = streamIndex; } + SampleVector& getData() { return m_data; } + int getSize() const { return m_dataSize; } + void setDataStart(int dataStart) { m_dataStart = dataStart; } + +private: + unsigned int m_streamIndex; + SampleVector m_data; + int m_dataSize; + int m_bufferSize; + int m_dataStart; + + uint32_t m_log2Decim; + uint32_t m_filterChainHash; +}; + + +#endif // SDRBASE_DOA2STREAMSINK_H_ diff --git a/plugins/channelmimo/doa2/doa2webapiadapter.cpp b/plugins/channelmimo/doa2/doa2webapiadapter.cpp new file mode 100644 index 000000000..d51487b27 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2webapiadapter.cpp @@ -0,0 +1,315 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "SWGChannelSettings.h" +#include "doa2webapiadapter.h" + +DOA2WebAPIAdapter::DOA2WebAPIAdapter() +{ + m_settings.setScopeGUI(&m_glScopeSettings); +} + +DOA2WebAPIAdapter::~DOA2WebAPIAdapter() +{} + +int DOA2WebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setDoa2Settings(new SWGSDRangel::SWGDOA2Settings()); + response.getDoa2Settings()->init(); + webapiFormatChannelSettings(response, m_settings, m_glScopeSettings); + return 200; +} + +void DOA2WebAPIAdapter::webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const DOA2Settings& settings, + const GLScopeSettings& scopeSettings) +{ + response.getDoa2Settings()->setCorrelationType((int) settings.m_correlationType); + response.getDoa2Settings()->setRgbColor(settings.m_rgbColor); + response.getDoa2Settings()->setTitle(new QString(settings.m_title)); + + // scope + SWGSDRangel::SWGGLScope *swgScope = new SWGSDRangel::SWGGLScope(); + swgScope->init(); + response.getDoa2Settings()->setScopeConfig(swgScope); + swgScope->setDisplayMode(scopeSettings.m_displayMode); + swgScope->setGridIntensity(scopeSettings.m_gridIntensity); + swgScope->setTime(scopeSettings.m_time); + swgScope->setTimeOfs(scopeSettings.m_timeOfs); + swgScope->setTraceIntensity(scopeSettings.m_traceIntensity); + swgScope->setTraceLenMult(scopeSettings.m_traceLenMult); + swgScope->setTrigPre(scopeSettings.m_trigPre); + + // array of traces + swgScope->setTracesData(new QList); + std::vector::const_iterator traceIt = scopeSettings.m_tracesData.begin(); + + for (; traceIt != scopeSettings.m_tracesData.end(); ++traceIt) + { + swgScope->getTracesData()->append(new SWGSDRangel::SWGTraceData); + swgScope->getTracesData()->back()->setAmp(traceIt->m_amp); + swgScope->getTracesData()->back()->setHasTextOverlay(traceIt->m_hasTextOverlay ? 1 : 0); + swgScope->getTracesData()->back()->setStreamIndex(traceIt->m_streamIndex); + swgScope->getTracesData()->back()->setOfs(traceIt->m_ofs); + swgScope->getTracesData()->back()->setProjectionType((int) traceIt->m_projectionType); + swgScope->getTracesData()->back()->setTextOverlay(new QString(traceIt->m_textOverlay)); + swgScope->getTracesData()->back()->setTraceColor(qColorToInt(traceIt->m_traceColor)); + swgScope->getTracesData()->back()->setTraceColorB(traceIt->m_traceColorB); + swgScope->getTracesData()->back()->setTraceColorG(traceIt->m_traceColorG); + swgScope->getTracesData()->back()->setTraceColorR(traceIt->m_traceColorR); + swgScope->getTracesData()->back()->setTraceDelay(traceIt->m_traceDelay); + swgScope->getTracesData()->back()->setTraceDelayCoarse(traceIt->m_traceDelayCoarse); + swgScope->getTracesData()->back()->setTraceDelayFine(traceIt->m_traceDelayFine); + swgScope->getTracesData()->back()->setTriggerDisplayLevel(traceIt->m_triggerDisplayLevel); + swgScope->getTracesData()->back()->setViewTrace(traceIt->m_viewTrace ? 1 : 0); + } + + // array of triggers + swgScope->setTriggersData(new QList); + std::vector::const_iterator triggerIt = scopeSettings.m_triggersData.begin(); + + for (; triggerIt != scopeSettings.m_triggersData.end(); ++triggerIt) + { + swgScope->getTriggersData()->append(new SWGSDRangel::SWGTriggerData); + swgScope->getTriggersData()->back()->setInputIndex(triggerIt->m_inputIndex); + swgScope->getTriggersData()->back()->setProjectionType((int) triggerIt->m_projectionType); + swgScope->getTriggersData()->back()->setTriggerBothEdges(triggerIt->m_triggerBothEdges ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerColor(qColorToInt(triggerIt->m_triggerColor)); + swgScope->getTriggersData()->back()->setTriggerColorB(triggerIt->m_triggerColorB); + swgScope->getTriggersData()->back()->setTriggerColorG(triggerIt->m_triggerColorG); + swgScope->getTriggersData()->back()->setTriggerColorR(triggerIt->m_triggerColorR); + swgScope->getTriggersData()->back()->setTriggerDelay(triggerIt->m_triggerDelay); + swgScope->getTriggersData()->back()->setTriggerDelayCoarse(triggerIt->m_triggerDelayCoarse); + swgScope->getTriggersData()->back()->setTriggerDelayFine(triggerIt->m_triggerDelayFine); + swgScope->getTriggersData()->back()->setTriggerDelayMult(triggerIt->m_triggerDelayMult); + swgScope->getTriggersData()->back()->setTriggerHoldoff(triggerIt->m_triggerHoldoff ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerLevel(triggerIt->m_triggerLevel); + swgScope->getTriggersData()->back()->setTriggerLevelCoarse(triggerIt->m_triggerLevelCoarse); + swgScope->getTriggersData()->back()->setTriggerLevelFine(triggerIt->m_triggerLevelFine); + swgScope->getTriggersData()->back()->setTriggerPositiveEdge(triggerIt->m_triggerPositiveEdge ? 1 : 0); + swgScope->getTriggersData()->back()->setTriggerRepeat(triggerIt->m_triggerRepeat); + } +} + +int DOA2WebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + webapiUpdateChannelSettings(m_settings, m_glScopeSettings, channelSettingsKeys, response); + return 200; +} + +void DOA2WebAPIAdapter::webapiUpdateChannelSettings( + DOA2Settings& settings, + GLScopeSettings& scopeSettings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("correlationType")) { + settings.m_correlationType = (DOA2Settings::CorrelationType) response.getDoa2Settings()->getCorrelationType(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDoa2Settings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDoa2Settings()->getTitle(); + } + // scope + if (channelSettingsKeys.contains("scopeConfig")) + { + if (channelSettingsKeys.contains("scopeConfig.displayMode")) { + scopeSettings.m_displayMode = (GLScopeSettings::DisplayMode) response.getDoa2Settings()->getScopeConfig()->getDisplayMode(); + } + if (channelSettingsKeys.contains("scopeConfig.gridIntensity")) { + scopeSettings.m_gridIntensity = response.getDoa2Settings()->getScopeConfig()->getGridIntensity(); + } + if (channelSettingsKeys.contains("scopeConfig.time")) { + scopeSettings.m_time = response.getDoa2Settings()->getScopeConfig()->getTime(); + } + if (channelSettingsKeys.contains("scopeConfig.timeOfs")) { + scopeSettings.m_timeOfs = response.getDoa2Settings()->getScopeConfig()->getTimeOfs(); + } + if (channelSettingsKeys.contains("scopeConfig.traceIntensity")) { + scopeSettings.m_traceIntensity = response.getDoa2Settings()->getScopeConfig()->getTraceIntensity(); + } + if (channelSettingsKeys.contains("scopeConfig.traceLenMult")) { + scopeSettings.m_traceLenMult = response.getDoa2Settings()->getScopeConfig()->getTraceLenMult(); + } + if (channelSettingsKeys.contains("scopeConfig.trigPre")) { + scopeSettings.m_trigPre = response.getDoa2Settings()->getScopeConfig()->getTrigPre(); + } + // traces + if (channelSettingsKeys.contains("scopeConfig.tracesData")) + { + QList *tracesData = response.getDoa2Settings()->getScopeConfig()->getTracesData(); + scopeSettings.m_tracesData.clear(); + + for (int i = 0; i < 10; i++) // no more than 10 traces anyway + { + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1]").arg(i))) + { + SWGSDRangel::SWGTraceData *traceData = tracesData->at(i); + scopeSettings.m_tracesData.push_back(GLScopeSettings::TraceData()); + + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].streamIndex").arg(i))) { + scopeSettings.m_tracesData.back().m_streamIndex = traceData->getStreamIndex(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].amp").arg(i))) { + scopeSettings.m_tracesData.back().m_amp = traceData->getAmp(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].hasTextOverlay").arg(i))) { + scopeSettings.m_tracesData.back().m_hasTextOverlay = traceData->getHasTextOverlay() != 0; + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].streamIndex").arg(i))) { + scopeSettings.m_tracesData.back().m_streamIndex = traceData->getStreamIndex(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].ofs").arg(i))) { + scopeSettings.m_tracesData.back().m_ofs = traceData->getOfs(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].projectionType").arg(i))) { + scopeSettings.m_tracesData.back().m_projectionType = (Projector::ProjectionType) traceData->getProjectionType(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColor").arg(i))) { + scopeSettings.m_tracesData.back().m_traceColor = intToQColor(traceData->getTraceColor()); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorB").arg(i))) { + scopeSettings.m_tracesData.back().m_traceColorB = traceData->getTraceColorB(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorG").arg(i))) { + scopeSettings.m_tracesData.back().m_traceColorG = traceData->getTraceColorG(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorR").arg(i))) { + scopeSettings.m_tracesData.back().m_traceColorR = traceData->getTraceColorR(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelay").arg(i))) { + scopeSettings.m_tracesData.back().m_traceDelay = traceData->getTraceDelay(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelayCoarse").arg(i))) { + scopeSettings.m_tracesData.back().m_traceDelayCoarse = traceData->getTraceDelayCoarse(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelayFine").arg(i))) { + scopeSettings.m_tracesData.back().m_traceDelayFine = traceData->getTraceDelayFine(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].triggerDisplayLevel").arg(i))) { + scopeSettings.m_tracesData.back().m_triggerDisplayLevel = traceData->getTriggerDisplayLevel(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].viewTrace").arg(i))) { + scopeSettings.m_tracesData.back().m_viewTrace = traceData->getViewTrace() != 0; + } + } + else + { + break; + } + } + } + // triggers + if (channelSettingsKeys.contains("scopeConfig.triggersData")) + { + QList *triggersData = response.getDoa2Settings()->getScopeConfig()->getTriggersData(); + scopeSettings.m_triggersData.clear(); + + for (int i = 0; i < 10; i++) // no more than 10 triggers anyway + { + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1]").arg(i))) + { + SWGSDRangel::SWGTriggerData *triggerData = triggersData->at(i); + scopeSettings.m_triggersData.push_back(GLScopeSettings::TriggerData()); + + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].streamIndex").arg(i))) { + scopeSettings.m_triggersData.back().m_streamIndex = triggerData->getStreamIndex(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].inputIndex").arg(i))) { + scopeSettings.m_triggersData.back().m_inputIndex = triggerData->getInputIndex(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].projectionType").arg(i))) { + scopeSettings.m_triggersData.back().m_projectionType = (Projector::ProjectionType) triggerData->getProjectionType(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerBothEdges").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerBothEdges = triggerData->getTriggerBothEdges() != 0; + } + if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].triggerColor").arg(i))) { + scopeSettings.m_tracesData.back().m_traceColor = intToQColor(triggerData->getTriggerColor()); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorB").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerColorB = triggerData->getTriggerColorB(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorG").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerColorG = triggerData->getTriggerColorG(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorR").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerColorR = triggerData->getTriggerColorR(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelay").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerDelay = triggerData->getTriggerDelay(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayCoarse").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerDelayCoarse = triggerData->getTriggerDelayCoarse(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayFine").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerDelayFine = triggerData->getTriggerDelayFine(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayMult").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerDelayMult = triggerData->getTriggerDelayMult(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerHoldoff").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerHoldoff = triggerData->getTriggerHoldoff(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevel").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerLevel = triggerData->getTriggerLevel(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevelCoarse").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerLevelCoarse = triggerData->getTriggerLevelCoarse(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevelFine").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerLevelFine = triggerData->getTriggerLevelFine(); + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerPositiveEdge").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerPositiveEdge = triggerData->getTriggerPositiveEdge() != 0; + } + if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerRepeat").arg(i))) { + scopeSettings.m_triggersData.back().m_triggerRepeat = triggerData->getTriggerRepeat() != 0; + } + } + } + } + } +} + +int DOA2WebAPIAdapter::qColorToInt(const QColor& color) +{ + return 256*256*color.blue() + 256*color.green() + color.red(); +} + +QColor DOA2WebAPIAdapter::intToQColor(int intColor) +{ + int r = intColor % 256; + int bg = intColor / 256; + int g = bg % 256; + int b = bg / 256; + return QColor(r, g, b); +} diff --git a/plugins/channelmimo/doa2/doa2webapiadapter.h b/plugins/channelmimo/doa2/doa2webapiadapter.h new file mode 100644 index 000000000..62b249646 --- /dev/null +++ b/plugins/channelmimo/doa2/doa2webapiadapter.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2022 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_DOA2_WEBAPIADAPTER_H +#define INCLUDE_DOA2_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "dsp/glscopesettings.h" +#include "dsp/spectrumsettings.h" +#include "doa2settings.h" + +/** + * Standalone API adapter only for the settings + */ +class DOA2WebAPIAdapter : public ChannelWebAPIAdapter { +public: + DOA2WebAPIAdapter(); + virtual ~DOA2WebAPIAdapter(); + + 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); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const DOA2Settings& settings, + const GLScopeSettings& scopeSettings); + + static void webapiUpdateChannelSettings( + DOA2Settings& settings, + GLScopeSettings& scopeSettings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response); + +private: + DOA2Settings m_settings; + GLScopeSettings m_glScopeSettings; + + static int qColorToInt(const QColor& color); + static QColor intToQColor(int intColor); +}; + +#endif // INCLUDE_DOA2_WEBAPIADAPTER_H diff --git a/plugins/channelmimo/doa2/readme.md b/plugins/channelmimo/doa2/readme.md new file mode 100644 index 000000000..cbadd93c0 --- /dev/null +++ b/plugins/channelmimo/doa2/readme.md @@ -0,0 +1,221 @@ +

DOA with 2 sources plugin

+ +

Introduction

+ +This MIMO reception only (MI) plugin can be used to determine the direction of arrival (DOA) of an incoming wave on a 2 antenna system connected to a coherent dual receiving device in MIMO (thus MI) mode like BladeRF3, LimeSDR USB, Pluto+. It is assume that antenna 1 is connected to stream 0 and antenna 2 is connected to stream 1. The direction of the antenna system goes from antenna 1 to antenna 2. + +Example of setup: + +![DOA2 full picture](../../../doc/img/DOA2_plugin_full.png) + +This plugin has similarities with the [Interferometer plugin](../interferometer/readme.md) upon which it is built but is specialized in DOA processing and has displays and controls added in the "DOA" section. Also there is no spectrum display as it is of little interest for this purpose. + +

Interface

+ +The top and bottom bars of the channel window are described [here](../../../sdrgui/channel/readme.md) + +![DOA2 plugin GUI](../../../doc/img/DOA2_plugin.png) + +The interface is divided in 3 sections that will be detailed next: + - A: settings. These are the general plugin controls + - B: scope. This is a scope display analogous to other scope displays. I can show the A (stream 0) input, the B (stream 1) input or FFT (correlation for DOA). In the FFT case This is not a time domain but a frequency domain display transposed to time analogous to a frequency sweep. Therefore on the X and XY displays the time scale is replaced by a frequency scale. + - C: DOA specific section + +

A. Settings section

+ +![DOA2 plugin settings GUI](../../../doc/img/DOA2_plugin_settings.png) + +

A.1. Decimation

+ +Input streams from baseband are decimated by a power of two. Use this combo to select from 0 (no decimation) to 64 (2^6). The resulting channel sample rate is displayed next (A.2) + +

A.2. Channel sample rate

+ +This is the channel sample rate in kilo or mega samples per second indicated by the `k` or `M` letter. + +

A.3. Half-band filter chain sequence display

+ +This string represents the sequence of half-band filters used in the decimation from device baseband to channel stream and controlled by (A.7). Each character represents a filter type: + + - **L**: lower half-band + - **H**: higher half-band + - **C**: centered + +

A.4. Correlation function

+ +This combo selects which function is applied to A and B channel inputs to obtain resulting correlation signal. + - **A**: A channel only (no correlation thus no DOA processing) + - **B**: B channel only (no correlation thus no DOA processing) + - **FFT**: The FFT of A is multiplied by the conjugate of the FFT of B (no IFFT) thus the result is in the frequency domain. This is sometimes called "interspectrum". This is used to evaluate the direction of arrival (DOA) of a plane wave which is the purpose of this plugin. Note that DOA processing is active only in this mode. + +

Scope inputs

+ +The scope display inputs vary depending on the correlation types and are summarized in the table below: + + + + + + + + + + + + + + + + + + +
Correlation typeScope
AA(t)
BB(t)
FFTFFT(A)*FFT(B)* possibly shows several FFT lengths on the scope (B) depending on trace length. Leave the default trace length to get accurate frequency readings on the X scale
+ +

A.5. Phase difference correction

+ +This is the phase correction in degrees applied to signal in channel B. + +

A.6 Center frequency shift

+ +This is the shift of the channel center frequency from the device center frequency. Its value is driven by the baseband sample rate, the decimation factor (A.1) and the filter chain sequence (A.7). + +

A.7. Half-band filter chain sequence adjust

+ +The slider moves the channel center frequency roughly from the lower to the higher frequency in the device baseband. The number on the right represents the filter sequence as the decimal value of a base 3 number. Each base 3 digit represents the filter type and its sequence from MSB to LSB in the filter chain: + + - **0**: lower half-band + - **1**: centered + - **2**: higher half-band + +

A.8. Center filter in passband

+ +Use this button to center the filter in the middle of the passband automatically. This sets all filters to center i.e CCC... + +The resulting filter chain sequence is represented in (A.3) + +

B. Scope display

+ +This is the scope (mainly time domain) display. This display and controls are identical to all scope displays in the software. Further details can be found in [Channel Analyzer documentation](../../channelrx/chanalyzer/readme.md) + +See the principle of operation section below for optimum scope settings. + +

C. DOA settings

+ +![DOA2 settings DOA](../../../doc/img/DOA2_plugin_DOA.png) + +

C.1 Compass readings

+ +![DOA2 compass](../../../doc/img/DOA2_plugin_compass.png) + +

C.1.1 Port side incoming wave

+ +The red arrow shows the direction of the incoming wave assuming it is ccoming from the port side of the antenna system (antenna 1 to 2). The value in degrees is displayed in (C.2). + +This also corresponds to positive angles in the trigonometric sense with respect to the antenna baseline. + +

C.1.2 Starboard side incoming wave

+ +The green arrow shows the direction of the incoming wave assuming it is ccoming from the starboard side of the antenna system (antenna 1 to 2). The value in degrees is displayed in (C.3). + +This also corresponds to negative angles in the trigonometric sense with respect to the antenna baseline. + +

C.1.3 Compass needle

+ +The compass needle shows the antennas direction with the white arrow towards antenna 2 and the grey arrow towards antenna 1. The direction (azimuth) is set with control (C.4). You must update this direction when the direction of the antenna system changes to get accurate readings. + +

C.1.4 Blind sector

+ +The darker area on the compass background shows the sector where no readings can be made when the distance between the two antennas (baseline distance set in C.6) is larger than the half of the wavelength displayed in (C.5). Check the principle of operation section at the ned of the document for details. + +

C.2 Positive DOA angle (Port side)

+ +Displays the posiive azimuth in degrees with respect to antenna direction of the incoming wave. This corresponds to the port side with respect to the antenna system from antenna 1 to antenna 2. This direction is displayed with a red arrow on the compass (1). + +

C.3 Negative DOA angle (Starboard side)

+ +Displays the negative azimuth in degrees with respect to antenna direction of the incoming wave. This corresponds to the starboard side with respect to the antenna system from antenna 1 to antenna 2. This direction is displayed with a green arrow on the compass (1). + +

C.4 Antenna system azimuth

+ +This is the direction in degrees of the antenna system from antenna 1 to antenna 2. You must update this value when the direction of the antenna system changes to get accurate readings. + +

C.5 Half wavelength distance in mm

+ +This displays the half wavelength distance in millimeters at the center of frequency of reception. + +

C.6 Antenna baseline distance in mm

+ +This is the distance in millimeters between antenna 1 and antenna 2. You must update this value when the distance between antennas change to get accurate readings. + +

C.7 Squelch threshold

+ +This is the threshold of squared magnitude in dB above which DOA processing takes place. You can use the scope display in XY mode to find the best value. + +![DOA2 XY display](../../../doc/img/DOA2_plugin_xy.png) + +In this exaple setting a squelch value of -55~-50 dB will select the narrow peak corresponding to samples giving the most accurate DOA measurements. + +This threshold can also be used with transient signals to activate DOA processing only when the signal is present effectively acting like a squelch in FM modes. + +

C.8 FFT Averaging

+ +This is the number of FFT series used for DOA calculation thus the weighting average is computed over this many FFTs. The tooltip shows the averaging time to obtain one result. + +

Principle of operation

+ +DOA estimation is based on the "FFT" correlation funtion and active only when selected with (A.2). FFT analysis helps in removing non essential contributions and is more efficient than simple product wutn conjugate (A.B*). + +It assumes that channel A is connected to antenna 1 or antenna of reference (device stream 0) and channel B is connected antenna 2 the second antenna (device stream 1) in the following configuration: + +![Interferometer antennas](../../../doc/img/interferometer_antennas.png) + +

Configuring the scope dsisplay

+ +The scope shall be configured to have X and Y displays with Y1 set to a magnitude display projection and X to a phase related projection. See scope controls in B section for setup. Here are the different possibilities: + - **X**: Phi, DOAP, DOAN + - **Y1**: Mag, MagSq, MagDB (this one is usually the most convenient) + +You will select the XY display on the scope and you can use the polar grid display to show phase or direction angles with respect to the antenna system directly. Note that for direction modes DOAP or DOAN a distance of half wavelength is assumed between antennas. + +Angles are counted from -π to π and normalized to π for display thus displayed from -1.0 to 1.0 on the scope. + +

DOA processing

+ +The phase difference φ between the reference signal A (antenna 1) and second signal B (antenna 2) and seen on scope X display with `Phi` selected is used to estimate the direction of arrival (DOA) of the incoming wave. For a given phase difference the wave may come from the positive side (port side) of angles (0 to π) or the negative side (starboard side) (-π to 0). Angles of arrival are referenced to the axis perpendicular to the axis passing by the two antennas. + +![Interferometer DOA](../../../doc/img/interferometer_doa.png) + +Thus when antennas are separated by half the wavelength the relation between the angle of arrival θ and the phase difference φ can be expressed as: + +φ = π cos(θ) thus θ = acos(φ / π) + +This angle can be displayed on the scope when `DOAP` (positive angles) or `DOAN` (negative angles) is selected for X input. + +![Interferometer DOA General](../../../doc/img/interferometer_doa_general.png) + +In general the angle can be calculated from the baseline distance D (distance between antennas) with the following formula: + +φ = π d / (λ/2) +φ = π D cos(θ) / (λ/2) +cos(θ) = (φ / π) . ((λ/2) / D) + +If D is larger than half the wavelength (λ/2) then a section in front of antenna 2 and at the back of antenna 1 cannot be reached since cos(θ) will lie in an interval smaller than [-1:1]. This section is shown on the compass with a darker background (C.1.4) + +If D is smaller than half the wavelength some incorrect values can be read as some angles will ovelap. Unless you have a hint about the wave direction this mode of operation is not recommended, + +There are two possible angles for the incoming wave leading to the same phase difference. One from the port side of the antenna system (positive) and the other from the starboard side (negative). + +To disambiguate readings one may turn the antenna system then the correct side will show a constant absolute angle with respect to true North (or South). + +For best results the antenna system should be clear of possible reflectors including your own body that can affect the incoming direct wave. Ideally you should also start the procedure with a distance between antennas (baseline distance) of half the wavelength of the signal of interest. + +Then a possible procedure to determine DOA is the following: + +1. Arrange antennas axis so that the phase difference φ is zero or DOA angle θ is roughly π/2 +2. Make an assumption for the wave to come from the positive (port) or negative (starboard) angles side and take it as the DOA angle +3. Rotate the antennas axis of about 45 degrees and if the DOA angle is roughly stable in absolute value then the assumption is correct and the wave is coming from the side corresponding to your assumption. You can then refine the antenna axis direction to obtain a π/2 or -π/2 angle depending from which side the wave is coming. The red arrow on the compass (C.1.1) shows the absolute direction of the wave coming from the port side and the green arrow (C.1.2) shows the absolute direction of the wave coming from the starboard side assuming that the direction of the antennas is set properly and follows the antennas system movement (C.4). +4. If when performing previous step (3) the DOA angle is significantly moving in absolute value then the wave is coming from the opposite side of the antenna system. Then take the other side angle reading as valid. +5. Once the ±π/2 DOA angle (zero phase difference) is obtained at λ/2 distance between antennas you can move your antennas further apart to refine the ±π/2 DOA angle. + + + diff --git a/plugins/channelmimo/interferometer/interferometerplugin.cpp b/plugins/channelmimo/interferometer/interferometerplugin.cpp index a163b6682..584fbd681 100644 --- a/plugins/channelmimo/interferometer/interferometerplugin.cpp +++ b/plugins/channelmimo/interferometer/interferometerplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor InterferometerPlugin::m_pluginDescriptor = { Interferometer::m_channelId, QStringLiteral("Interferometer"), - QStringLiteral("7.0.0"), + QStringLiteral("7.3.0"), QStringLiteral("(c) Edouard Griffiths, F4EXB"), QStringLiteral("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp index 6f534d75b..4d11d62bf 100644 --- a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp +++ b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp @@ -520,6 +520,7 @@ void FreeDVDemodGUI::makeUIConnections() QObject::connect(ui->volumeIn, &QDial::valueChanged, this, &FreeDVDemodGUI::on_volumeIn_valueChanged); QObject::connect(ui->agc, &ButtonSwitch::toggled, this, &FreeDVDemodGUI::on_agc_toggled); QObject::connect(ui->audioMute, &QToolButton::toggled, this, &FreeDVDemodGUI::on_audioMute_toggled); + QObject::connect(ui->spanLog2, &QSlider::valueChanged, this, &FreeDVDemodGUI::on_spanLog2_valueChanged); } void FreeDVDemodGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channeltx/modfreedv/freedvmodgui.cpp b/plugins/channeltx/modfreedv/freedvmodgui.cpp index 19ac57d3e..58325a00a 100644 --- a/plugins/channeltx/modfreedv/freedvmodgui.cpp +++ b/plugins/channeltx/modfreedv/freedvmodgui.cpp @@ -635,6 +635,7 @@ void FreeDVModGUI::makeUIConnections() QObject::connect(ui->morseKeyer, &ButtonSwitch::toggled, this, &FreeDVModGUI::on_morseKeyer_toggled); QObject::connect(ui->navTimeSlider, &QSlider::valueChanged, this, &FreeDVModGUI::on_navTimeSlider_valueChanged); QObject::connect(ui->showFileDialog, &QPushButton::clicked, this, &FreeDVModGUI::on_showFileDialog_clicked); + QObject::connect(ui->spanLog2, &QSlider::valueChanged, this, &FreeDVModGUI::on_spanLog2_valueChanged); } void FreeDVModGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channeltx/modfreedv/freedvmodplugin.cpp b/plugins/channeltx/modfreedv/freedvmodplugin.cpp index f2d036766..b83c58296 100644 --- a/plugins/channeltx/modfreedv/freedvmodplugin.cpp +++ b/plugins/channeltx/modfreedv/freedvmodplugin.cpp @@ -28,7 +28,7 @@ const PluginDescriptor FreeDVModPlugin::m_pluginDescriptor = { FreeDVMod::m_channelId, QStringLiteral("FreeDV Modulator"), - QStringLiteral("7.0.0"), + QStringLiteral("7.3.0"), QStringLiteral("(c) Edouard Griffiths, F4EXB"), QStringLiteral("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channeltx/modfreedv/freedvmodsource.cpp b/plugins/channeltx/modfreedv/freedvmodsource.cpp index 4df1ca2e8..373360614 100644 --- a/plugins/channeltx/modfreedv/freedvmodsource.cpp +++ b/plugins/channeltx/modfreedv/freedvmodsource.cpp @@ -455,7 +455,7 @@ void FreeDVModSource::applyFreeDVMode(FreeDVModSettings::FreeDVMode mode) { case FreeDVModSettings::FreeDVMode700C: fdv_mode = FREEDV_MODE_700C; - m_scaleFactor = SDR_TX_SCALEF / 3.2f; + m_scaleFactor = SDR_TX_SCALEF / 6.4f; break; case FreeDVModSettings::FreeDVMode700D: fdv_mode = FREEDV_MODE_700D; @@ -463,16 +463,16 @@ void FreeDVModSource::applyFreeDVMode(FreeDVModSettings::FreeDVMode mode) break; case FreeDVModSettings::FreeDVMode800XA: fdv_mode = FREEDV_MODE_800XA; - m_scaleFactor = SDR_TX_SCALEF / 8.2f; + m_scaleFactor = SDR_TX_SCALEF / 10.3f; break; case FreeDVModSettings::FreeDVMode1600: fdv_mode = FREEDV_MODE_1600; - m_scaleFactor = SDR_TX_SCALEF / 3.2f; + m_scaleFactor = SDR_TX_SCALEF / 4.0f; break; case FreeDVModSettings::FreeDVMode2400A: default: fdv_mode = FREEDV_MODE_2400A; - m_scaleFactor = SDR_TX_SCALEF / 8.2f; + m_scaleFactor = SDR_TX_SCALEF / 10.3f; break; } diff --git a/sdrbase/resources/webapi.qrc b/sdrbase/resources/webapi.qrc index 5c38b5c68..7e941bf14 100644 --- a/sdrbase/resources/webapi.qrc +++ b/sdrbase/resources/webapi.qrc @@ -36,6 +36,7 @@ webapi/doc/swagger/include/DATVDemod.yaml webapi/doc/swagger/include/DATVMod.yaml webapi/doc/swagger/include/DemodAnalyzer.yaml + webapi/doc/swagger/include/DOA2.yaml webapi/doc/swagger/include/DSDDemod.yaml webapi/doc/swagger/include/DeviceActions.yaml webapi/doc/swagger/include/DeviceSettings.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 120b8e2ef..e0203e2fd 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -3397,6 +3397,9 @@ margin-bottom: 20px; "DATVModReport" : { "$ref" : "#/definitions/DATVModReport" }, + "DOA2Report" : { + "$ref" : "#/definitions/DOA2Report" + }, "DSDDemodReport" : { "$ref" : "#/definitions/DSDDemodReport" }, @@ -3543,6 +3546,9 @@ margin-bottom: 20px; "DABDemodSettings" : { "$ref" : "#/definitions/DABDemodSettings" }, + "DOA2Settings" : { + "$ref" : "#/definitions/DOA2Settings" + }, "DSDDemodSettings" : { "$ref" : "#/definitions/DSDDemodSettings" }, @@ -4522,6 +4528,97 @@ margin-bottom: 20px; } }, "description" : "DATVMod" +}; + defs.DOA2Report = { + "properties" : { + "phi" : { + "type" : "integer", + "description" : "Raw phase difference in degrees from 0 to 180" + }, + "posAz" : { + "type" : "integer", + "description" : "Calculated port side (positive) arrival angle in degrees from 0 to 180" + }, + "negAz" : { + "type" : "integer", + "description" : "Calculated starboard side (negative) arrival angle in degrees from 0 to 180" + }, + "fftSize" : { + "type" : "integer", + "description" : "Size of FFT used in correlation" + }, + "channelSampleRate" : { + "type" : "integer", + "description" : "Channel sample rate (then used in FFT) in S/s" + } + }, + "description" : "DOA2" +}; + defs.DOA2Settings = { + "properties" : { + "correlationType" : { + "type" : "integer", + "description" : "see DOA2Settings::CorrelationType" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "log2Decim" : { + "type" : "integer" + }, + "filterChainHash" : { + "type" : "integer" + }, + "phase" : { + "type" : "integer", + "description" : "Phase difference correction in degrees from -180 to +180" + }, + "antennaAz" : { + "type" : "integer", + "description" : "Antennas azimuth from antenna 1 to antenna 2 in degrees from 0 to 359" + }, + "basebandDistance" : { + "type" : "integer", + "description" : "Antennas baseline distance in millimeters from 1 to 99999" + }, + "squelchdB" : { + "type" : "integer", + "description" : "Porcessing squared magnitude threshold (squelch) in dB from -140 t0 0" + }, + "fftAveragingValue" : { + "type" : "integer", + "description" : "Number of FFTs to average over. Use 1, 2, 5 or 10 times 10^0 to 10^5 (1 to 1M)" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + }, + "scopeConfig" : { + "$ref" : "#/definitions/GLScope" + }, + "channelMarker" : { + "$ref" : "#/definitions/ChannelMarker" + }, + "rollupState" : { + "$ref" : "#/definitions/RollupState" + } + }, + "description" : "DOA2" }; defs.DSDDemodReport = { "properties" : { @@ -56060,7 +56157,7 @@ except ApiException as e:
- Generated 2022-05-25T12:47:57.273+02:00 + Generated 2022-05-28T12:29:36.569+02:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChannelReport.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChannelReport.yaml index df8b65951..986ee1f58 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChannelReport.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChannelReport.yaml @@ -33,6 +33,8 @@ ChannelReport: $ref: "/doc/swagger/include/DATVDemod.yaml#/DATVDemodReport" DATVModReport: $ref: "/doc/swagger/include/DATVMod.yaml#/DATVModReport" + DOA2Report: + $ref: "/doc/swagger/include/DOA2.yaml#/DOA2Report" DSDDemodReport: $ref: "/doc/swagger/include/DSDDemod.yaml#/DSDDemodReport" IEEE_802_15_4_ModReport: diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml index 126328d40..c3a73c9d5 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChannelSettings.yaml @@ -49,6 +49,8 @@ ChannelSettings: $ref: "/doc/swagger/include/DATVDemod.yaml#/DATVDemodSettings" DABDemodSettings: $ref: "/doc/swagger/include/DABDemod.yaml#/DABDemodSettings" + DOA2Settings: + $ref: "/doc/swagger/include/DOA2.yaml#/DOA2Settings" DSDDemodSettings: $ref: "/doc/swagger/include/DSDDemod.yaml#/DSDDemodSettings" FileSinkSettings: diff --git a/sdrbase/resources/webapi/doc/swagger/include/DOA2.yaml b/sdrbase/resources/webapi/doc/swagger/include/DOA2.yaml new file mode 100644 index 000000000..9ed1a5880 --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/DOA2.yaml @@ -0,0 +1,65 @@ +DOA2Settings: + description: DOA2 + properties: + correlationType: + description: see DOA2Settings::CorrelationType + type: integer + rgbColor: + type: integer + title: + type: string + log2Decim: + type: integer + filterChainHash: + type: integer + phase: + type: integer + description: Phase difference correction in degrees from -180 to +180 + antennaAz: + type: integer + description: Antennas azimuth from antenna 1 to antenna 2 in degrees from 0 to 359 + basebandDistance: + type: integer + description: Antennas baseline distance in millimeters from 1 to 99999 + squelchdB: + type: integer + description: Porcessing squared magnitude threshold (squelch) in dB from -140 t0 0 + fftAveragingValue: + type: integer + description: Number of FFTs to average over. Use 1, 2, 5 or 10 times 10^0 to 10^5 (1 to 1M) + 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 + scopeConfig: + $ref: "/doc/swagger/include/GLScope.yaml#/GLScope" + channelMarker: + $ref: "/doc/swagger/include/ChannelMarker.yaml#/ChannelMarker" + rollupState: + $ref: "/doc/swagger/include/RollupState.yaml#/RollupState" + +DOA2Report: + description: DOA2 + properties: + phi: + type: integer + description: Raw phase difference in degrees from 0 to 180 + posAz: + type: integer + description: Calculated port side (positive) arrival angle in degrees from 0 to 180 + negAz: + type: integer + description: Calculated starboard side (negative) arrival angle in degrees from 0 to 180 + fftSize: + type: integer + description: Size of FFT used in correlation + channelSampleRate: + type: integer + description: Channel sample rate (then used in FFT) in S/s diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 291ccb590..17b2e8d6d 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -4471,6 +4471,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings()); channelSettings->getDabDemodSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "DOA2Settings") + { + channelSettings->setDoa2Settings(new SWGSDRangel::SWGDOA2Settings()); + channelSettings->getDoa2Settings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "DSDDemodSettings") { channelSettings->setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index 8708cf012..fdbb0024b 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -40,6 +40,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channel.demodatv", "ATVDemodSettings"}, {"sdrangel.channel.demoddatv", "DATVDemodSettings"}, {"sdrangel.channel.dabdemod", "DABDemodSettings"}, + {"sdrangel.channel.doa2", "DOA2Settings"}, {"sdrangel.channel.dsddemod", "DSDDemodSettings"}, {"sdrangel.channel.filesink", "FileSinkSettings"}, {"sdrangel.channeltx.filesource", "FileSourceSettings"}, @@ -144,6 +145,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"DATVDemod", "DATVDemodSettings"}, {"DATVMod", "DATVModSettings"}, {"DABDemod", "DABDemodSettings"}, + {"DOA2", "DOA2Settings"}, {"DSDDemod", "DSDDemodSettings"}, {"FileSink", "FileSinkSettings"}, {"FileSource", "FileSourceSettings"}, diff --git a/sdrgui/gui/glscope.cpp b/sdrgui/gui/glscope.cpp index f8608af91..6a6a778ba 100644 --- a/sdrgui/gui/glscope.cpp +++ b/sdrgui/gui/glscope.cpp @@ -75,6 +75,9 @@ GLScope::GLScope(QWidget *parent) : m_x1Scale.setOrientation(Qt::Horizontal); m_x2Scale.setFont(font()); m_x2Scale.setOrientation(Qt::Horizontal); + m_xScaleFreq = false; + m_xScaleCenterFrequency = 0; + m_xScaleFrequencySpan = 48000; m_channelOverlayFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_channelOverlayFont.setBold(true); @@ -1077,12 +1080,23 @@ void GLScope::applyConfig() // scales - m_x1Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale - - if (m_displayMode == DisplayPol) { - setYScale(m_x2Scale, 0); // polar scale (X) + if (m_xScaleFreq) { + m_x1Scale.setRange(Unit::Frequency, m_xScaleCenterFrequency - (m_xScaleFrequencySpan/2), m_xScaleCenterFrequency + (m_xScaleFrequencySpan/2)); } else { - m_x2Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale + m_x1Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale + } + + if (m_displayMode == DisplayPol) + { + setYScale(m_x2Scale, 0); // polar scale (X) + } + else + { + if (m_xScaleFreq) { + m_x2Scale.setRange(Unit::Time, t_start, t_start + t_len); // time scale + } else { + m_x2Scale.setRange(Unit::Frequency, m_xScaleCenterFrequency - (m_xScaleFrequencySpan/2), m_xScaleCenterFrequency + (m_xScaleFrequencySpan/2)); + } } if (m_traces->size() > 0) { diff --git a/sdrgui/gui/glscope.h b/sdrgui/gui/glscope.h index 65c7df0f6..560912c06 100644 --- a/sdrgui/gui/glscope.h +++ b/sdrgui/gui/glscope.h @@ -86,6 +86,11 @@ public: virtual const QAtomicInt& getProcessingTraceIndex() const { return m_processingTraceIndex; } void setTraceModulo(int modulo) { m_traceModulo = modulo; } + void setXScaleFreq(bool set) { m_xScaleFreq = set; m_configChanged = true; } + bool isXScaleFreq() const { return m_xScaleFreq; } + void setXScaleCenterFrequency(qint64 cf) { m_xScaleCenterFrequency = cf; m_configChanged = true; } + void setXScaleFrequencySpan(int span) { m_xScaleFrequencySpan = span; m_configChanged = true; } + signals: void sampleRateChanged(int); void traceSizeChanged(uint32_t); @@ -187,6 +192,9 @@ private: ScaleEngine m_x2Scale; //!< Display #2 X scale. Time scale ScaleEngine m_y1Scale; //!< Display #1 Y scale. Always connected to trace #0 (X trace) ScaleEngine m_y2Scale; //!< Display #2 Y scale. Connected to highlighted Y trace (#1..n) + bool m_xScaleFreq; //!< Force frequency display on time line for correlation modes + qint64 m_xScaleCenterFrequency; //!< Frequency time line mode center frequency + int m_xScaleFrequencySpan; //!< Frequency time line mode frequency span QFont m_channelOverlayFont; QFont m_textOverlayFont; diff --git a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml index deda36e1b..548d2ba87 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml @@ -33,6 +33,8 @@ ChannelReport: $ref: "http://swgserver:8081/api/swagger/include/DATVDemod.yaml#/DATVDemodReport" DATVModReport: $ref: "http://swgserver:8081/api/swagger/include/DATVMod.yaml#/DATVModReport" + DOA2Report: + $ref: "http://swgserver:8081/api/swagger/include/DOA2.yaml#/DOA2Report" DSDDemodReport: $ref: "http://swgserver:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodReport" IEEE_802_15_4_ModReport: diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index b7c594db1..5125d53ad 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -49,6 +49,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/DATVDemod.yaml#/DATVDemodSettings" DABDemodSettings: $ref: "http://swgserver:8081/api/swagger/include/DABDemod.yaml#/DABDemodSettings" + DOA2Settings: + $ref: "http://swgserver:8081/api/swagger/include/DOA2.yaml#/DOA2Settings" DSDDemodSettings: $ref: "http://swgserver:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodSettings" FileSinkSettings: diff --git a/swagger/sdrangel/api/swagger/include/DOA2.yaml b/swagger/sdrangel/api/swagger/include/DOA2.yaml new file mode 100644 index 000000000..55c006fcc --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/DOA2.yaml @@ -0,0 +1,65 @@ +DOA2Settings: + description: DOA2 + properties: + correlationType: + description: see DOA2Settings::CorrelationType + type: integer + rgbColor: + type: integer + title: + type: string + log2Decim: + type: integer + filterChainHash: + type: integer + phase: + type: integer + description: Phase difference correction in degrees from -180 to +180 + antennaAz: + type: integer + description: Antennas azimuth from antenna 1 to antenna 2 in degrees from 0 to 359 + basebandDistance: + type: integer + description: Antennas baseline distance in millimeters from 1 to 99999 + squelchdB: + type: integer + description: Porcessing squared magnitude threshold (squelch) in dB from -140 t0 0 + fftAveragingValue: + type: integer + description: Number of FFTs to average over. Use 1, 2, 5 or 10 times 10^0 to 10^5 (1 to 1M) + 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 + scopeConfig: + $ref: "http://swgserver:8081/api/swagger/include/GLScope.yaml#/GLScope" + channelMarker: + $ref: "http://swgserver:8081/api/swagger/include/ChannelMarker.yaml#/ChannelMarker" + rollupState: + $ref: "http://swgserver:8081/api/swagger/include/RollupState.yaml#/RollupState" + +DOA2Report: + description: DOA2 + properties: + phi: + type: integer + description: Raw phase difference in degrees from 0 to 180 + posAz: + type: integer + description: Calculated port side (positive) arrival angle in degrees from 0 to 180 + negAz: + type: integer + description: Calculated starboard side (negative) arrival angle in degrees from 0 to 180 + fftSize: + type: integer + description: Size of FFT used in correlation + channelSampleRate: + type: integer + description: Channel sample rate (then used in FFT) in S/s diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 120b8e2ef..e0203e2fd 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -3397,6 +3397,9 @@ margin-bottom: 20px; "DATVModReport" : { "$ref" : "#/definitions/DATVModReport" }, + "DOA2Report" : { + "$ref" : "#/definitions/DOA2Report" + }, "DSDDemodReport" : { "$ref" : "#/definitions/DSDDemodReport" }, @@ -3543,6 +3546,9 @@ margin-bottom: 20px; "DABDemodSettings" : { "$ref" : "#/definitions/DABDemodSettings" }, + "DOA2Settings" : { + "$ref" : "#/definitions/DOA2Settings" + }, "DSDDemodSettings" : { "$ref" : "#/definitions/DSDDemodSettings" }, @@ -4522,6 +4528,97 @@ margin-bottom: 20px; } }, "description" : "DATVMod" +}; + defs.DOA2Report = { + "properties" : { + "phi" : { + "type" : "integer", + "description" : "Raw phase difference in degrees from 0 to 180" + }, + "posAz" : { + "type" : "integer", + "description" : "Calculated port side (positive) arrival angle in degrees from 0 to 180" + }, + "negAz" : { + "type" : "integer", + "description" : "Calculated starboard side (negative) arrival angle in degrees from 0 to 180" + }, + "fftSize" : { + "type" : "integer", + "description" : "Size of FFT used in correlation" + }, + "channelSampleRate" : { + "type" : "integer", + "description" : "Channel sample rate (then used in FFT) in S/s" + } + }, + "description" : "DOA2" +}; + defs.DOA2Settings = { + "properties" : { + "correlationType" : { + "type" : "integer", + "description" : "see DOA2Settings::CorrelationType" + }, + "rgbColor" : { + "type" : "integer" + }, + "title" : { + "type" : "string" + }, + "log2Decim" : { + "type" : "integer" + }, + "filterChainHash" : { + "type" : "integer" + }, + "phase" : { + "type" : "integer", + "description" : "Phase difference correction in degrees from -180 to +180" + }, + "antennaAz" : { + "type" : "integer", + "description" : "Antennas azimuth from antenna 1 to antenna 2 in degrees from 0 to 359" + }, + "basebandDistance" : { + "type" : "integer", + "description" : "Antennas baseline distance in millimeters from 1 to 99999" + }, + "squelchdB" : { + "type" : "integer", + "description" : "Porcessing squared magnitude threshold (squelch) in dB from -140 t0 0" + }, + "fftAveragingValue" : { + "type" : "integer", + "description" : "Number of FFTs to average over. Use 1, 2, 5 or 10 times 10^0 to 10^5 (1 to 1M)" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + }, + "reverseAPIChannelIndex" : { + "type" : "integer" + }, + "scopeConfig" : { + "$ref" : "#/definitions/GLScope" + }, + "channelMarker" : { + "$ref" : "#/definitions/ChannelMarker" + }, + "rollupState" : { + "$ref" : "#/definitions/RollupState" + } + }, + "description" : "DOA2" }; defs.DSDDemodReport = { "properties" : { @@ -56060,7 +56157,7 @@ except ApiException as e:
- Generated 2022-05-25T12:47:57.273+02:00 + Generated 2022-05-28T12:29:36.569+02:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index a328bbb26..8759fbce4 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -54,6 +54,8 @@ SWGChannelReport::SWGChannelReport() { m_datv_demod_report_isSet = false; datv_mod_report = nullptr; m_datv_mod_report_isSet = false; + doa2_report = nullptr; + m_doa2_report_isSet = false; dsd_demod_report = nullptr; m_dsd_demod_report_isSet = false; ieee_802_15_4_mod_report = nullptr; @@ -138,6 +140,8 @@ SWGChannelReport::init() { m_datv_demod_report_isSet = false; datv_mod_report = new SWGDATVModReport(); m_datv_mod_report_isSet = false; + doa2_report = new SWGDOA2Report(); + m_doa2_report_isSet = false; dsd_demod_report = new SWGDSDDemodReport(); m_dsd_demod_report_isSet = false; ieee_802_15_4_mod_report = new SWGIEEE_802_15_4_ModReport(); @@ -229,6 +233,9 @@ SWGChannelReport::cleanup() { if(datv_mod_report != nullptr) { delete datv_mod_report; } + if(doa2_report != nullptr) { + delete doa2_report; + } if(dsd_demod_report != nullptr) { delete dsd_demod_report; } @@ -343,6 +350,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&datv_mod_report, pJson["DATVModReport"], "SWGDATVModReport", "SWGDATVModReport"); + ::SWGSDRangel::setValue(&doa2_report, pJson["DOA2Report"], "SWGDOA2Report", "SWGDOA2Report"); + ::SWGSDRangel::setValue(&dsd_demod_report, pJson["DSDDemodReport"], "SWGDSDDemodReport", "SWGDSDDemodReport"); ::SWGSDRangel::setValue(&ieee_802_15_4_mod_report, pJson["IEEE_802_15_4_ModReport"], "SWGIEEE_802_15_4_ModReport", "SWGIEEE_802_15_4_ModReport"); @@ -448,6 +457,9 @@ SWGChannelReport::asJsonObject() { if((datv_mod_report != nullptr) && (datv_mod_report->isSet())){ toJsonValue(QString("DATVModReport"), datv_mod_report, obj, QString("SWGDATVModReport")); } + if((doa2_report != nullptr) && (doa2_report->isSet())){ + toJsonValue(QString("DOA2Report"), doa2_report, obj, QString("SWGDOA2Report")); + } if((dsd_demod_report != nullptr) && (dsd_demod_report->isSet())){ toJsonValue(QString("DSDDemodReport"), dsd_demod_report, obj, QString("SWGDSDDemodReport")); } @@ -657,6 +669,16 @@ SWGChannelReport::setDatvModReport(SWGDATVModReport* datv_mod_report) { this->m_datv_mod_report_isSet = true; } +SWGDOA2Report* +SWGChannelReport::getDoa2Report() { + return doa2_report; +} +void +SWGChannelReport::setDoa2Report(SWGDOA2Report* doa2_report) { + this->doa2_report = doa2_report; + this->m_doa2_report_isSet = true; +} + SWGDSDDemodReport* SWGChannelReport::getDsdDemodReport() { return dsd_demod_report; @@ -951,6 +973,9 @@ SWGChannelReport::isSet(){ if(datv_mod_report && datv_mod_report->isSet()){ isObjectUpdated = true; break; } + if(doa2_report && doa2_report->isSet()){ + isObjectUpdated = true; break; + } if(dsd_demod_report && dsd_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 b20d6098e..9d2e66a79 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -33,6 +33,7 @@ #include "SWGChirpChatModReport.h" #include "SWGDATVDemodReport.h" #include "SWGDATVModReport.h" +#include "SWGDOA2Report.h" #include "SWGDSDDemodReport.h" #include "SWGFileSinkReport.h" #include "SWGFileSourceReport.h" @@ -117,6 +118,9 @@ public: SWGDATVModReport* getDatvModReport(); void setDatvModReport(SWGDATVModReport* datv_mod_report); + SWGDOA2Report* getDoa2Report(); + void setDoa2Report(SWGDOA2Report* doa2_report); + SWGDSDDemodReport* getDsdDemodReport(); void setDsdDemodReport(SWGDSDDemodReport* dsd_demod_report); @@ -235,6 +239,9 @@ private: SWGDATVModReport* datv_mod_report; bool m_datv_mod_report_isSet; + SWGDOA2Report* doa2_report; + bool m_doa2_report_isSet; + SWGDSDDemodReport* dsd_demod_report; bool m_dsd_demod_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index dc4f16b84..7c77c3aba 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -68,6 +68,8 @@ SWGChannelSettings::SWGChannelSettings() { m_datv_demod_settings_isSet = false; dab_demod_settings = nullptr; m_dab_demod_settings_isSet = false; + doa2_settings = nullptr; + m_doa2_settings_isSet = false; dsd_demod_settings = nullptr; m_dsd_demod_settings_isSet = false; file_sink_settings = nullptr; @@ -174,6 +176,8 @@ SWGChannelSettings::init() { m_datv_demod_settings_isSet = false; dab_demod_settings = new SWGDABDemodSettings(); m_dab_demod_settings_isSet = false; + doa2_settings = new SWGDOA2Settings(); + m_doa2_settings_isSet = false; dsd_demod_settings = new SWGDSDDemodSettings(); m_dsd_demod_settings_isSet = false; file_sink_settings = new SWGFileSinkSettings(); @@ -290,6 +294,9 @@ SWGChannelSettings::cleanup() { if(dab_demod_settings != nullptr) { delete dab_demod_settings; } + if(doa2_settings != nullptr) { + delete doa2_settings; + } if(dsd_demod_settings != nullptr) { delete dsd_demod_settings; } @@ -430,6 +437,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&dab_demod_settings, pJson["DABDemodSettings"], "SWGDABDemodSettings", "SWGDABDemodSettings"); + ::SWGSDRangel::setValue(&doa2_settings, pJson["DOA2Settings"], "SWGDOA2Settings", "SWGDOA2Settings"); + ::SWGSDRangel::setValue(&dsd_demod_settings, pJson["DSDDemodSettings"], "SWGDSDDemodSettings", "SWGDSDDemodSettings"); ::SWGSDRangel::setValue(&file_sink_settings, pJson["FileSinkSettings"], "SWGFileSinkSettings", "SWGFileSinkSettings"); @@ -564,6 +573,9 @@ SWGChannelSettings::asJsonObject() { if((dab_demod_settings != nullptr) && (dab_demod_settings->isSet())){ toJsonValue(QString("DABDemodSettings"), dab_demod_settings, obj, QString("SWGDABDemodSettings")); } + if((doa2_settings != nullptr) && (doa2_settings->isSet())){ + toJsonValue(QString("DOA2Settings"), doa2_settings, obj, QString("SWGDOA2Settings")); + } if((dsd_demod_settings != nullptr) && (dsd_demod_settings->isSet())){ toJsonValue(QString("DSDDemodSettings"), dsd_demod_settings, obj, QString("SWGDSDDemodSettings")); } @@ -855,6 +867,16 @@ SWGChannelSettings::setDabDemodSettings(SWGDABDemodSettings* dab_demod_settings) this->m_dab_demod_settings_isSet = true; } +SWGDOA2Settings* +SWGChannelSettings::getDoa2Settings() { + return doa2_settings; +} +void +SWGChannelSettings::setDoa2Settings(SWGDOA2Settings* doa2_settings) { + this->doa2_settings = doa2_settings; + this->m_doa2_settings_isSet = true; +} + SWGDSDDemodSettings* SWGChannelSettings::getDsdDemodSettings() { return dsd_demod_settings; @@ -1210,6 +1232,9 @@ SWGChannelSettings::isSet(){ if(dab_demod_settings && dab_demod_settings->isSet()){ isObjectUpdated = true; break; } + if(doa2_settings && doa2_settings->isSet()){ + isObjectUpdated = true; break; + } if(dsd_demod_settings && dsd_demod_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index a791d64fe..a5411e9f6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -38,6 +38,7 @@ #include "SWGDABDemodSettings.h" #include "SWGDATVDemodSettings.h" #include "SWGDATVModSettings.h" +#include "SWGDOA2Settings.h" #include "SWGDSDDemodSettings.h" #include "SWGFileSinkSettings.h" #include "SWGFileSourceSettings.h" @@ -147,6 +148,9 @@ public: SWGDABDemodSettings* getDabDemodSettings(); void setDabDemodSettings(SWGDABDemodSettings* dab_demod_settings); + SWGDOA2Settings* getDoa2Settings(); + void setDoa2Settings(SWGDOA2Settings* doa2_settings); + SWGDSDDemodSettings* getDsdDemodSettings(); void setDsdDemodSettings(SWGDSDDemodSettings* dsd_demod_settings); @@ -298,6 +302,9 @@ private: SWGDABDemodSettings* dab_demod_settings; bool m_dab_demod_settings_isSet; + SWGDOA2Settings* doa2_settings; + bool m_doa2_settings_isSet; + SWGDSDDemodSettings* dsd_demod_settings; bool m_dsd_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDOA2Report.cpp b/swagger/sdrangel/code/qt5/client/SWGDOA2Report.cpp new file mode 100644 index 000000000..8d6b95acb --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDOA2Report.cpp @@ -0,0 +1,200 @@ +/** + * 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: 7.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 "SWGDOA2Report.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDOA2Report::SWGDOA2Report(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDOA2Report::SWGDOA2Report() { + phi = 0; + m_phi_isSet = false; + pos_az = 0; + m_pos_az_isSet = false; + neg_az = 0; + m_neg_az_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGDOA2Report::~SWGDOA2Report() { + this->cleanup(); +} + +void +SWGDOA2Report::init() { + phi = 0; + m_phi_isSet = false; + pos_az = 0; + m_pos_az_isSet = false; + neg_az = 0; + m_neg_az_isSet = false; + fft_size = 0; + m_fft_size_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGDOA2Report::cleanup() { + + + + + +} + +SWGDOA2Report* +SWGDOA2Report::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDOA2Report::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&phi, pJson["phi"], "qint32", ""); + + ::SWGSDRangel::setValue(&pos_az, pJson["posAz"], "qint32", ""); + + ::SWGSDRangel::setValue(&neg_az, pJson["negAz"], "qint32", ""); + + ::SWGSDRangel::setValue(&fft_size, pJson["fftSize"], "qint32", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGDOA2Report::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDOA2Report::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_phi_isSet){ + obj->insert("phi", QJsonValue(phi)); + } + if(m_pos_az_isSet){ + obj->insert("posAz", QJsonValue(pos_az)); + } + if(m_neg_az_isSet){ + obj->insert("negAz", QJsonValue(neg_az)); + } + if(m_fft_size_isSet){ + obj->insert("fftSize", QJsonValue(fft_size)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +qint32 +SWGDOA2Report::getPhi() { + return phi; +} +void +SWGDOA2Report::setPhi(qint32 phi) { + this->phi = phi; + this->m_phi_isSet = true; +} + +qint32 +SWGDOA2Report::getPosAz() { + return pos_az; +} +void +SWGDOA2Report::setPosAz(qint32 pos_az) { + this->pos_az = pos_az; + this->m_pos_az_isSet = true; +} + +qint32 +SWGDOA2Report::getNegAz() { + return neg_az; +} +void +SWGDOA2Report::setNegAz(qint32 neg_az) { + this->neg_az = neg_az; + this->m_neg_az_isSet = true; +} + +qint32 +SWGDOA2Report::getFftSize() { + return fft_size; +} +void +SWGDOA2Report::setFftSize(qint32 fft_size) { + this->fft_size = fft_size; + this->m_fft_size_isSet = true; +} + +qint32 +SWGDOA2Report::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGDOA2Report::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGDOA2Report::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_phi_isSet){ + isObjectUpdated = true; break; + } + if(m_pos_az_isSet){ + isObjectUpdated = true; break; + } + if(m_neg_az_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_size_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/SWGDOA2Report.h b/swagger/sdrangel/code/qt5/client/SWGDOA2Report.h new file mode 100644 index 000000000..0274f389f --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDOA2Report.h @@ -0,0 +1,82 @@ +/** + * 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: 7.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. + */ + +/* + * SWGDOA2Report.h + * + * DOA2 + */ + +#ifndef SWGDOA2Report_H_ +#define SWGDOA2Report_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDOA2Report: public SWGObject { +public: + SWGDOA2Report(); + SWGDOA2Report(QString* json); + virtual ~SWGDOA2Report(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDOA2Report* fromJson(QString &jsonString) override; + + qint32 getPhi(); + void setPhi(qint32 phi); + + qint32 getPosAz(); + void setPosAz(qint32 pos_az); + + qint32 getNegAz(); + void setNegAz(qint32 neg_az); + + qint32 getFftSize(); + void setFftSize(qint32 fft_size); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + qint32 phi; + bool m_phi_isSet; + + qint32 pos_az; + bool m_pos_az_isSet; + + qint32 neg_az; + bool m_neg_az_isSet; + + qint32 fft_size; + bool m_fft_size_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGDOA2Report_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.cpp b/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.cpp new file mode 100644 index 000000000..b7cf65ac4 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.cpp @@ -0,0 +1,509 @@ +/** + * 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: 7.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 "SWGDOA2Settings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDOA2Settings::SWGDOA2Settings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDOA2Settings::SWGDOA2Settings() { + correlation_type = 0; + m_correlation_type_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + filter_chain_hash = 0; + m_filter_chain_hash_isSet = false; + phase = 0; + m_phase_isSet = false; + antenna_az = 0; + m_antenna_az_isSet = false; + baseband_distance = 0; + m_baseband_distance_isSet = false; + squelchd_b = 0; + m_squelchd_b_isSet = false; + fft_averaging_value = 0; + m_fft_averaging_value_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; + scope_config = nullptr; + m_scope_config_isSet = false; + channel_marker = nullptr; + m_channel_marker_isSet = false; + rollup_state = nullptr; + m_rollup_state_isSet = false; +} + +SWGDOA2Settings::~SWGDOA2Settings() { + this->cleanup(); +} + +void +SWGDOA2Settings::init() { + correlation_type = 0; + m_correlation_type_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + log2_decim = 0; + m_log2_decim_isSet = false; + filter_chain_hash = 0; + m_filter_chain_hash_isSet = false; + phase = 0; + m_phase_isSet = false; + antenna_az = 0; + m_antenna_az_isSet = false; + baseband_distance = 0; + m_baseband_distance_isSet = false; + squelchd_b = 0; + m_squelchd_b_isSet = false; + fft_averaging_value = 0; + m_fft_averaging_value_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; + scope_config = new SWGGLScope(); + m_scope_config_isSet = false; + channel_marker = new SWGChannelMarker(); + m_channel_marker_isSet = false; + rollup_state = new SWGRollupState(); + m_rollup_state_isSet = false; +} + +void +SWGDOA2Settings::cleanup() { + + + if(title != nullptr) { + delete title; + } + + + + + + + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + + if(scope_config != nullptr) { + delete scope_config; + } + if(channel_marker != nullptr) { + delete channel_marker; + } + if(rollup_state != nullptr) { + delete rollup_state; + } +} + +SWGDOA2Settings* +SWGDOA2Settings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDOA2Settings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&correlation_type, pJson["correlationType"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&log2_decim, pJson["log2Decim"], "qint32", ""); + + ::SWGSDRangel::setValue(&filter_chain_hash, pJson["filterChainHash"], "qint32", ""); + + ::SWGSDRangel::setValue(&phase, pJson["phase"], "qint32", ""); + + ::SWGSDRangel::setValue(&antenna_az, pJson["antennaAz"], "qint32", ""); + + ::SWGSDRangel::setValue(&baseband_distance, pJson["basebandDistance"], "qint32", ""); + + ::SWGSDRangel::setValue(&squelchd_b, pJson["squelchdB"], "qint32", ""); + + ::SWGSDRangel::setValue(&fft_averaging_value, pJson["fftAveragingValue"], "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", ""); + + ::SWGSDRangel::setValue(&scope_config, pJson["scopeConfig"], "SWGGLScope", "SWGGLScope"); + + ::SWGSDRangel::setValue(&channel_marker, pJson["channelMarker"], "SWGChannelMarker", "SWGChannelMarker"); + + ::SWGSDRangel::setValue(&rollup_state, pJson["rollupState"], "SWGRollupState", "SWGRollupState"); + +} + +QString +SWGDOA2Settings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDOA2Settings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_correlation_type_isSet){ + obj->insert("correlationType", QJsonValue(correlation_type)); + } + 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_log2_decim_isSet){ + obj->insert("log2Decim", QJsonValue(log2_decim)); + } + if(m_filter_chain_hash_isSet){ + obj->insert("filterChainHash", QJsonValue(filter_chain_hash)); + } + if(m_phase_isSet){ + obj->insert("phase", QJsonValue(phase)); + } + if(m_antenna_az_isSet){ + obj->insert("antennaAz", QJsonValue(antenna_az)); + } + if(m_baseband_distance_isSet){ + obj->insert("basebandDistance", QJsonValue(baseband_distance)); + } + if(m_squelchd_b_isSet){ + obj->insert("squelchdB", QJsonValue(squelchd_b)); + } + if(m_fft_averaging_value_isSet){ + obj->insert("fftAveragingValue", QJsonValue(fft_averaging_value)); + } + 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)); + } + if((scope_config != nullptr) && (scope_config->isSet())){ + toJsonValue(QString("scopeConfig"), scope_config, obj, QString("SWGGLScope")); + } + if((channel_marker != nullptr) && (channel_marker->isSet())){ + toJsonValue(QString("channelMarker"), channel_marker, obj, QString("SWGChannelMarker")); + } + if((rollup_state != nullptr) && (rollup_state->isSet())){ + toJsonValue(QString("rollupState"), rollup_state, obj, QString("SWGRollupState")); + } + + return obj; +} + +qint32 +SWGDOA2Settings::getCorrelationType() { + return correlation_type; +} +void +SWGDOA2Settings::setCorrelationType(qint32 correlation_type) { + this->correlation_type = correlation_type; + this->m_correlation_type_isSet = true; +} + +qint32 +SWGDOA2Settings::getRgbColor() { + return rgb_color; +} +void +SWGDOA2Settings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGDOA2Settings::getTitle() { + return title; +} +void +SWGDOA2Settings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGDOA2Settings::getLog2Decim() { + return log2_decim; +} +void +SWGDOA2Settings::setLog2Decim(qint32 log2_decim) { + this->log2_decim = log2_decim; + this->m_log2_decim_isSet = true; +} + +qint32 +SWGDOA2Settings::getFilterChainHash() { + return filter_chain_hash; +} +void +SWGDOA2Settings::setFilterChainHash(qint32 filter_chain_hash) { + this->filter_chain_hash = filter_chain_hash; + this->m_filter_chain_hash_isSet = true; +} + +qint32 +SWGDOA2Settings::getPhase() { + return phase; +} +void +SWGDOA2Settings::setPhase(qint32 phase) { + this->phase = phase; + this->m_phase_isSet = true; +} + +qint32 +SWGDOA2Settings::getAntennaAz() { + return antenna_az; +} +void +SWGDOA2Settings::setAntennaAz(qint32 antenna_az) { + this->antenna_az = antenna_az; + this->m_antenna_az_isSet = true; +} + +qint32 +SWGDOA2Settings::getBasebandDistance() { + return baseband_distance; +} +void +SWGDOA2Settings::setBasebandDistance(qint32 baseband_distance) { + this->baseband_distance = baseband_distance; + this->m_baseband_distance_isSet = true; +} + +qint32 +SWGDOA2Settings::getSquelchdB() { + return squelchd_b; +} +void +SWGDOA2Settings::setSquelchdB(qint32 squelchd_b) { + this->squelchd_b = squelchd_b; + this->m_squelchd_b_isSet = true; +} + +qint32 +SWGDOA2Settings::getFftAveragingValue() { + return fft_averaging_value; +} +void +SWGDOA2Settings::setFftAveragingValue(qint32 fft_averaging_value) { + this->fft_averaging_value = fft_averaging_value; + this->m_fft_averaging_value_isSet = true; +} + +qint32 +SWGDOA2Settings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGDOA2Settings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGDOA2Settings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGDOA2Settings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGDOA2Settings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGDOA2Settings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGDOA2Settings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGDOA2Settings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGDOA2Settings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGDOA2Settings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + +SWGGLScope* +SWGDOA2Settings::getScopeConfig() { + return scope_config; +} +void +SWGDOA2Settings::setScopeConfig(SWGGLScope* scope_config) { + this->scope_config = scope_config; + this->m_scope_config_isSet = true; +} + +SWGChannelMarker* +SWGDOA2Settings::getChannelMarker() { + return channel_marker; +} +void +SWGDOA2Settings::setChannelMarker(SWGChannelMarker* channel_marker) { + this->channel_marker = channel_marker; + this->m_channel_marker_isSet = true; +} + +SWGRollupState* +SWGDOA2Settings::getRollupState() { + return rollup_state; +} +void +SWGDOA2Settings::setRollupState(SWGRollupState* rollup_state) { + this->rollup_state = rollup_state; + this->m_rollup_state_isSet = true; +} + + +bool +SWGDOA2Settings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_correlation_type_isSet){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_log2_decim_isSet){ + isObjectUpdated = true; break; + } + if(m_filter_chain_hash_isSet){ + isObjectUpdated = true; break; + } + if(m_phase_isSet){ + isObjectUpdated = true; break; + } + if(m_antenna_az_isSet){ + isObjectUpdated = true; break; + } + if(m_baseband_distance_isSet){ + isObjectUpdated = true; break; + } + if(m_squelchd_b_isSet){ + isObjectUpdated = true; break; + } + if(m_fft_averaging_value_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; + } + if(scope_config && scope_config->isSet()){ + isObjectUpdated = true; break; + } + if(channel_marker && channel_marker->isSet()){ + isObjectUpdated = true; break; + } + if(rollup_state && rollup_state->isSet()){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.h b/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.h new file mode 100644 index 000000000..b6718d872 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDOA2Settings.h @@ -0,0 +1,164 @@ +/** + * 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: 7.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. + */ + +/* + * SWGDOA2Settings.h + * + * DOA2 + */ + +#ifndef SWGDOA2Settings_H_ +#define SWGDOA2Settings_H_ + +#include + + +#include "SWGChannelMarker.h" +#include "SWGGLScope.h" +#include "SWGRollupState.h" +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDOA2Settings: public SWGObject { +public: + SWGDOA2Settings(); + SWGDOA2Settings(QString* json); + virtual ~SWGDOA2Settings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDOA2Settings* fromJson(QString &jsonString) override; + + qint32 getCorrelationType(); + void setCorrelationType(qint32 correlation_type); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getLog2Decim(); + void setLog2Decim(qint32 log2_decim); + + qint32 getFilterChainHash(); + void setFilterChainHash(qint32 filter_chain_hash); + + qint32 getPhase(); + void setPhase(qint32 phase); + + qint32 getAntennaAz(); + void setAntennaAz(qint32 antenna_az); + + qint32 getBasebandDistance(); + void setBasebandDistance(qint32 baseband_distance); + + qint32 getSquelchdB(); + void setSquelchdB(qint32 squelchd_b); + + qint32 getFftAveragingValue(); + void setFftAveragingValue(qint32 fft_averaging_value); + + 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); + + SWGGLScope* getScopeConfig(); + void setScopeConfig(SWGGLScope* scope_config); + + SWGChannelMarker* getChannelMarker(); + void setChannelMarker(SWGChannelMarker* channel_marker); + + SWGRollupState* getRollupState(); + void setRollupState(SWGRollupState* rollup_state); + + + virtual bool isSet() override; + +private: + qint32 correlation_type; + bool m_correlation_type_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + qint32 log2_decim; + bool m_log2_decim_isSet; + + qint32 filter_chain_hash; + bool m_filter_chain_hash_isSet; + + qint32 phase; + bool m_phase_isSet; + + qint32 antenna_az; + bool m_antenna_az_isSet; + + qint32 baseband_distance; + bool m_baseband_distance_isSet; + + qint32 squelchd_b; + bool m_squelchd_b_isSet; + + qint32 fft_averaging_value; + bool m_fft_averaging_value_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; + + SWGGLScope* scope_config; + bool m_scope_config_isSet; + + SWGChannelMarker* channel_marker; + bool m_channel_marker_isSet; + + SWGRollupState* rollup_state; + bool m_rollup_state_isSet; + +}; + +} + +#endif /* SWGDOA2Settings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index cf694db10..d6891d140 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -95,6 +95,8 @@ #include "SWGDATVDemodSettings.h" #include "SWGDATVModReport.h" #include "SWGDATVModSettings.h" +#include "SWGDOA2Report.h" +#include "SWGDOA2Settings.h" #include "SWGDSDDemodReport.h" #include "SWGDSDDemodSettings.h" #include "SWGDVSerialDevice.h" @@ -738,6 +740,16 @@ namespace SWGSDRangel { obj->init(); return obj; } + if(QString("SWGDOA2Report").compare(type) == 0) { + SWGDOA2Report *obj = new SWGDOA2Report(); + obj->init(); + return obj; + } + if(QString("SWGDOA2Settings").compare(type) == 0) { + SWGDOA2Settings *obj = new SWGDOA2Settings(); + obj->init(); + return obj; + } if(QString("SWGDSDDemodReport").compare(type) == 0) { SWGDSDDemodReport *obj = new SWGDSDDemodReport(); obj->init();