From 1d72798d42e2506fba62fa5e8dba3a3d1ffb44fa Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 24 May 2022 15:18:55 +0200 Subject: [PATCH] AMBE feature: creation and changes to DSD demod --- plugins/channelrx/demoddsd/dsddemod.cpp | 111 +++++ plugins/channelrx/demoddsd/dsddemod.h | 37 ++ plugins/channelrx/demoddsd/dsddemodbaseband.h | 1 + plugins/channelrx/demoddsd/dsddemodgui.cpp | 59 +++ plugins/channelrx/demoddsd/dsddemodgui.h | 4 + plugins/channelrx/demoddsd/dsddemodgui.ui | 23 + plugins/channelrx/demoddsd/dsddemodplugin.cpp | 2 +- .../channelrx/demoddsd/dsddemodsettings.cpp | 6 + plugins/channelrx/demoddsd/dsddemodsettings.h | 13 + plugins/channelrx/demoddsd/dsddemodsink.cpp | 38 +- plugins/channelrx/demoddsd/dsddemodsink.h | 2 + plugins/feature/CMakeLists.txt | 4 + plugins/feature/ambe/CMakeLists.txt | 65 +++ plugins/feature/ambe/ambe.cpp | 170 ++++++++ plugins/feature/ambe/ambe.h | 92 ++++ plugins/feature/ambe/ambeengine.cpp | 402 ++++++++++++++++++ plugins/feature/ambe/ambeengine.h | 93 ++++ plugins/feature/ambe/ambegui.cpp | 343 +++++++++++++++ plugins/feature/ambe/ambegui.h | 92 ++++ plugins/feature/ambe/ambegui.ui | 285 +++++++++++++ plugins/feature/ambe/ambeplugin.cpp | 80 ++++ plugins/feature/ambe/ambeplugin.h | 48 +++ plugins/feature/ambe/ambesettings.cpp | 114 +++++ plugins/feature/ambe/ambesettings.h | 46 ++ plugins/feature/ambe/ambeworker.cpp | 232 ++++++++++ plugins/feature/ambe/ambeworker.h | 156 +++++++ sdrbase/dsp/dspcommands.cpp | 1 + sdrbase/dsp/dspcommands.h | 41 ++ 28 files changed, 2553 insertions(+), 7 deletions(-) create mode 100644 plugins/feature/ambe/CMakeLists.txt create mode 100644 plugins/feature/ambe/ambe.cpp create mode 100644 plugins/feature/ambe/ambe.h create mode 100644 plugins/feature/ambe/ambeengine.cpp create mode 100644 plugins/feature/ambe/ambeengine.h create mode 100644 plugins/feature/ambe/ambegui.cpp create mode 100644 plugins/feature/ambe/ambegui.h create mode 100644 plugins/feature/ambe/ambegui.ui create mode 100644 plugins/feature/ambe/ambeplugin.cpp create mode 100644 plugins/feature/ambe/ambeplugin.h create mode 100644 plugins/feature/ambe/ambesettings.cpp create mode 100644 plugins/feature/ambe/ambesettings.h create mode 100644 plugins/feature/ambe/ambeworker.cpp create mode 100644 plugins/feature/ambe/ambeworker.h diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index 00307028b..980786713 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -39,6 +39,7 @@ #include "dsp/dspcommands.h" #include "device/deviceapi.h" #include "feature/feature.h" +#include "feature/featureset.h" #include "settings/serializable.h" #include "util/db.h" #include "maincore.h" @@ -46,6 +47,8 @@ #include "dsddemod.h" MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message) +MESSAGE_CLASS_DEFINITION(DSDDemod::MsgQueryAvailableAMBEFeatures, Message) +MESSAGE_CLASS_DEFINITION(DSDDemod::MsgReportAvailableAMBEFeatures, Message) const char* const DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod"; const char* const DSDDemod::m_channelId = "DSDDemod"; @@ -82,6 +85,20 @@ DSDDemod::DSDDemod(DeviceAPI *deviceAPI) : this, &DSDDemod::handleIndexInDeviceSetChanged ); + QObject::connect( + MainCore::instance(), + &MainCore::featureAdded, + this, + &DSDDemod::handleFeatureAdded + ); + QObject::connect( + MainCore::instance(), + &MainCore::featureRemoved, + this, + &DSDDemod::handleFeatureRemoved + ); + + scanAvailableAMBEFeatures(); } DSDDemod::~DSDDemod() @@ -176,6 +193,11 @@ bool DSDDemod::handleMessage(const Message& cmd) return true; } + else if (MsgQueryAvailableAMBEFeatures::match(cmd)) + { + notifyUpdateAMBEFeatures(); + return true; + } else { return false; @@ -219,6 +241,8 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) << " m_traceStroke: " << settings.m_traceStroke << " m_traceDecay: " << settings.m_traceDecay << " m_streamIndex: " << settings.m_streamIndex + << " m_ambeFeatureIndex: " << settings.m_ambeFeatureIndex + << " m_connectAMBE: " << settings.m_connectAMBE << " force: " << force; QList reverseAPIKeys; @@ -280,6 +304,30 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { reverseAPIKeys.append("audioDeviceName"); } + if ((settings.m_ambeFeatureIndex != m_settings.m_ambeFeatureIndex) || force) { + reverseAPIKeys.append("ambeFeatureIndex"); + } + if ((settings.m_connectAMBE != m_settings.m_connectAMBE) || force) { + reverseAPIKeys.append("connectAMBE"); + } + + if ((m_settings.m_connectAMBE != settings.m_connectAMBE) + || (m_settings.m_ambeFeatureIndex != settings.m_ambeFeatureIndex) || force) + { + if (settings.m_connectAMBE) + { + for (const auto& feature : m_availableAMBEFeatures) + { + if (feature.m_featureIndex == settings.m_ambeFeatureIndex) { + m_basebandSink->setAMBEFeatureMessageQueue(feature.m_feature->getInputMessageQueue()); + } + } + } + else + { + m_basebandSink->setAMBEFeatureMessageQueue(nullptr); + } + } if (m_settings.m_streamIndex != settings.m_streamIndex) { @@ -339,6 +387,32 @@ bool DSDDemod::deserialize(const QByteArray& data) } } +void DSDDemod::scanAvailableAMBEFeatures() +{ + MainCore *mainCore = MainCore::instance(); + int nbFeatures = mainCore->getFeatureeSets()[0]->getNumberOfFeatures(); + m_availableAMBEFeatures.clear(); + + for (int i = 0; i < nbFeatures; i++) + { + Feature *feature = mainCore->getFeatureeSets()[0]->getFeatureAt(i); + + if (feature->getURI() == "sdrangel.feature.ambe") { + m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{i, feature}; + } + } +} + +void DSDDemod::notifyUpdateAMBEFeatures() +{ + if (getMessageQueueToGUI()) + { + MsgReportAvailableAMBEFeatures *msg = MsgReportAvailableAMBEFeatures::create(); + msg->getFeatures() = m_availableAMBEFeatures.values(); + getMessageQueueToGUI()->push(msg); + } +} + void DSDDemod::sendSampleRateToDemodAnalyzer() { QList pipes; @@ -794,3 +868,40 @@ void DSDDemod::handleIndexInDeviceSetChanged(int index) m_basebandSink->setFifoLabel(fifoLabel); m_basebandSink->setAudioFifoLabel(fifoLabel); } + +void DSDDemod::handleFeatureAdded(int featureSetIndex, Feature *feature) +{ + if (featureSetIndex != 0) { + return; + } + + if ((feature->getURI() == "sdrangel.feature.ambe") && !m_availableAMBEFeatures.contains(feature)) + { + m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{feature->getIndexInFeatureSet(), feature}; + + if (m_settings.m_connectAMBE && (m_settings.m_ambeFeatureIndex == feature->getIndexInFeatureSet())) { + m_basebandSink->setAMBEFeatureMessageQueue(feature->getInputMessageQueue()); + } + + notifyUpdateAMBEFeatures(); + } +} + +void DSDDemod::handleFeatureRemoved(int featureSetIndex, Feature *feature) +{ + if (featureSetIndex != 0) { + return; + } + + if (m_availableAMBEFeatures.contains(feature)) + { + if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[feature].m_featureIndex) + { + m_settings.m_connectAMBE = false; + m_basebandSink->setAMBEFeatureMessageQueue(nullptr); + } + + m_availableAMBEFeatures.remove(feature); + notifyUpdateAMBEFeatures(); + } +} diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index dd5584d1d..147d10143 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -62,6 +62,38 @@ public: { } }; + class MsgQueryAvailableAMBEFeatures : public Message { + MESSAGE_CLASS_DECLARATION + + public: + static MsgQueryAvailableAMBEFeatures* create() { + return new MsgQueryAvailableAMBEFeatures(); + } + + protected: + MsgQueryAvailableAMBEFeatures() : + Message() + { } + }; + + class MsgReportAvailableAMBEFeatures : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QList& getFeatures() { return m_availableFeatures; } + + static MsgReportAvailableAMBEFeatures* create() { + return new MsgReportAvailableAMBEFeatures(); + } + + private: + QList m_availableFeatures; + + MsgReportAvailableAMBEFeatures() : + Message() + {} + }; + DSDDemod(DeviceAPI *deviceAPI); virtual ~DSDDemod(); virtual void destroy() { delete this; } @@ -140,6 +172,7 @@ private: DSDDemodBaseband *m_basebandSink; DSDDemodSettings m_settings; int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + QHash m_availableAMBEFeatures; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; @@ -148,6 +181,8 @@ private: virtual bool handleMessage(const Message& cmd); void applySettings(const DSDDemodSettings& settings, bool force = false); + void scanAvailableAMBEFeatures(); + void notifyUpdateAMBEFeatures(); void sendSampleRateToDemodAnalyzer(); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const DSDDemodSettings& settings, bool force); @@ -167,6 +202,8 @@ private: private slots: void networkManagerFinished(QNetworkReply *reply); void handleIndexInDeviceSetChanged(int index); + void handleFeatureAdded(int featureSetIndex, Feature *feature); + void handleFeatureRemoved(int featureSetIndex, Feature *feature); }; #endif // INCLUDE_DSDDEMOD_H diff --git a/plugins/channelrx/demoddsd/dsddemodbaseband.h b/plugins/channelrx/demoddsd/dsddemodbaseband.h index 13f54448c..e96df9e97 100644 --- a/plugins/channelrx/demoddsd/dsddemodbaseband.h +++ b/plugins/channelrx/demoddsd/dsddemodbaseband.h @@ -75,6 +75,7 @@ public: void setChannel(ChannelAPI *channel); void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); } void setAudioFifoLabel(const QString& label) { m_sink.setAudioFifoLabel(label); } + void setAMBEFeatureMessageQueue(MessageQueue *ambeFeatureMessageQueue) { m_sink.setAmbeFeatureMessageQueue(ambeFeatureMessageQueue); } private: SampleSinkFifo m_sampleFifo; diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index fc256d618..9dfa7490d 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -113,6 +113,13 @@ bool DSDDemodGUI::handleMessage(const Message& message) updateAbsoluteCenterFrequency(); return true; } + else if (DSDDemod::MsgReportAvailableAMBEFeatures::match(message)) + { + DSDDemod::MsgReportAvailableAMBEFeatures& report = (DSDDemod::MsgReportAvailableAMBEFeatures&) message; + m_availableAMBEFeatures = report.getFeatures(); + updateAMBEFeaturesList(); + return true; + } else { return false; @@ -263,6 +270,19 @@ void DSDDemodGUI::on_symbolPLLLock_toggled(bool checked) applySettings(); } +void DSDDemodGUI::on_ambeSupport_clicked(bool checked) +{ + m_settings.m_connectAMBE = checked; + m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[ui->ambeFeatures->currentIndex()].m_featureIndex; + applySettings(); +} + +void DSDDemodGUI::on_ambeFeatures_currentIndexChanged(int index) +{ + m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[index].m_featureIndex; + applySettings(); +} + void DSDDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -490,6 +510,17 @@ void DSDDemodGUI::displaySettings() ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay)); m_scopeVisXY->setDecay(m_settings.m_traceDecay); + ui->ambeSupport->setChecked(m_settings.m_connectAMBE); + + for (int i = 0; i < ui->ambeFeatures->count(); i++) + { + if (ui->ambeFeatures->itemData(i).toInt() == m_settings.m_ambeFeatureIndex) + { + ui->ambeFeatures->setCurrentIndex(i); + break; + } + } + updateIndexLabel(); getRollupContents()->restoreState(m_rollupState); @@ -497,6 +528,32 @@ void DSDDemodGUI::displaySettings() blockApplySettings(false); } +void DSDDemodGUI::updateAMBEFeaturesList() +{ + ui->ambeFeatures->blockSignals(true); + ui->ambeSupport->blockSignals(true); + ui->ambeFeatures->clear(); + bool unsetAMBE = true; + + for (int i = 0; i < m_availableAMBEFeatures.count(); i++) + { + ui->ambeFeatures->addItem(tr("F:%1").arg(m_availableAMBEFeatures[i].m_featureIndex), m_availableAMBEFeatures[i].m_featureIndex); + + if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[i].m_featureIndex) + { + unsetAMBE = false; + ui->ambeFeatures->setCurrentIndex(i); + } + } + + if (unsetAMBE) { + ui->ambeSupport->setChecked(false); + } + + ui->ambeSupport->blockSignals(false); + ui->ambeFeatures->blockSignals(false); +} + void DSDDemodGUI::applySettings(bool force) { if (m_doApplySettings) @@ -658,6 +715,8 @@ void DSDDemodGUI::makeUIConnections() QObject::connect(ui->audioMute, &QToolButton::toggled, this, &DSDDemodGUI::on_audioMute_toggled); QObject::connect(ui->symbolPLLLock, &QToolButton::toggled, this, &DSDDemodGUI::on_symbolPLLLock_toggled); QObject::connect(ui->viewStatusLog, &QPushButton::clicked, this, &DSDDemodGUI::on_viewStatusLog_clicked); + QObject::connect(ui->ambeSupport, &QCheckBox::clicked, this, &DSDDemodGUI::on_ambeSupport_clicked); + QObject::connect(ui->ambeFeatures, QOverload::of(&QComboBox::currentIndexChanged), this, &DSDDemodGUI::on_ambeFeatures_currentIndexChanged); } void DSDDemodGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channelrx/demoddsd/dsddemodgui.h b/plugins/channelrx/demoddsd/dsddemodgui.h index e5de1b302..c841fd342 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.h +++ b/plugins/channelrx/demoddsd/dsddemodgui.h @@ -90,6 +90,7 @@ private: qint64 m_deviceCenterFrequency; int m_basebandSampleRate; bool m_doApplySettings; + QList m_availableAMBEFeatures; ScopeVisXY* m_scopeVisXY; @@ -117,6 +118,7 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); + void updateAMBEFeaturesList(); void updateMyPosition(); bool handleMessage(const Message& message); void makeUIConnections(); @@ -145,6 +147,8 @@ private slots: void on_highPassFilter_toggled(bool checked); void on_audioMute_toggled(bool checked); void on_symbolPLLLock_toggled(bool checked); + void on_ambeSupport_clicked(bool checked); + void on_ambeFeatures_currentIndexChanged(int index); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void on_viewStatusLog_clicked(); diff --git a/plugins/channelrx/demoddsd/dsddemodgui.ui b/plugins/channelrx/demoddsd/dsddemodgui.ui index 781d39d68..0403c7743 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.ui +++ b/plugins/channelrx/demoddsd/dsddemodgui.ui @@ -1231,6 +1231,29 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 10 + 170 + 71 + 23 + + + + AMBE + + + + + + 80 + 170 + 50 + 20 + + + diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp index 17f2485f9..903d7348a 100644 --- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp +++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp @@ -30,7 +30,7 @@ const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = { DSDDemod::m_channelId, QStringLiteral("DSD Demodulator"), - QStringLiteral("7.0.0"), + QStringLiteral("7.2.0"), QStringLiteral("(c) Edouard Griffiths, F4EXB"), QStringLiteral("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.cpp b/plugins/channelrx/demoddsd/dsddemodsettings.cpp index ed7a6394f..73aaadda1 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsettings.cpp @@ -61,6 +61,8 @@ void DSDDemodSettings::resetToDefaults() m_reverseAPIChannelIndex = 0; m_workspaceIndex = 0; m_hidden = false; + m_ambeFeatureIndex = -1; + m_connectAMBE = false; } QByteArray DSDDemodSettings::serialize() const @@ -107,6 +109,8 @@ QByteArray DSDDemodSettings::serialize() const s.writeS32(32, m_workspaceIndex); s.writeBlob(33, m_geometryBytes); s.writeBool(34, m_hidden); + s.writeS32(35, m_ambeFeatureIndex); + s.writeBool(36, m_connectAMBE); return s.final(); } @@ -190,6 +194,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data) d.readS32(32, &m_workspaceIndex, 0); d.readBlob(33, &m_geometryBytes); d.readBool(34, &m_hidden, false); + d.readS32(35, &m_ambeFeatureIndex, -1); + d.readBool(36, &m_connectAMBE, false); return true; } diff --git a/plugins/channelrx/demoddsd/dsddemodsettings.h b/plugins/channelrx/demoddsd/dsddemodsettings.h index 3339be650..e4d22c30e 100644 --- a/plugins/channelrx/demoddsd/dsddemodsettings.h +++ b/plugins/channelrx/demoddsd/dsddemodsettings.h @@ -21,9 +21,20 @@ #include class Serializable; +class Feature; struct DSDDemodSettings { + struct AvailableAMBEFeature + { + int m_featureIndex; + Feature *m_feature; + + AvailableAMBEFeature() = default; + AvailableAMBEFeature(const AvailableAMBEFeature&) = default; + AvailableAMBEFeature& operator=(const AvailableAMBEFeature&) = default; + }; + qint64 m_inputFrequencyOffset; Real m_rfBandwidth; Real m_fmDeviation; @@ -55,6 +66,8 @@ struct DSDDemodSettings int m_workspaceIndex; QByteArray m_geometryBytes; bool m_hidden; + int m_ambeFeatureIndex; + bool m_connectAMBE; Serializable *m_channelMarker; Serializable *m_rollupState; diff --git a/plugins/channelrx/demoddsd/dsddemodsink.cpp b/plugins/channelrx/demoddsd/dsddemodsink.cpp index b1d20df72..e91af3932 100644 --- a/plugins/channelrx/demoddsd/dsddemodsink.cpp +++ b/plugins/channelrx/demoddsd/dsddemodsink.cpp @@ -34,6 +34,7 @@ #include "dsp/dspengine.h" #include "dsp/basebandsamplesink.h" #include "dsp/datafifo.h" +#include "dsp/dspcommands.h" #include "audio/audiooutputdevice.h" #include "util/db.h" #include "util/messagequeue.h" @@ -44,6 +45,7 @@ DSDDemodSink::DSDDemodSink() : m_channelSampleRate(48000), m_channelFrequencyOffset(0), + m_ambeFeatureMessageQueue(nullptr), m_audioSampleRate(48000), m_interpolatorDistance(0.0f), m_interpolatorDistanceRemain(0.0f), @@ -229,20 +231,32 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV m_scopeSampleBuffer.push_back(s); } - if (DSPEngine::instance()->hasDVSerialSupport()) + // if (DSPEngine::instance()->hasDVSerialSupport()) + if (m_ambeFeatureMessageQueue) { if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1()) { if (!m_settings.m_audioMute) { - DSPEngine::instance()->pushMbeFrame( + m_ambeFeatureMessageQueue->push( + new DSPPushMbeFrame( m_dsdDecoder.getMbeDVFrame1(), m_dsdDecoder.getMbeRateIndex(), m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 1 : 3, // left or both channels m_settings.m_highPassFilter, m_audioSampleRate/8000, // upsample from native 8k - &m_audioFifo1); + &m_audioFifo1 + ) + ); + // DSPEngine::instance()->pushMbeFrame( + // m_dsdDecoder.getMbeDVFrame1(), + // m_dsdDecoder.getMbeRateIndex(), + // m_settings.m_volume * 10.0, + // m_settings.m_tdmaStereo ? 1 : 3, // left or both channels + // m_settings.m_highPassFilter, + // m_audioSampleRate/8000, // upsample from native 8k + // &m_audioFifo1); } m_dsdDecoder.resetMbeDV1(); @@ -252,14 +266,25 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV { if (!m_settings.m_audioMute) { - DSPEngine::instance()->pushMbeFrame( + m_ambeFeatureMessageQueue->push( + new DSPPushMbeFrame( m_dsdDecoder.getMbeDVFrame2(), m_dsdDecoder.getMbeRateIndex(), m_settings.m_volume * 10.0, m_settings.m_tdmaStereo ? 2 : 3, // right or both channels m_settings.m_highPassFilter, m_audioSampleRate/8000, // upsample from native 8k - &m_audioFifo2); + &m_audioFifo2 + ) + ); + // DSPEngine::instance()->pushMbeFrame( + // m_dsdDecoder.getMbeDVFrame2(), + // m_dsdDecoder.getMbeRateIndex(), + // m_settings.m_volume * 10.0, + // m_settings.m_tdmaStereo ? 2 : 3, // right or both channels + // m_settings.m_highPassFilter, + // m_audioSampleRate/8000, // upsample from native 8k + // &m_audioFifo2); } m_dsdDecoder.resetMbeDV2(); @@ -270,7 +295,8 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV } } - if (!DSPEngine::instance()->hasDVSerialSupport()) + if (!m_ambeFeatureMessageQueue) + // if (!DSPEngine::instance()->hasDVSerialSupport()) { if (m_settings.m_slot1On) { diff --git a/plugins/channelrx/demoddsd/dsddemodsink.h b/plugins/channelrx/demoddsd/dsddemodsink.h index 3279d553d..dd4dbdfc5 100644 --- a/plugins/channelrx/demoddsd/dsddemodsink.h +++ b/plugins/channelrx/demoddsd/dsddemodsink.h @@ -83,6 +83,7 @@ public: } const char *updateAndGetStatusText(); + void setAmbeFeatureMessageQueue(MessageQueue *queue) { m_ambeFeatureMessageQueue = queue; } private: struct MagSqLevelsStore @@ -114,6 +115,7 @@ private: int m_channelFrequencyOffset; DSDDemodSettings m_settings; ChannelAPI *m_channel; + MessageQueue *m_ambeFeatureMessageQueue; int m_audioSampleRate; QVector m_demodBuffer; int m_demodBufferFill; diff --git a/plugins/feature/CMakeLists.txt b/plugins/feature/CMakeLists.txt index ac1b031d5..4a79484d5 100644 --- a/plugins/feature/CMakeLists.txt +++ b/plugins/feature/CMakeLists.txt @@ -39,3 +39,7 @@ endif() if (ENABLE_LIMESUITE AND LIMESUITE_FOUND) add_subdirectory(limerfe) endif() + +if (LIBSERIALDV_FOUND) + add_subdirectory(ambe) +endif() diff --git a/plugins/feature/ambe/CMakeLists.txt b/plugins/feature/ambe/CMakeLists.txt new file mode 100644 index 000000000..d243b2633 --- /dev/null +++ b/plugins/feature/ambe/CMakeLists.txt @@ -0,0 +1,65 @@ +project(ambe) + +set(ambe_SOURCES + ambe.cpp + ambesettings.cpp + ambeplugin.cpp + ambeengine.cpp + ambeworker.cpp + # ambewebapiadapter.cpp +) + +set(ambe_HEADERS + ambe.h + ambesettings.h + ambeplugin.h + ambeengine.h + ambeworker.h + # ambewebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${LIBSERIALDV_INCLUDE_DIR} +) + +if(NOT SERVER_MODE) + set(ambe_SOURCES + ${ambe_SOURCES} + ambegui.cpp + ambegui.ui + ) + set(ambe_HEADERS + ${ambe_HEADERS} + ambegui.h + ) + + set(TARGET_NAME featureambe) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME featureambesrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${ambe_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + ${LIBSERIALDV_LIBRARY} +) + +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/feature/ambe/ambe.cpp b/plugins/feature/ambe/ambe.cpp new file mode 100644 index 000000000..5a6bb90b4 --- /dev/null +++ b/plugins/feature/ambe/ambe.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "SWGFeatureSettings.h" +#include "SWGFeatureReport.h" +#include "SWGFeatureActions.h" + +#include "settings/serializable.h" +#include "util/simpleserializer.h" +#include "dsp/dspcommands.h" + +#include "ambe.h" + +MESSAGE_CLASS_DEFINITION(AMBE::MsgConfigureAMBE, Message) + +const char* const AMBE::m_featureIdURI = "sdrangel.feature.ambe"; +const char* const AMBE::m_featureId = "AMBE"; + +AMBE::AMBE(WebAPIAdapterInterface *webAPIAdapterInterface) : + Feature(m_featureIdURI, webAPIAdapterInterface) +{ + setObjectName(m_featureId); + m_state = StIdle; + m_errorMessage = "AMBE error"; + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AMBE::networkManagerFinished + ); +} + +AMBE::~AMBE() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AMBE::networkManagerFinished + ); + delete m_networkManager; +} + +void AMBE::start() +{ + qDebug("AMBE::start"); + m_state = StRunning; +} + +void AMBE::stop() +{ + qDebug("AMBE::stop"); + m_state = StIdle; +} + +void AMBE::applySettings(const AMBESettings& settings, bool force) +{ + (void) force; + m_settings = settings; +} + +bool AMBE::handleMessage(const Message& cmd) +{ + if (MsgConfigureAMBE::match(cmd)) + { + MsgConfigureAMBE& cfg = (MsgConfigureAMBE&) cmd; + qDebug() << "AMBE::handleMessage: MsgConfigureAMBE"; + applySettings(cfg.getSettings(), cfg.getForce()); + return true; + } + else if (DSPPushMbeFrame::match(cmd)) + { + DSPPushMbeFrame& cfg = (DSPPushMbeFrame&) cmd; + m_ambeEngine.pushMbeFrame( + cfg.getMbeFrame(), + cfg.getMbeRateIndex(), + cfg.getMbeVolumeIndex(), + cfg.getChannels(), + cfg.getUseHP(), + cfg.getUpsampling(), + cfg.getAudioFifo() + ); + return true; + } + + return false; +} + +QByteArray AMBE::serialize() const +{ + SimpleSerializer s(1); + s.writeBlob(1, m_settings.serialize()); + return s.final(); +} + +bool AMBE::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + m_settings.resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + QByteArray bytetmp; + d.readBlob(1, &bytetmp); + + if (m_settings.deserialize(bytetmp)) + { + MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } + } + else + { + return false; + } +} + +void AMBE::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "AMBE::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("AMBE::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/feature/ambe/ambe.h b/plugins/feature/ambe/ambe.h new file mode 100644 index 000000000..a456e4a34 --- /dev/null +++ b/plugins/feature/ambe/ambe.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FEATURE_AMBE_H_ +#define INCLUDE_FEATURE_AMBE_H_ + +#include +#include + +#include "feature/feature.h" +#include "util/message.h" + +#include "ambeengine.h" +#include "ambesettings.h" + +class WebAPIAdapterInterface; +class QNetworkAccessManager; +class QNetworkReply; + +class AMBE : public Feature +{ + Q_OBJECT +public: + class MsgConfigureAMBE : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AMBESettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAMBE* create(const AMBESettings& settings, bool force) { + return new MsgConfigureAMBE(settings, force); + } + + private: + AMBESettings m_settings; + bool m_force; + + MsgConfigureAMBE(const AMBESettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + AMBE(WebAPIAdapterInterface *webAPIAdapterInterface); + virtual ~AMBE(); + virtual void destroy() { delete this; } + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) const { id = objectName(); } + virtual QString getIdentifier() const { return objectName(); } + virtual void getTitle(QString& title) const { title = m_settings.m_title; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + AMBEEngine *getAMBEEngine() { return &m_ambeEngine; } + + static const char* const m_featureIdURI; + static const char* const m_featureId; + +private: + AMBESettings m_settings; + AMBEEngine m_ambeEngine; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void start(); + void stop(); + void applySettings(const AMBESettings& settings, bool force = false); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif diff --git a/plugins/feature/ambe/ambeengine.cpp b/plugins/feature/ambe/ambeengine.cpp new file mode 100644 index 000000000..bc16a6c07 --- /dev/null +++ b/plugins/feature/ambe/ambeengine.cpp @@ -0,0 +1,402 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 __APPLE__ +#include +#include +#endif + +#ifndef _MSC_VER +#include +#include +#include +#endif + +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#include +#include +#endif + +#include +#include + +#include +#include +#include + +#include "ambeworker.h" +#include "ambeengine.h" + +AMBEEngine::AMBEEngine() +{} + +AMBEEngine::~AMBEEngine() +{ + qDebug("AMBEEngine::~AMBEEngine: %lu controllers", m_controllers.size()); +} + +#if defined(_WIN32) +void AMBEEngine::getComList() +{ + m_comList.clear(); + m_comList8250.clear(); + char comCStr[16]; + + // Arbitrarily set the list to the 20 first COM ports + for (int i = 1; i <= 20; i++) + { + sprintf(comCStr, "COM%d", i); + m_comList.push_back(std::string(comCStr)); + } +} + +// Do not activate serial support at all for windows +void AMBEEngine::scan(std::vector& ambeDevices) +{ + (void) ambeDevices; +} +#elif defined(__APPLE__) +void AMBEEngine::getComList() +{ +} +void AMBEEngine::scan(std::vector& ambeDevices) +{ + (void) ambeDevices; +} +#else +void AMBEEngine::getComList() +{ + int n; + struct dirent **namelist; + m_comList.clear(); + m_comList8250.clear(); + const char* sysdir = "/sys/class/tty/"; + + // Scan through /sys/class/tty - it contains all tty-devices in the system + n = scandir(sysdir, &namelist, NULL, alphasort); + if (n < 0) + { + perror("scandir"); + } + else + { + while (n--) + { + if (strcmp(namelist[n]->d_name, "..") && strcmp(namelist[n]->d_name, ".")) + { + // Construct full absolute file path + std::string devicedir = sysdir; + devicedir += namelist[n]->d_name; + // Register the device + register_comport(m_comList, m_comList8250, devicedir); + } + + free(namelist[n]); + } + + free(namelist); + } + + // Only non-serial8250 has been added to comList without any further testing + // serial8250-devices must be probe to check for validity + probe_serial8250_comports(m_comList, m_comList8250); +} +#endif // not Windows nor Apple + +#if !defined(_WIN32) && !defined(__APPLE__) +void AMBEEngine::register_comport( + std::vector& comList, + std::vector& comList8250, + const std::string& dir) +{ + // Get the driver the device is using + std::string driver = get_driver(dir); + + // Skip devices without a driver + if (driver.size() > 0) + { + //std::cerr << "register_comport: dir: "<< dir << " driver: " << driver << std::endl; + std::string devfile = std::string("/dev/") + basename((char *) dir.c_str()); + + // Put serial8250-devices in a seperate list + if (driver == "serial8250") { + comList8250.push_back(devfile); + } else { + comList.push_back(devfile); + } + } +} + +void AMBEEngine::probe_serial8250_comports( + std::vector& comList, + std::vector comList8250) +{ + struct serial_struct serinfo; + std::vector::iterator it = comList8250.begin(); + + // Iterate over all serial8250-devices + while (it != comList8250.end()) + { + + // Try to open the device + int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY); + + if (fd >= 0) + { + // Get serial_info + if (ioctl(fd, TIOCGSERIAL, &serinfo) == 0) + { + // If device type is no PORT_UNKNOWN we accept the port + if (serinfo.type != PORT_UNKNOWN) { + comList.push_back(*it); + } + } + + close(fd); + } + it++; + } +} + +std::string AMBEEngine::get_driver(const std::string& tty) +{ + struct stat st; + std::string devicedir = tty; + // Append '/device' to the tty-path + devicedir += "/device"; + + // Stat the devicedir and handle it if it is a symlink + if (lstat(devicedir.c_str(), &st) == 0 && S_ISLNK(st.st_mode)) + { + char buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + // Append '/driver' and return basename of the target + devicedir += "/driver"; + + if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0) { + return basename(buffer); + } + } + + return ""; +} + +void AMBEEngine::scan(std::vector& ambeDevices) +{ + getComList(); + std::vector::const_iterator it = m_comList.begin(); + ambeDevices.clear(); + + while (it != m_comList.end()) + { + AMBEWorker *worker = new AMBEWorker(); + qDebug("AMBEEngine::scan: com: %s", it->c_str()); + + if (worker->open(*it)) + { + ambeDevices.push_back(QString(it->c_str())); + worker->close(); + } + + delete worker; + ++it; + } +} +#endif // not Windows nor Apple + +bool AMBEEngine::registerController(const std::string& deviceRef) +{ + AMBEWorker *worker = new AMBEWorker(); + + if (worker->open(deviceRef)) + { + qDebug("AMBEEngine::registerController: device: %s", deviceRef.c_str()); + m_controllers.push_back(AMBEController()); + m_controllers.back().worker = worker; + m_controllers.back().thread = new QThread(); + m_controllers.back().device = deviceRef; + + m_controllers.back().worker->moveToThread(m_controllers.back().thread); + connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().thread, SLOT(quit())); + connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().worker, SLOT(deleteLater())); + connect(m_controllers.back().thread, SIGNAL(finished()), m_controllers.back().thread, SLOT(deleteLater())); + connect(&m_controllers.back().worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), m_controllers.back().worker, SLOT(handleInputMessages())); + std::this_thread::sleep_for(std::chrono::seconds(1)); + m_controllers.back().thread->start(); + + return true; + } + + qWarning("AMBEEngine::registerController: failed to register device: %s", deviceRef.c_str()); + return false; +} + +void AMBEEngine::releaseController(const std::string& deviceRef) +{ + std::vector::iterator it = m_controllers.begin(); + + while (it != m_controllers.end()) + { + if (it->device == deviceRef) + { + disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages())); + it->worker->stop(); + it->thread->wait(100); + it->worker->m_inputMessageQueue.clear(); + it->worker->close(); + qDebug() << "AMBEEngine::releaseController: closed device at: " << it->device.c_str(); + m_controllers.erase(it); + break; + } + + ++it; + } +} + +void AMBEEngine::releaseAll() +{ + std::vector::iterator it = m_controllers.begin(); + + while (it != m_controllers.end()) + { + disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages())); + it->worker->stop(); + it->thread->wait(100); + it->worker->m_inputMessageQueue.clear(); + it->worker->close(); + qDebug() << "AMBEEngine::release: closed device at: " << it->device.c_str(); + ++it; + } + + m_controllers.clear(); +} + +void AMBEEngine::getDeviceRefs(std::vector& deviceNames) +{ + std::vector::const_iterator it = m_controllers.begin(); + + while (it != m_controllers.end()) + { + deviceNames.push_back(QString(it->device.c_str())); + ++it; + } +} + +void AMBEEngine::pushMbeFrame( + const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useLP, + int upsampling, + AudioFifo *audioFifo) +{ + std::vector::iterator it = m_controllers.begin(); + std::vector::iterator itAvail = m_controllers.end(); + bool done = false; + QMutexLocker locker(&m_mutex); + + while (it != m_controllers.end()) + { + if (it->worker->hasFifo(audioFifo)) + { + it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); + done = true; + } + else if (it->worker->isAvailable()) + { + itAvail = it; + } + + ++it; + } + + if (!done) + { + if (itAvail != m_controllers.end()) + { + int wNum = itAvail - m_controllers.begin(); + + qDebug("AMBEEngine::pushMbeFrame: push %p on empty queue %d", audioFifo, wNum); + itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo); + } + else + { + qDebug("AMBEEngine::pushMbeFrame: no DV device available. MBE frame dropped"); + } + } +} + +QByteArray AMBEEngine::serialize() const +{ + qDebug("AMBEEngine::serialize"); + QStringList qDeviceList; + std::vector::const_iterator it = m_controllers.begin(); + + while (it != m_controllers.end()) + { + qDebug("AMBEEngine::serialize: %s", it->device.c_str()); + qDeviceList << QString(it->device.c_str()); + ++it; + } + + QByteArray data; + QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly); + (*stream) << qDeviceList; + delete stream; + + return data; +} + +bool AMBEEngine::deserialize(const QByteArray& data) +{ + if (data.size() <= 0) + { + qDebug("AMBEEngine::deserialize: invalid or no data"); + return false; + } + + QStringList qDeviceList; + QDataStream *stream = new QDataStream(data); + (*stream) >> qDeviceList; + delete stream; + + releaseAll(); + + for (int i = 0; i < qDeviceList.size(); ++i) + { + qDebug(" AMBEEngine::deserialize: %s", qDeviceList.at(i).toStdString().c_str()); + registerController(qDeviceList.at(i).toStdString()); + } + + return true; +} + +void AMBEEngine::formatTo(SWGSDRangel::SWGObject *swgObject) const +{ + (void) swgObject; + // TODO +} + +void AMBEEngine::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject) +{ + (void) keys; + (void) swgObject; + // TODO +} diff --git a/plugins/feature/ambe/ambeengine.h b/plugins/feature/ambe/ambeengine.h new file mode 100644 index 000000000..897cefa71 --- /dev/null +++ b/plugins/feature/ambe/ambeengine.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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_AMBE_AMBEENGINE_H_ +#define SDRBASE_AMBE_AMBEENGINE_H_ + +#include +#include + +#include +#include +#include +#include + +#include "settings/serializable.h" + +class QThread; +class AMBEWorker; +class AudioFifo; + +class AMBEEngine : public QObject, public Serializable +{ + Q_OBJECT +public: + AMBEEngine(); + ~AMBEEngine(); + + void scan(std::vector& ambeDevices); + void releaseAll(); + + int getNbDevices() const { return m_controllers.size(); } //!< number of devices used + void getDeviceRefs(std::vector& devicesRefs); //!< reference of the devices used (device path or url) + bool registerController(const std::string& deviceRef); //!< create a new controller for the device in reference + void releaseController(const std::string& deviceRef); //!< release controller resources for the device in reference + + void pushMbeFrame( + const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; //!< Serialize to API + virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); //!< Deserialize from API + +private: + struct AMBEController + { + AMBEController() : + thread(nullptr), + worker(nullptr) + {} + + QThread *thread; + AMBEWorker *worker; + std::string device; + }; + +#ifndef _WIN32 + static std::string get_driver(const std::string& tty); + static void register_comport(std::vector& comList, std::vector& comList8250, const std::string& dir); + static void probe_serial8250_comports(std::vector& comList, std::vector comList8250); +#endif + void getComList(); + + std::vector m_controllers; + std::vector m_comList; + std::vector m_comList8250; + QMutex m_mutex; +}; + + + +#endif /* SDRBASE_AMBE_AMBEENGINE_H_ */ diff --git a/plugins/feature/ambe/ambegui.cpp b/plugins/feature/ambe/ambegui.cpp new file mode 100644 index 000000000..9c7eb425a --- /dev/null +++ b/plugins/feature/ambe/ambegui.cpp @@ -0,0 +1,343 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "feature/featureuiset.h" +#include "gui/basicfeaturesettingsdialog.h" +#include "gui/crightclickenabler.h" + +#include "ui_ambegui.h" +#include "ambegui.h" +#include "ambe.h" + +AMBEGUI* AMBEGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature) +{ + AMBEGUI* gui = new AMBEGUI(pluginAPI, featureUISet, feature); + return gui; +} + +void AMBEGUI::destroy() +{ + delete this; +} + +AMBEGUI::AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) : + FeatureGUI(parent), + ui(new Ui::AMBEGUI), + m_pluginAPI(pluginAPI), + m_featureUISet(featureUISet), + m_doApplySettings(true) +{ + m_feature = feature; + setAttribute(Qt::WA_DeleteOnClose, true); + m_helpURL = "plugins/feature/ambe/readme.md"; + RollupContents *rollupContents = getRollupContents(); + ui->setupUi(rollupContents); + setSizePolicy(rollupContents->sizePolicy()); + rollupContents->arrangeRollups(); + connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + + m_ambe = reinterpret_cast(feature); + m_ambe->setMessageQueueToGUI(&m_inputMessageQueue); + + m_settings.setRollupState(&m_rollupState); + + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + populateSerialList(); + refreshInUseList(); + displaySettings(); + makeUIConnections(); +} + +AMBEGUI::~AMBEGUI() +{ + delete ui; +} + +void AMBEGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray AMBEGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool AMBEGUI::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex); + displaySettings(); + refreshInUseList(); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void AMBEGUI::setWorkspaceIndex(int index) +{ + m_settings.m_workspaceIndex = index; + m_feature->setWorkspaceIndex(index); +} + +void AMBEGUI::resizeEvent(QResizeEvent* size) +{ + int maxWidth = getRollupContents()->maximumWidth(); + int minHeight = getRollupContents()->minimumHeight() + getAdditionalHeight(); + resize(width() < maxWidth ? width() : maxWidth, minHeight); + size->accept(); +} + +void AMBEGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; + + getRollupContents()->saveState(m_rollupState); + applySettings(); +} + +void AMBEGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicFeatureSettingsDialog dialog(this); + dialog.setTitle(m_settings.m_title); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex); + dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex); + dialog.setDefaultTitle(m_displayedName); + + dialog.move(p); + dialog.exec(); + + m_settings.m_title = dialog.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex(); + m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex(); + + setTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + + resetContextMenuType(); +} + +void AMBEGUI::displaySettings() +{ + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_settings.m_title); + setTitle(m_settings.m_title); +} + +void AMBEGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + AMBE::MsgConfigureAMBE* message = AMBE::MsgConfigureAMBE::create( m_settings, force); + m_ambe->getInputMessageQueue()->push(message); + } +} + +bool AMBEGUI::handleMessage(const Message& message) +{ + if (AMBE::MsgConfigureAMBE::match(message)) + { + qDebug("AMBEGUI::handleMessage: AMBE::MsgConfigureAMBE"); + const AMBE::MsgConfigureAMBE& cfg = (AMBE::MsgConfigureAMBE&) message; + m_settings = cfg.getSettings(); + displaySettings(); + return true; + } + + return false; +} + +void AMBEGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop())) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +void AMBEGUI::populateSerialList() +{ + std::vector ambeSerialDevices; + m_ambe->getAMBEEngine()->scan(ambeSerialDevices); + ui->ambeSerialDevices->clear(); + std::vector::const_iterator it = ambeSerialDevices.begin(); + + for (; it != ambeSerialDevices.end(); ++it) { + ui->ambeSerialDevices->addItem(QString(*it)); + } +} + +void AMBEGUI::refreshInUseList() +{ + std::vector inUseDevices; + m_ambe->getAMBEEngine()->getDeviceRefs(inUseDevices); + ui->ambeDeviceRefs->clear(); + std::vector::const_iterator it = inUseDevices.begin(); + + for (; it != inUseDevices.end(); ++it) + { + qDebug("AMBEGUI::refreshInUseList: %s", qPrintable(*it)); + ui->ambeDeviceRefs->addItem(*it); + } +} +void AMBEGUI::on_importSerial_clicked() +{ + QListWidgetItem *serialItem = ui->ambeSerialDevices->currentItem(); + + if (!serialItem) + { + ui->statusText->setText("No selection"); + return; + } + + QString serialName = serialItem->text(); + QList foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive); + + if (foundItems.size() == 0) + { + if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString())) + { + ui->ambeDeviceRefs->addItem(serialName); + ui->statusText->setText(tr("%1 added").arg(serialName)); + } + else + { + ui->statusText->setText(tr("Cannot open %1").arg(serialName)); + } + } + else + { + ui->statusText->setText("Device already in use"); + } +} + +void AMBEGUI::on_importAllSerial_clicked() +{ + int count = 0; + + for (int i = 0; i < ui->ambeSerialDevices->count(); i++) + { + const QListWidgetItem *serialItem = ui->ambeSerialDevices->item(i); + QString serialName = serialItem->text(); + QList foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive); + + if (foundItems.size() == 0) + { + if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString())) + { + ui->ambeDeviceRefs->addItem(serialName); + count++; + } + } + } + + ui->statusText->setText(tr("%1 devices added").arg(count)); +} + +void AMBEGUI::on_removeAmbeDevice_clicked() +{ + QListWidgetItem *deviceItem = ui->ambeDeviceRefs->currentItem(); + + if (!deviceItem) + { + ui->statusText->setText("No selection"); + return; + } + + QString deviceName = deviceItem->text(); + m_ambe->getAMBEEngine()->releaseController(deviceName.toStdString()); + ui->statusText->setText(tr("%1 removed").arg(deviceName)); + refreshInUseList(); +} + +void AMBEGUI::on_refreshAmbeList_clicked() +{ + refreshInUseList(); + ui->statusText->setText("In use refreshed"); +} + +void AMBEGUI::on_clearAmbeList_clicked() +{ + if (ui->ambeDeviceRefs->count() == 0) + { + ui->statusText->setText("No active items"); + return; + } + + m_ambe->getAMBEEngine()->releaseAll(); + ui->ambeDeviceRefs->clear(); + ui->statusText->setText("All items released"); +} + +void AMBEGUI::on_importAddress_clicked() +{ + QString addressAndPort = ui->ambeAddressText->text(); + + QList foundItems = ui->ambeDeviceRefs->findItems(addressAndPort, Qt::MatchFixedString|Qt::MatchCaseSensitive); + + if (foundItems.size() == 0) + { + if (m_ambe->getAMBEEngine()->registerController(addressAndPort.toStdString())) + { + ui->ambeDeviceRefs->addItem(addressAndPort); + ui->statusText->setText(tr("%1 added").arg(addressAndPort)); + } + else + { + ui->statusText->setText(tr("Cannot open %1").arg(addressAndPort)); + } + } + else + { + ui->statusText->setText("Address already in use"); + } +} + +void AMBEGUI::makeUIConnections() +{ + QObject::connect(ui->importSerial, &QPushButton::clicked, this, &AMBEGUI::on_importSerial_clicked); + QObject::connect(ui->importAllSerial, &QPushButton::clicked, this, &AMBEGUI::on_importAllSerial_clicked); + QObject::connect(ui->removeAmbeDevice, &QPushButton::clicked, this, &AMBEGUI::on_removeAmbeDevice_clicked); + QObject::connect(ui->refreshAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_refreshAmbeList_clicked); + QObject::connect(ui->clearAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_clearAmbeList_clicked); + QObject::connect(ui->importAddress, &QPushButton::clicked, this, &AMBEGUI::on_importAddress_clicked); +} diff --git a/plugins/feature/ambe/ambegui.h b/plugins/feature/ambe/ambegui.h new file mode 100644 index 000000000..f93dc16f2 --- /dev/null +++ b/plugins/feature/ambe/ambegui.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FEATURE_AMBEGUI_H_ +#define INCLUDE_FEATURE_AMBEGUI_H_ + +#include "feature/featuregui.h" +#include "util/messagequeue.h" +#include "util/movingaverage.h" +#include "settings/rollupstate.h" + +#include "ambesettings.h" + +class PluginAPI; +class FeatureUISet; +class Feature; +class AMBE; +class DSPDeviceSourceEngine; +class DSPDeviceSinkEngine; + +namespace Ui { + class AMBEGUI; +} + +class AMBEGUI : public FeatureGUI +{ + Q_OBJECT +public: + static AMBEGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual void setWorkspaceIndex(int 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; } + +protected: + void resizeEvent(QResizeEvent* size); + +private: + Ui::AMBEGUI* ui; + AMBE *m_ambe; + PluginAPI* m_pluginAPI; + FeatureUISet* m_featureUISet; + AMBESettings m_settings; + RollupState m_rollupState; + bool m_doApplySettings; + MessageQueue m_inputMessageQueue; + + explicit AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr); + virtual ~AMBEGUI(); + + void populateSerialList(); + void refreshInUseList(); + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void applySettings(bool force = false); + void displaySettings(); + bool handleMessage(const Message& message); + void makeUIConnections(); + +private slots: + void onMenuDialogCalled(const QPoint &p); + void onWidgetRolled(QWidget* widget, bool rollDown); + void handleInputMessages(); + void on_importSerial_clicked(); + void on_importAllSerial_clicked(); + void on_removeAmbeDevice_clicked(); + void on_refreshAmbeList_clicked(); + void on_clearAmbeList_clicked(); + void on_importAddress_clicked(); +}; + +#endif diff --git a/plugins/feature/ambe/ambegui.ui b/plugins/feature/ambe/ambegui.ui new file mode 100644 index 000000000..1db075df0 --- /dev/null +++ b/plugins/feature/ambe/ambegui.ui @@ -0,0 +1,285 @@ + + + AMBEGUI + + + + 0 + 0 + 360 + 452 + + + + + 0 + 0 + + + + + 360 + 452 + + + + + 560 + 452 + + + + + Liberation Sans + 9 + + + + AMBE Decoder Controller + + + + + 0 + 0 + 358 + 450 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + AMBE server IP and port or direct input + + + + + + + + + + + AMBE server address as ip:port or direct input + + + + + + + + + + + + + + In use + + + + + + + Use server + + + + + + + :/arrow_down.png:/arrow_down.png + + + + + + + Release all devices and servers + + + + + + + :/bin.png:/bin.png + + + + + + + Refresh list of devices and servers in use + + + + + + + :/recycle.png:/recycle.png + + + + + + + Remove all devices + + + + + + + :/sweep.png:/sweep.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 16777215 + 140 + + + + List of devices/servers in use + + + + + + + + + + + Serial devices + + + + + + + Use serial device + + + + + + + :/arrow_up.png:/arrow_up.png + + + + + + + Use all serial devices + + + + + + + :/double_arrow_up.png:/double_arrow_up.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 16777215 + 140 + + + + List of available AMBE serial devices available + + + + + + + + + + + ... + + + + + + + + + + + RollupContents + QWidget +
gui/rollupcontents.h
+ 1 +
+
+ + + + +
diff --git a/plugins/feature/ambe/ambeplugin.cpp b/plugins/feature/ambe/ambeplugin.cpp new file mode 100644 index 000000000..7157cb2cd --- /dev/null +++ b/plugins/feature/ambe/ambeplugin.cpp @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "ambegui.h" +#endif +#include "ambe.h" +#include "ambeplugin.h" +// #include "simplepttwebapiadapter.h" + +const PluginDescriptor AMBEPlugin::m_pluginDescriptor = { + AMBE::m_featureId, + QStringLiteral("AMBE Controller"), + QStringLiteral("7.2.0"), + QStringLiteral("(c) Edouard Griffiths, F4EXB"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +AMBEPlugin::AMBEPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(nullptr) +{ +} + +const PluginDescriptor& AMBEPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void AMBEPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register Simple PTT feature + m_pluginAPI->registerFeature(AMBE::m_featureIdURI, AMBE::m_featureId, this); +} + +#ifdef SERVER_MODE +FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + (void) featureUISet; + (void) feature; + return nullptr; +} +#else +FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const +{ + return AMBEGUI::create(m_pluginAPI, featureUISet, feature); +} +#endif + +Feature* AMBEPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const +{ + return new AMBE(webAPIAdapterInterface); +} + +FeatureWebAPIAdapter* AMBEPlugin::createFeatureWebAPIAdapter() const +{ + return nullptr; // TODO new SimplePTTWebAPIAdapter(); +} diff --git a/plugins/feature/ambe/ambeplugin.h b/plugins/feature/ambe/ambeplugin.h new file mode 100644 index 000000000..2d95587b7 --- /dev/null +++ b/plugins/feature/ambe/ambeplugin.h @@ -0,0 +1,48 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FEATURE_AMBEPLUGIN_H +#define INCLUDE_FEATURE_AMBEPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class FeatureGUI; +class WebAPIAdapterInterface; + +class AMBEPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.feature.ambe") + +public: + explicit AMBEPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const; + virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const; + virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FEATURE_AMBEPLUGIN_H diff --git a/plugins/feature/ambe/ambesettings.cpp b/plugins/feature/ambe/ambesettings.cpp new file mode 100644 index 000000000..13610c10e --- /dev/null +++ b/plugins/feature/ambe/ambesettings.cpp @@ -0,0 +1,114 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "util/simpleserializer.h" +#include "settings/serializable.h" + +#include "ambesettings.h" + +AMBESettings::AMBESettings() : + m_rollupState(nullptr) +{ + resetToDefaults(); +} + +void AMBESettings::resetToDefaults() +{ + m_title = "AMBE Controller"; + m_rgbColor = QColor(255, 0, 0).rgb(); + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIFeatureSetIndex = 0; + m_reverseAPIFeatureIndex = 0; + m_workspaceIndex = 0; +} + +QByteArray AMBESettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_title); + s.writeU32(2, m_rgbColor); + s.writeBool(7, m_useReverseAPI); + s.writeString(8, m_reverseAPIAddress); + s.writeU32(9, m_reverseAPIPort); + s.writeU32(10, m_reverseAPIFeatureSetIndex); + s.writeU32(11, m_reverseAPIFeatureIndex); + + if (m_rollupState) { + s.writeBlob(12, m_rollupState->serialize()); + } + + s.writeS32(18, m_workspaceIndex); + s.writeBlob(19, m_geometryBytes); + + return s.final(); +} + +bool AMBESettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readString(1, &m_title, "Simple PTT"); + d.readU32(2, &m_rgbColor, QColor(255, 0, 0).rgb()); + 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_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp; + d.readU32(11, &utmp, 0); + m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp; + + if (m_rollupState) + { + d.readBlob(12, &bytetmp); + m_rollupState->deserialize(bytetmp); + } + + d.readS32(18, &m_workspaceIndex, 0); + d.readBlob(19, &m_geometryBytes); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/feature/ambe/ambesettings.h b/plugins/feature/ambe/ambesettings.h new file mode 100644 index 000000000..5201a2c8f --- /dev/null +++ b/plugins/feature/ambe/ambesettings.h @@ -0,0 +1,46 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_FEATURE_AMBESETTINGS_H_ +#define INCLUDE_FEATURE_AMBESETTINGS_H_ + +#include +#include + +class Serializable; + +struct AMBESettings +{ + QString m_title; + quint32 m_rgbColor; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIFeatureSetIndex; + uint16_t m_reverseAPIFeatureIndex; + Serializable *m_rollupState; + int m_workspaceIndex; + QByteArray m_geometryBytes; + + AMBESettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; } +}; + +#endif // INCLUDE_FEATURE_AMBESETTINGS_H_ diff --git a/plugins/feature/ambe/ambeworker.cpp b/plugins/feature/ambe/ambeworker.cpp new file mode 100644 index 000000000..37713daba --- /dev/null +++ b/plugins/feature/ambe/ambeworker.cpp @@ -0,0 +1,232 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 "audio/audiofifo.h" +#include "ambeworker.h" + +MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgMbeDecode, Message) +MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgTest, Message) + +AMBEWorker::AMBEWorker() : + m_running(false), + m_currentGainIn(0), + m_currentGainOut(0), + m_upsamplerLastValue(0.0f), + m_phase(0), + m_upsampling(1), + m_volume(1.0f) +{ + m_audioBuffer.resize(48000); + m_audioBufferFill = 0; + m_audioFifo = 0; + std::fill(m_dvAudioSamples, m_dvAudioSamples+SerialDV::MBE_AUDIO_BLOCK_SIZE, 0); + setVolumeFactors(); +} + +AMBEWorker::~AMBEWorker() +{} + +bool AMBEWorker::open(const std::string& deviceRef) +{ + return m_dvController.open(deviceRef); +} + +void AMBEWorker::close() +{ + m_dvController.close(); +} + +void AMBEWorker::process() +{ + m_running = true; + qDebug("AMBEWorker::process: started"); + + while (m_running) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + qDebug("AMBEWorker::process: stopped"); + emit finished(); +} + +void AMBEWorker::stop() +{ + m_running = false; +} + +void AMBEWorker::handleInputMessages() +{ + Message* message; + m_audioBufferFill = 0; + AudioFifo *audioFifo = 0; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (MsgMbeDecode::match(*message)) + { + MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message; + int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 4; + float volume = pow(10.0, dBVolume / 10.0f); + int upsampling = decodeMsg->getUpsampling(); + upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling; + + if ((volume != m_volume) || (upsampling != m_upsampling)) + { + m_volume = volume; + m_upsampling = upsampling; + setVolumeFactors(); + } + + m_upsampleFilter.useHP(decodeMsg->getUseHP()); + + if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate())) + { + if (upsampling > 1) { + upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + } else { + noUpsample(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels()); + } + + audioFifo = decodeMsg->getAudioFifo(); + + if (audioFifo && (m_audioBufferFill >= m_audioBuffer.size() - 960)) + { + uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + } + } + else + { + qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: decode failed"); + } + } + + delete message; + + if (m_inputMessageQueue.size() > 100) + { + qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: too many messages in queue. Flushing..."); + m_inputMessageQueue.clear(); + break; + } + } + + if (audioFifo) + { + uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + } + + m_timestamp = QDateTime::currentDateTime(); +} + +void AMBEWorker::pushMbeFrame(const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo) +{ + m_audioFifo = audioFifo; + m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo)); +} + +bool AMBEWorker::isAvailable() +{ + if (m_audioFifo == 0) { + return true; + } + + return m_timestamp.time().msecsTo(QDateTime::currentDateTime().time()) > 1000; // 1 second inactivity timeout +} + +bool AMBEWorker::hasFifo(AudioFifo *audioFifo) +{ + return m_audioFifo == audioFifo; +} + +void AMBEWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels) +{ + for (int i = 0; i < nbSamplesIn; i++) + { + //float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]); + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + float prev = m_upsamplerLastValue; + qint16 upsample; + + for (int j = 1; j <= upsampling; j++) + { + upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]); + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? m_compressor.compress(upsample) : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? m_compressor.compress(upsample) : 0; + + if (m_audioBufferFill < m_audioBuffer.size() - 1) { + ++m_audioBufferFill; + } + } + + m_upsamplerLastValue = cur; + } + + if (m_audioBufferFill >= m_audioBuffer.size() - 1) { + qDebug("AMBEWorker::upsample(%d): audio buffer is full check its size", upsampling); + } +} + +void AMBEWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channels) +{ + for (int i = 0; i < nbSamplesIn; i++) + { + float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i]; + m_audioBuffer[m_audioBufferFill].l = channels & 1 ? cur*m_upsamplingFactors[0] : 0; + m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? cur*m_upsamplingFactors[0] : 0; + + if (m_audioBufferFill < m_audioBuffer.size() - 1) { + ++m_audioBufferFill; + } + } + + if (m_audioBufferFill >= m_audioBuffer.size() - 1) { + qDebug("AMBEWorker::noUpsample: audio buffer is full check its size"); + } +} + +void AMBEWorker::setVolumeFactors() +{ + m_upsamplingFactors[0] = m_volume; + + for (int i = 1; i <= m_upsampling; i++) { + m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling; + } +} diff --git a/plugins/feature/ambe/ambeworker.h b/plugins/feature/ambe/ambeworker.h new file mode 100644 index 000000000..e6136c139 --- /dev/null +++ b/plugins/feature/ambe/ambeworker.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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_AMBE_AMBEWORKER_H_ +#define SDRBASE_AMBE_AMBEWORKER_H_ + +#include +#include +#include + +#include "export.h" +#include "dvcontroller.h" + +#include "util/messagequeue.h" +#include "util/message.h" +#include "dsp/filtermbe.h" +#include "dsp/dsptypes.h" +#include "audio/audiocompressor.h" + +class AudioFifo; + +class SDRBASE_API AMBEWorker : public QObject { + Q_OBJECT +public: + class MsgTest : public Message + { + MESSAGE_CLASS_DECLARATION + public: + static MsgTest* create() { return new MsgTest(); } + private: + MsgTest() {} + }; + + class MsgMbeDecode : public Message + { + MESSAGE_CLASS_DECLARATION + public: + const unsigned char *getMbeFrame() const { return m_mbeFrame; } + SerialDV::DVRate getMbeRate() const { return m_mbeRate; } + int getVolumeIndex() const { return m_volumeIndex; } + unsigned char getChannels() const { return m_channels % 4; } + bool getUseHP() const { return m_useHP; } + int getUpsampling() const { return m_upsampling; } + AudioFifo *getAudioFifo() { return m_audioFifo; } + + static MsgMbeDecode* create( + const unsigned char *mbeFrame, + int mbeRateIndex, + int volumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo) + { + return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upsampling, audioFifo); + } + + private: + unsigned char m_mbeFrame[SerialDV::MBE_FRAME_MAX_LENGTH_BYTES]; + SerialDV::DVRate m_mbeRate; + int m_volumeIndex; + unsigned char m_channels; + bool m_useHP; + int m_upsampling; + AudioFifo *m_audioFifo; + + MsgMbeDecode(const unsigned char *mbeFrame, + SerialDV::DVRate mbeRate, + int volumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo) : + Message(), + m_mbeRate(mbeRate), + m_volumeIndex(volumeIndex), + m_channels(channels), + m_useHP(useHP), + m_upsampling(upsampling), + m_audioFifo(audioFifo) + { + memcpy((void *) m_mbeFrame, (const void *) mbeFrame, SerialDV::DVController::getNbMbeBytes(m_mbeRate)); + } + }; + + AMBEWorker(); + ~AMBEWorker(); + + void pushMbeFrame(const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo); + + bool open(const std::string& deviceRef); //!< Either serial device or ip:port + void close(); + void process(); + void stop(); + bool isAvailable(); + bool hasFifo(AudioFifo *audioFifo); + + void postTest() + { + //emit inputMessageReady(); + m_inputMessageQueue.push(MsgTest::create()); + } + + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + +signals: + void finished(); + +public slots: + void handleInputMessages(); + +private: + void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels); + void noUpsample(short *in, int nbSamplesIn, unsigned char channels); + void setVolumeFactors(); + + SerialDV::DVController m_dvController; + AudioFifo *m_audioFifo; + QDateTime m_timestamp; + volatile bool m_running; + int m_currentGainIn; + int m_currentGainOut; + short m_dvAudioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE]; + AudioVector m_audioBuffer; + uint m_audioBufferFill; + float m_upsamplerLastValue; + float m_phase; + MBEAudioInterpolatorFilter m_upsampleFilter; + int m_upsampling; + float m_volume; + float m_upsamplingFactors[7]; + AudioCompressor m_compressor; +}; + +#endif // SDRBASE_AMBE_AMBEWORKER_H_ diff --git a/sdrbase/dsp/dspcommands.cpp b/sdrbase/dsp/dspcommands.cpp index 1ef8bbe9a..ba65aeef0 100644 --- a/sdrbase/dsp/dspcommands.cpp +++ b/sdrbase/dsp/dspcommands.cpp @@ -44,3 +44,4 @@ MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message) MESSAGE_CLASS_DEFINITION(DSPMIMOSignalNotification, Message) MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message) +MESSAGE_CLASS_DEFINITION(DSPPushMbeFrame, Message) diff --git a/sdrbase/dsp/dspcommands.h b/sdrbase/dsp/dspcommands.h index eda9ccc0e..9553ce2e6 100644 --- a/sdrbase/dsp/dspcommands.h +++ b/sdrbase/dsp/dspcommands.h @@ -345,4 +345,45 @@ private: AudioType m_autioType; }; +class SDRBASE_API DSPPushMbeFrame : public Message { + MESSAGE_CLASS_DECLARATION + +public: + DSPPushMbeFrame( + const unsigned char *mbeFrame, + int mbeRateIndex, + int mbeVolumeIndex, + unsigned char channels, + bool useHP, + int upsampling, + AudioFifo *audioFifo + ) : + Message(), + m_mbeFrame(mbeFrame), + m_mbeRateIndex(mbeRateIndex), + m_mbeVolumeIndex(mbeVolumeIndex), + m_channels(channels), + m_useHP(useHP), + m_upsampling(upsampling), + m_audioFifo(audioFifo) + { } + + const unsigned char * getMbeFrame() const { return m_mbeFrame; } + int getMbeRateIndex() const { return m_mbeRateIndex; } + int getMbeVolumeIndex() const { return m_mbeVolumeIndex; } + unsigned char getChannels() const { return m_channels; } + bool getUseHP() const { return m_useHP; } + int getUpsampling() const { return m_upsampling; } + AudioFifo *getAudioFifo() const { return m_audioFifo; } + +private: + const unsigned char *m_mbeFrame; + int m_mbeRateIndex; + int m_mbeVolumeIndex; + unsigned char m_channels; + bool m_useHP; + int m_upsampling; + AudioFifo *m_audioFifo; +}; + #endif // INCLUDE_DSPCOMMANDS_H