diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp index 8604b001b..37c2873f1 100644 --- a/plugins/channeltx/modam/ammod.cpp +++ b/plugins/channeltx/modam/ammod.cpp @@ -14,19 +14,23 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#include "ammod.h" +#include +#include #include #include #include - -#include -#include +#include +#include +#include +#include #include "SWGChannelSettings.h" #include "SWGChannelReport.h" #include "SWGAMModReport.h" +#include "ammod.h" + #include "dsp/upchannelizer.h" #include "dsp/dspengine.h" #include "dsp/threadedbasebandsamplesource.h" @@ -84,10 +88,15 @@ AMMod::AMMod(DeviceSinkAPI *deviceAPI) : m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this); m_deviceAPI->addThreadedSource(m_threadedChannelizer); m_deviceAPI->addChannelAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); } AMMod::~AMMod() { + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; m_deviceAPI->removeChannelAPI(this); m_deviceAPI->removeThreadedSource(m_threadedChannelizer); delete m_threadedChannelizer; @@ -332,6 +341,16 @@ bool AMMod::handleMessage(const Message& cmd) return true; } + else if (CWKeyer::MsgConfigureCWKeyer::match(cmd)) + { + const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd; + + if (m_settings.m_useReverseAPI) { + webapiReverseSendCWSettings(cfg.getSettings()); + } + + return true; + } else if (DSPConfigureAudio::match(cmd)) { DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; @@ -451,14 +470,46 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) << " m_modFactor: " << settings.m_modFactor << " m_toneFrequency: " << settings.m_toneFrequency << " m_volumeFactor: " << settings.m_volumeFactor - << " m_audioMute: " << settings.m_channelMute + << " m_channelMute: " << settings.m_channelMute << " m_playLoop: " << settings.m_playLoop << " m_modAFInput " << settings.m_modAFInput << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIAddress: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex + << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex << " force: " << force; - if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + QList reverseAPIKeys; + + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + + if ((settings.m_modFactor != m_settings.m_modFactor) || force) { + reverseAPIKeys.append("modFactor"); + } + + if ((settings.m_volumeFactor != m_settings.m_volumeFactor) || force) { + reverseAPIKeys.append("volumeFactor"); + } + + if ((settings.m_channelMute != m_settings.m_channelMute) || force) { + reverseAPIKeys.append("channelMute"); + } + + if ((settings.m_playLoop != m_settings.m_playLoop) || force) { + reverseAPIKeys.append("playLoop"); + } + + if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) { + reverseAPIKeys.append("modAFInput"); + } + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); m_settingsMutex.lock(); m_interpolatorDistanceRemain = 0; m_interpolatorConsumed = false; @@ -469,6 +520,7 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) { + reverseAPIKeys.append("toneFrequency"); m_settingsMutex.lock(); m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate); m_settingsMutex.unlock(); @@ -476,16 +528,28 @@ void AMMod::applySettings(const AMModSettings& settings, bool force) if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { + reverseAPIKeys.append("audioDeviceName"); AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName); audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex); if (m_audioSampleRate != audioSampleRate) { + reverseAPIKeys.append("audioSampleRate"); applyAudioSampleRate(audioSampleRate); } } + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + m_settings = settings; } @@ -684,3 +748,131 @@ void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) response.getAmModReport()->setAudioSampleRate(m_audioSampleRate); response.getAmModReport()->setChannelSampleRate(m_outputSampleRate); } + +void AMMod::webapiReverseSendSettings(QList& channelSettingsKeys, const AMModSettings& settings, bool force) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + swgChannelSettings->setTx(1); + swgChannelSettings->setChannelType(new QString("AMMod")); + swgChannelSettings->setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + SWGSDRangel::SWGAMModSettings *swgAMModSettings = swgChannelSettings->getAmModSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("channelMute") || force) { + swgAMModSettings->setChannelMute(settings.m_channelMute ? 1 : 0); + } + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgAMModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("modAFInput") || force) { + swgAMModSettings->setModAfInput((int) settings.m_modAFInput); + } + if (channelSettingsKeys.contains("audioDeviceName") || force) { + swgAMModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + if (channelSettingsKeys.contains("playLoop") || force) { + swgAMModSettings->setPlayLoop(settings.m_playLoop ? 1 : 0); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgAMModSettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgAMModSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgAMModSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("toneFrequency") || force) { + swgAMModSettings->setToneFrequency(settings.m_toneFrequency); + } + if (channelSettingsKeys.contains("volumeFactor") || force) { + swgAMModSettings->setVolumeFactor(settings.m_volumeFactor); + } + if (channelSettingsKeys.contains("modFactor") || force) { + swgAMModSettings->setModFactor(settings.m_modFactor); + } + + if (force) + { + const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings(); + swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + } + + 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 + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgChannelSettings; +} + +void AMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + swgChannelSettings->setTx(1); + swgChannelSettings->setChannelType(new QString("AMMod")); + swgChannelSettings->setAmModSettings(new SWGSDRangel::SWGAMModSettings()); + SWGSDRangel::SWGAMModSettings *swgAMModSettings = swgChannelSettings->getAmModSettings(); + + swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings()); + SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer(); + apiCwKeyerSettings->setLoop(cwKeyerSettings.m_loop ? 1 : 0); + apiCwKeyerSettings->setMode(cwKeyerSettings.m_mode); + apiCwKeyerSettings->setSampleRate(cwKeyerSettings.m_sampleRate); + apiCwKeyerSettings->setText(new QString(cwKeyerSettings.m_text)); + apiCwKeyerSettings->setWpm(cwKeyerSettings.m_wpm); + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex) + .arg(m_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 + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgChannelSettings; +} + +void AMMod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "AMMod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + return; + } + + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("AMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); +} diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h index 4e41f52bf..95b11ed72 100644 --- a/plugins/channeltx/modam/ammod.h +++ b/plugins/channeltx/modam/ammod.h @@ -17,11 +17,13 @@ #ifndef PLUGINS_CHANNELTX_MODAM_AMMOD_H_ #define PLUGINS_CHANNELTX_MODAM_AMMOD_H_ -#include #include #include #include +#include +#include + #include "dsp/basebandsamplesource.h" #include "channel/channelsourceapi.h" #include "dsp/nco.h" @@ -35,6 +37,8 @@ #include "ammodsettings.h" +class QNetworkAccessManager; +class QNetworkReply; class ThreadedBasebandSampleSource; class UpChannelizer; class DeviceSinkAPI; @@ -291,6 +295,9 @@ private: static const int m_levelNbSamples; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + void applyAudioSampleRate(int sampleRate); void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const AMModSettings& settings, bool force = false); @@ -301,6 +308,11 @@ private: void seekFileStream(int seekPercentage); void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMModSettings& settings); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiReverseSendSettings(QList& channelSettingsKeys, const AMModSettings& settings, bool force); + void webapiReverseSendCWSettings(const CWKeyerSettings& settings); + +private slots: + void networkManagerFinished(QNetworkReply *reply); }; diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp index 108cc94f7..28aa53342 100644 --- a/plugins/channeltx/modam/ammodgui.cpp +++ b/plugins/channeltx/modam/ammodgui.cpp @@ -279,12 +279,22 @@ void AMModGUI::onWidgetRolled(QWidget* widget, bool rollDown) void AMModGUI::onMenuDialogCalled(const QPoint &p) { BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); dialog.move(p); dialog.exec(); m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); m_settings.m_title = m_channelMarker.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); setWindowTitle(m_settings.m_title); setTitleColor(m_settings.m_rgbColor); diff --git a/plugins/channeltx/modam/ammodsettings.cpp b/plugins/channeltx/modam/ammodsettings.cpp index da1d78a2e..60b2a6aa7 100644 --- a/plugins/channeltx/modam/ammodsettings.cpp +++ b/plugins/channeltx/modam/ammodsettings.cpp @@ -41,6 +41,11 @@ void AMModSettings::resetToDefaults() m_title = "AM Modulator"; m_modAFInput = AMModInputAF::AMModInputNone; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; } QByteArray AMModSettings::serialize() const @@ -65,6 +70,11 @@ QByteArray AMModSettings::serialize() const s.writeString(9, m_title); s.writeString(10, m_audioDeviceName); s.writeS32(11, (int) m_modAFInput); + s.writeBool(12, m_useReverseAPI); + s.writeString(13, m_reverseAPIAddress); + s.writeU32(14, m_reverseAPIPort); + s.writeU32(15, m_reverseAPIDeviceIndex); + s.writeU32(16, m_reverseAPIChannelIndex); return s.final(); } @@ -83,6 +93,7 @@ bool AMModSettings::deserialize(const QByteArray& data) { QByteArray bytetmp; qint32 tmp; + uint32_t utmp; d.readS32(1, &tmp, 0); m_inputFrequencyOffset = tmp; @@ -112,6 +123,21 @@ bool AMModSettings::deserialize(const QByteArray& data) m_modAFInput = (AMModInputAF) tmp; } + d.readBool(12, &m_useReverseAPI, false); + d.readString(13, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(14, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(15, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(16, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + return true; } else diff --git a/plugins/channeltx/modam/ammodsettings.h b/plugins/channeltx/modam/ammodsettings.h index 0ed6b435e..ae8102cca 100644 --- a/plugins/channeltx/modam/ammodsettings.h +++ b/plugins/channeltx/modam/ammodsettings.h @@ -43,6 +43,11 @@ struct AMModSettings QString m_title; AMModInputAF m_modAFInput; QString m_audioDeviceName; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; Serializable *m_channelMarker; Serializable *m_cwKeyerGUI; diff --git a/sdrgui/gui/cwkeyergui.cpp b/sdrgui/gui/cwkeyergui.cpp index 72abccc90..db5f7be0b 100644 --- a/sdrgui/gui/cwkeyergui.cpp +++ b/sdrgui/gui/cwkeyergui.cpp @@ -22,6 +22,7 @@ #include "ui_cwkeyergui.h" #include "dsp/cwkeyer.h" #include "util/simpleserializer.h" +#include "util/messagequeue.h" CWKeyerGUI::CWKeyerGUI(QWidget* parent) : QWidget(parent), @@ -44,6 +45,7 @@ void CWKeyerGUI::setBuddies(MessageQueue* messageQueue, CWKeyer* cwKeyer) m_messageQueue = messageQueue; m_cwKeyer = cwKeyer; applySettings(); + sendSettings(); } void CWKeyerGUI::resetToDefaults() @@ -83,6 +85,8 @@ bool CWKeyerGUI::deserialize(const QByteArray& data) ui->cwSpeed->setValue(aValue); applySettings(); + sendSettings(); + return true; } else @@ -102,13 +106,22 @@ void CWKeyerGUI::on_cwTextClear_clicked(bool checked) void CWKeyerGUI::on_cwTextEdit_editingFinished() { - if (m_doApplySettings) { m_cwKeyer->setText(ui->cwTextEdit->text()); } + if (m_doApplySettings) + { + m_cwKeyer->setText(ui->cwTextEdit->text()); + sendSettings(); + } } void CWKeyerGUI::on_cwSpeed_valueChanged(int value) { ui->cwSpeedText->setText(QString("%1").arg(value)); - if (m_doApplySettings) { m_cwKeyer->setWPM(value); } + + if (m_doApplySettings) + { + m_cwKeyer->setWPM(value); + sendSettings(); + } } void CWKeyerGUI::on_playDots_toggled(bool checked) @@ -117,7 +130,11 @@ void CWKeyerGUI::on_playDots_toggled(bool checked) ui->playDashes->setEnabled(!checked); ui->playText->setEnabled(!checked); - if (m_doApplySettings) { m_cwKeyer->setMode(checked ? CWKeyerSettings::CWDots : CWKeyerSettings::CWNone); } + if (m_doApplySettings) + { + m_cwKeyer->setMode(checked ? CWKeyerSettings::CWDots : CWKeyerSettings::CWNone); + sendSettings(); + } } void CWKeyerGUI::on_playDashes_toggled(bool checked) @@ -126,7 +143,11 @@ void CWKeyerGUI::on_playDashes_toggled(bool checked) //ui->playDashes->setEnabled(!checked); ui->playText->setEnabled(!checked); - if (m_doApplySettings) { m_cwKeyer->setMode(checked ? CWKeyerSettings::CWDashes : CWKeyerSettings::CWNone); } + if (m_doApplySettings) + { + m_cwKeyer->setMode(checked ? CWKeyerSettings::CWDashes : CWKeyerSettings::CWNone); + sendSettings(); + } } void CWKeyerGUI::on_playText_toggled(bool checked) @@ -135,7 +156,11 @@ void CWKeyerGUI::on_playText_toggled(bool checked) ui->playDashes->setEnabled(!checked); //ui->playText->setEnabled(!checked); - if (m_doApplySettings) { m_cwKeyer->setMode(checked ? CWKeyerSettings::CWText : CWKeyerSettings::CWNone); } + if (m_doApplySettings) + { + m_cwKeyer->setMode(checked ? CWKeyerSettings::CWText : CWKeyerSettings::CWNone); + sendSettings(); + } if (checked) { ui->playStop->setChecked(true); @@ -146,17 +171,18 @@ void CWKeyerGUI::on_playText_toggled(bool checked) void CWKeyerGUI::on_playLoopCW_toggled(bool checked) { - if (m_doApplySettings) { m_cwKeyer->setLoop(checked); } + if (m_doApplySettings) + { + m_cwKeyer->setLoop(checked); + sendSettings(); + } } void CWKeyerGUI::on_playStop_toggled(bool checked) { - if (checked) - { + if (checked) { m_cwKeyer->resetText(); - } - else - { + } else { m_cwKeyer->stopText(); } } @@ -200,3 +226,12 @@ void CWKeyerGUI::blockApplySettings(bool block) { m_doApplySettings = !block; } + +void CWKeyerGUI::sendSettings() +{ + if (m_cwKeyer && m_messageQueue) + { + CWKeyer::MsgConfigureCWKeyer *msg = CWKeyer::MsgConfigureCWKeyer::create(m_cwKeyer->getSettings(), false); + m_messageQueue->push(msg); + } +} diff --git a/sdrgui/gui/cwkeyergui.h b/sdrgui/gui/cwkeyergui.h index a9d273c5b..f1f840159 100644 --- a/sdrgui/gui/cwkeyergui.h +++ b/sdrgui/gui/cwkeyergui.h @@ -54,6 +54,7 @@ private: bool m_doApplySettings; void applySettings(); + void sendSettings(); void blockApplySettings(bool block); private slots: