diff --git a/doc/img/PagerDemod_plugin.png b/doc/img/PagerDemod_plugin.png new file mode 100644 index 000000000..2cb03ec10 Binary files /dev/null and b/doc/img/PagerDemod_plugin.png differ diff --git a/doc/img/PagerDemod_plugin_messages.png b/doc/img/PagerDemod_plugin_messages.png new file mode 100644 index 000000000..0d89866b5 Binary files /dev/null and b/doc/img/PagerDemod_plugin_messages.png differ diff --git a/plugins/channelrx/demodpager/CMakeLists.txt b/plugins/channelrx/demodpager/CMakeLists.txt new file mode 100644 index 000000000..894406fc6 --- /dev/null +++ b/plugins/channelrx/demodpager/CMakeLists.txt @@ -0,0 +1,58 @@ +project(demodpager) + +set(demodpager_SOURCES + pagerdemod.cpp + pagerdemodsettings.cpp + pagerdemodbaseband.cpp + pagerdemodsink.cpp + pagerdemodplugin.cpp + pagerdemodwebapiadapter.cpp +) + +set(demodpager_HEADERS + pagerdemod.h + pagerdemodsettings.h + pagerdemodbaseband.h + pagerdemodsink.h + pagerdemodplugin.h + pagerdemodwebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(demodpager_SOURCES + ${demodpager_SOURCES} + pagerdemodgui.cpp + pagerdemodgui.ui + ) + set(demodpager_HEADERS + ${demodpager_HEADERS} + pagerdemodgui.h + ) + + set(TARGET_NAME demodpager) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME demodpagersrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${demodpager_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/channelrx/demodpager/pagerdemod.cpp b/plugins/channelrx/demodpager/pagerdemod.cpp new file mode 100644 index 000000000..a73f473e2 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemod.cpp @@ -0,0 +1,509 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "pagerdemod.h" + +#include +#include +#include +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGChannelReport.h" + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/deviceapi.h" +#include "feature/feature.h" +#include "maincore.h" + +MESSAGE_CLASS_DEFINITION(PagerDemod::MsgConfigurePagerDemod, Message) +MESSAGE_CLASS_DEFINITION(PagerDemod::MsgPagerMessage, Message) + +const char * const PagerDemod::m_channelIdURI = "sdrangel.channel.pagerdemod"; +const char * const PagerDemod::m_channelId = "PagerDemod"; + +PagerDemod::PagerDemod(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0) +{ + setObjectName(m_channelId); + + m_basebandSink = new PagerDemodBaseband(this); + m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); + m_basebandSink->setChannel(this); + m_basebandSink->moveToThread(&m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); + m_deviceAPI->addChannelSinkAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + connect(&m_channelMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleChannelMessages())); +} + +PagerDemod::~PagerDemod() +{ + qDebug("PagerDemod::~PagerDemod"); + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + + if (m_basebandSink->isRunning()) { + stop(); + } + + delete m_basebandSink; +} + +uint32_t PagerDemod::getNumberOfDeviceStreams() const +{ + return m_deviceAPI->getNbSourceStreams(); +} + +void PagerDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +{ + (void) firstOfBurst; + m_basebandSink->feed(begin, end); +} + +void PagerDemod::start() +{ + qDebug("PagerDemod::start"); + + m_basebandSink->reset(); + m_basebandSink->startWork(); + m_thread.start(); + + DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency); + m_basebandSink->getInputMessageQueue()->push(dspMsg); + + PagerDemodBaseband::MsgConfigurePagerDemodBaseband *msg = PagerDemodBaseband::MsgConfigurePagerDemodBaseband::create(m_settings, true); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +void PagerDemod::stop() +{ + qDebug("PagerDemod::stop"); + m_basebandSink->stopWork(); + m_thread.quit(); + m_thread.wait(); +} + +bool PagerDemod::handleMessage(const Message& cmd) +{ + if (MsgConfigurePagerDemod::match(cmd)) + { + MsgConfigurePagerDemod& cfg = (MsgConfigurePagerDemod&) cmd; + qDebug() << "PagerDemod::handleMessage: MsgConfigurePagerDemod"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "PagerDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + + return true; + } + else if (MsgPagerMessage::match(cmd)) + { + // Forward to GUI + MsgPagerMessage& report = (MsgPagerMessage&)cmd; + if (getMessageQueueToGUI()) + { + MsgPagerMessage *msg = new MsgPagerMessage(report); + getMessageQueueToGUI()->push(msg); + } + + // Forward via UDP + if (m_settings.m_udpEnabled) + { + QString message = QString("%1%2%3%4%5%6%7%8%9").arg( + report.getDateTime().toString(), + '\0', + QString::number(report.getAddress()), + '\0', + QString::number(report.getFunctionBits()), + '\0', + report.getAlphaMessage(), + '\0', + report.getNumericMessage()); + m_udpSocket.writeDatagram(message.toLatin1(), message.size(), + QHostAddress(m_settings.m_udpAddress), m_settings.m_udpPort); + } + + return true; + } + else if (MainCore::MsgChannelDemodQuery::match(cmd)) + { + qDebug() << "PagerDemod::handleMessage: MsgChannelDemodQuery"; + sendSampleRateToDemodAnalyzer(); + + return true; + } + else + { + return false; + } +} + +ScopeVis *PagerDemod::getScopeSink() +{ + return m_basebandSink->getScopeSink(); +} + +void PagerDemod::applySettings(const PagerDemodSettings& settings, bool force) +{ + qDebug() << "PagerDemod::applySettings:" + << " m_streamIndex: " << settings.m_streamIndex + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress + << " m_reverseAPIPort: " << settings.m_reverseAPIPort + << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex + << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex + << " force: " << force; + + QList reverseAPIKeys; + + if ((settings.m_baud != m_settings.m_baud) || force) { + reverseAPIKeys.append("baud"); + } + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); + } + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { + reverseAPIKeys.append("fmDeviation"); + } + if ((settings.m_udpEnabled != m_settings.m_udpEnabled) || force) { + reverseAPIKeys.append("udpEnabled"); + } + if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) { + reverseAPIKeys.append("udpAddress"); + } + if ((settings.m_udpPort != m_settings.m_udpPort) || force) { + reverseAPIKeys.append("udpPort"); + } + if (m_settings.m_streamIndex != settings.m_streamIndex) + { + if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only + { + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); + m_deviceAPI->addChannelSinkAPI(this); + } + + reverseAPIKeys.append("streamIndex"); + } + + PagerDemodBaseband::MsgConfigurePagerDemodBaseband *msg = PagerDemodBaseband::MsgConfigurePagerDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +QByteArray PagerDemod::serialize() const +{ + return m_settings.serialize(); +} + +bool PagerDemod::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigurePagerDemod *msg = MsgConfigurePagerDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigurePagerDemod *msg = MsgConfigurePagerDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +void PagerDemod::sendSampleRateToDemodAnalyzer() +{ + QList *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(this, "reportdemod"); + + if (messageQueues) + { + QList::iterator it = messageQueues->begin(); + + for (; it != messageQueues->end(); ++it) + { + MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create( + this, + PagerDemodSettings::m_channelSampleRate + ); + (*it)->push(msg); + } + } +} + +int PagerDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings()); + response.getPagerDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int PagerDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + PagerDemodSettings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigurePagerDemod *msg = MsgConfigurePagerDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("PagerDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigurePagerDemod *msgToGUI = MsgConfigurePagerDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void PagerDemod::webapiUpdateChannelSettings( + PagerDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("baud")) { + settings.m_baud = response.getPagerDemodSettings()->getBaud(); + } + if (channelSettingsKeys.contains("inputFrequencyOffset")) { + settings.m_inputFrequencyOffset = response.getPagerDemodSettings()->getInputFrequencyOffset(); + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getPagerDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("fmDeviation")) { + settings.m_fmDeviation = response.getPagerDemodSettings()->getFmDeviation(); + } + if (channelSettingsKeys.contains("udpEnabled")) { + settings.m_udpEnabled = response.getPagerDemodSettings()->getUdpEnabled(); + } + if (channelSettingsKeys.contains("udpAddress")) { + settings.m_udpAddress = *response.getPagerDemodSettings()->getUdpAddress(); + } + if (channelSettingsKeys.contains("udpPort")) { + settings.m_udpPort = response.getPagerDemodSettings()->getUdpPort(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getPagerDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getPagerDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getPagerDemodSettings()->getStreamIndex(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getPagerDemodSettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getPagerDemodSettings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getPagerDemodSettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getPagerDemodSettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getPagerDemodSettings()->getReverseApiChannelIndex(); + } +} + +void PagerDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const PagerDemodSettings& settings) +{ + response.getPagerDemodSettings()->setBaud(settings.m_baud); + response.getPagerDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getPagerDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getPagerDemodSettings()->setFmDeviation(settings.m_fmDeviation); + response.getPagerDemodSettings()->setUdpEnabled(settings.m_udpEnabled); + response.getPagerDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress)); + response.getPagerDemodSettings()->setUdpPort(settings.m_udpPort); + + response.getPagerDemodSettings()->setRgbColor(settings.m_rgbColor); + if (response.getPagerDemodSettings()->getTitle()) { + *response.getPagerDemodSettings()->getTitle() = settings.m_title; + } else { + response.getPagerDemodSettings()->setTitle(new QString(settings.m_title)); + } + + response.getPagerDemodSettings()->setStreamIndex(settings.m_streamIndex); + response.getPagerDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getPagerDemodSettings()->getReverseApiAddress()) { + *response.getPagerDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getPagerDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getPagerDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getPagerDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getPagerDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void PagerDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const PagerDemodSettings& settings, bool force) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force); + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex) + .arg(settings.m_reverseAPIChannelIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgChannelSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgChannelSettings; +} + +void PagerDemod::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const PagerDemodSettings& settings, + bool force +) +{ + swgChannelSettings->setDirection(0); // Single sink (Rx) + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("PagerDemod")); + swgChannelSettings->setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings()); + SWGSDRangel::SWGPagerDemodSettings *swgPagerDemodSettings = swgChannelSettings->getPagerDemodSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("baud") || force) { + swgPagerDemodSettings->setBaud(settings.m_baud); + } + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgPagerDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgPagerDemodSettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("fmDeviation") || force) { + swgPagerDemodSettings->setFmDeviation(settings.m_fmDeviation); + } + if (channelSettingsKeys.contains("udpEnabled") || force) { + swgPagerDemodSettings->setUdpEnabled(settings.m_udpEnabled); + } + if (channelSettingsKeys.contains("udpAddress") || force) { + swgPagerDemodSettings->setUdpAddress(new QString(settings.m_udpAddress)); + } + if (channelSettingsKeys.contains("udpPort") || force) { + swgPagerDemodSettings->setUdpPort(settings.m_udpPort); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgPagerDemodSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgPagerDemodSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("streamIndex") || force) { + swgPagerDemodSettings->setStreamIndex(settings.m_streamIndex); + } +} + +void PagerDemod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "PagerDemod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("PagerDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} + +void PagerDemod::handleChannelMessages() +{ + Message* message; + + while ((message = m_channelMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} diff --git a/plugins/channelrx/demodpager/pagerdemod.h b/plugins/channelrx/demodpager/pagerdemod.h new file mode 100644 index 000000000..a8db1cc86 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemod.h @@ -0,0 +1,197 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMOD_H +#define INCLUDE_PAGERDEMOD_H + +#include + +#include +#include +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelapi.h" +#include "util/message.h" + +#include "pagerdemodbaseband.h" +#include "pagerdemodsettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class DeviceAPI; +class ScopeVis; + +class PagerDemod : public BasebandSampleSink, public ChannelAPI { + Q_OBJECT +public: + class MsgConfigurePagerDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PagerDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigurePagerDemod* create(const PagerDemodSettings& settings, bool force) + { + return new MsgConfigurePagerDemod(settings, force); + } + + private: + PagerDemodSettings m_settings; + bool m_force; + + MsgConfigurePagerDemod(const PagerDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgPagerMessage : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getAddress() const { return m_address; } + int getFunctionBits() const { return m_functionBits; } + QString getAlphaMessage() const { return m_alphaMessage; } + QString getNumericMessage() const { return m_numericMessage; } + int getEvenParityErrors() const { return m_evenParityErrors; } + int getBCHParityErrors() const { return m_bchParityErrors; } + QDateTime getDateTime() const { return m_dateTime; } + + static MsgPagerMessage* create(int address, int functionBits, const QString& alphaMessage, const QString& numericMessage, int evenParityErrors, int bchParityErrors) + { + return new MsgPagerMessage(address, functionBits, alphaMessage, numericMessage, evenParityErrors, bchParityErrors, QDateTime::currentDateTime()); + } + + private: + int m_address; + int m_functionBits; + QString m_alphaMessage; + QString m_numericMessage; + int m_evenParityErrors; + int m_bchParityErrors; + QDateTime m_dateTime; + + MsgPagerMessage(int address, int functionBits, const QString& alphaMessage, const QString& numericMessage, int evenParityErrors, int bchParityErrors, QDateTime dateTime) : + Message(), + m_address(address), + m_functionBits(functionBits), + m_alphaMessage(alphaMessage), + m_numericMessage(numericMessage), + m_evenParityErrors(evenParityErrors), + m_bchParityErrors(bchParityErrors), + m_dateTime(dateTime) + { + } + }; + + PagerDemod(DeviceAPI *deviceAPI); + virtual ~PagerDemod(); + virtual void destroy() { delete this; } + + using BasebandSampleSink::feed; + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual const QString& getURI() const { return getName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int getNbSinkStreams() const { return 1; } + virtual int getNbSourceStreams() const { return 0; } + + virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const + { + (void) streamIndex; + (void) sinkElseSource; + return 0; + } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const PagerDemodSettings& settings); + + static void webapiUpdateChannelSettings( + PagerDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response); + + ScopeVis *getScopeSink(); + double getMagSq() const { return m_basebandSink->getMagSq(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_basebandSink->getMagSqLevels(avg, peak, nbSamples); + } +/* void setMessageQueueToGUI(MessageQueue* queue) override { + ChannelAPI::setMessageQueueToGUI(queue); + m_basebandSink->setMessageQueueToGUI(queue); + }*/ + + uint32_t getNumberOfDeviceStreams() const; + + static const char * const m_channelIdURI; + static const char * const m_channelId; + +private: + DeviceAPI *m_deviceAPI; + QThread m_thread; + PagerDemodBaseband* m_basebandSink; + PagerDemodSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + qint64 m_centerFrequency; + QUdpSocket m_udpSocket; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const PagerDemodSettings& settings, bool force = false); + void sendSampleRateToDemodAnalyzer(); + void webapiReverseSendSettings(QList& channelSettingsKeys, const PagerDemodSettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const PagerDemodSettings& settings, + bool force + ); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + void handleChannelMessages(); +}; + +#endif // INCLUDE_PAGERDEMOD_H diff --git a/plugins/channelrx/demodpager/pagerdemodbaseband.cpp b/plugins/channelrx/demodpager/pagerdemodbaseband.cpp new file mode 100644 index 000000000..632b484bc --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodbaseband.cpp @@ -0,0 +1,176 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" + +#include "pagerdemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(PagerDemodBaseband::MsgConfigurePagerDemodBaseband, Message) + +PagerDemodBaseband::PagerDemodBaseband(PagerDemod *pagerDemod) : + m_sink(pagerDemod), + m_running(false), + m_mutex(QMutex::Recursive) +{ + qDebug("PagerDemodBaseband::PagerDemodBaseband"); + + m_sink.setScopeSink(&m_scopeSink); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownChannelizer(&m_sink); +} + +PagerDemodBaseband::~PagerDemodBaseband() +{ + m_inputMessageQueue.clear(); + + delete m_channelizer; +} + +void PagerDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); + m_sampleFifo.reset(); +} + +void PagerDemodBaseband::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &PagerDemodBaseband::handleData, + Qt::QueuedConnection + ); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; +} + +void PagerDemodBaseband::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + QObject::disconnect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &PagerDemodBaseband::handleData + ); + m_running = false; +} + +void PagerDemodBaseband::setChannel(ChannelAPI *channel) +{ + m_sink.setChannel(channel); +} + +void PagerDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void PagerDemodBaseband::handleData() +{ + QMutexLocker mutexLocker(&m_mutex); + + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void PagerDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool PagerDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigurePagerDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigurePagerDemodBaseband& cfg = (MsgConfigurePagerDemodBaseband&) cmd; + qDebug() << "PagerDemodBaseband::handleMessage: MsgConfigurePagerDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "PagerDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + setBasebandSampleRate(notif.getSampleRate()); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + + return true; + } + else + { + return false; + } +} + +void PagerDemodBaseband::applySettings(const PagerDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(PagerDemodSettings::m_channelSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +void PagerDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/demodpager/pagerdemodbaseband.h b/plugins/channelrx/demodpager/pagerdemodbaseband.h new file mode 100644 index 000000000..b55c3aa1c --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodbaseband.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMODBASEBAND_H +#define INCLUDE_PAGERDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "dsp/scopevis.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "pagerdemodsink.h" + +class DownChannelizer; +class ChannelAPI; +class PagerDemod; +class ScopeVis; + +class PagerDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigurePagerDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const PagerDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigurePagerDemodBaseband* create(const PagerDemodSettings& settings, bool force) + { + return new MsgConfigurePagerDemodBaseband(settings, force); + } + + private: + PagerDemodSettings m_settings; + bool m_force; + + MsgConfigurePagerDemodBaseband(const PagerDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + PagerDemodBaseband(PagerDemod *pagerDemod); + ~PagerDemodBaseband(); + void reset(); + void startWork(); + void stopWork(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_sink.getMagSqLevels(avg, peak, nbSamples); + } + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); } + void setBasebandSampleRate(int sampleRate); + ScopeVis *getScopeSink() { return &m_scopeSink; } + void setChannel(ChannelAPI *channel); + double getMagSq() const { return m_sink.getMagSq(); } + bool isRunning() const { return m_running; } + +private: + SampleSinkFifo m_sampleFifo; + DownChannelizer *m_channelizer; + PagerDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + PagerDemodSettings m_settings; + ScopeVis m_scopeSink; + bool m_running; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void calculateOffset(PagerDemodSink *sink); + void applySettings(const PagerDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_PAGERDEMODBASEBAND_H diff --git a/plugins/channelrx/demodpager/pagerdemodgui.cpp b/plugins/channelrx/demodpager/pagerdemodgui.cpp new file mode 100644 index 000000000..5b18651be --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodgui.cpp @@ -0,0 +1,649 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "pagerdemodgui.h" + +#include "device/deviceuiset.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "ui_pagerdemodgui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "gui/basicchannelsettingsdialog.h" +#include "gui/devicestreamselectiondialog.h" +#include "dsp/dspengine.h" +#include "dsp/glscopesettings.h" +#include "gui/crightclickenabler.h" +#include "maincore.h" + +#include "pagerdemod.h" + +void PagerDemodGUI::resizeTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + // Trailing spaces are for sort arrow + int row = ui->messages->rowCount(); + ui->messages->setRowCount(row + 1); + ui->messages->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016-")); + ui->messages->setItem(row, MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00")); + ui->messages->setItem(row, MESSAGE_COL_ADDRESS, new QTableWidgetItem("1000000")); + ui->messages->setItem(row, MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ")); + ui->messages->setItem(row, MESSAGE_COL_FUNCTION, new QTableWidgetItem("0")); + ui->messages->setItem(row, MESSAGE_COL_ALPHA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ")); + ui->messages->setItem(row, MESSAGE_COL_NUMERIC, new QTableWidgetItem("123456789123456789123456789123456789123456789123456789")); + ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, new QTableWidgetItem("0")); + ui->messages->setItem(row, MESSAGE_COL_BCH_PE, new QTableWidgetItem("0")); + ui->messages->resizeColumnsToContents(); + ui->messages->removeRow(row); +} + +// Columns in table reordered +void PagerDemodGUI::messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_messageColumnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void PagerDemodGUI::messages_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_messageColumnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void PagerDemodGUI::messagesColumnSelectMenu(QPoint pos) +{ + messagesMenu->popup(ui->messages->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void PagerDemodGUI::messagesColumnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->messages->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu item +QAction *PagerDemodGUI::createCheckableItem(QString &text, int idx, bool checked, const char *slot) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, slot); + return action; +} + +PagerDemodGUI* PagerDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + PagerDemodGUI* gui = new PagerDemodGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void PagerDemodGUI::destroy() +{ + delete this; +} + +void PagerDemodGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray PagerDemodGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool PagerDemodGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +// Add row to table +void PagerDemodGUI::messageReceived(const PagerDemod::MsgPagerMessage& message) +{ + // Add to messages table + ui->messages->setSortingEnabled(false); + int row = ui->messages->rowCount(); + ui->messages->setRowCount(row + 1); + + QTableWidgetItem *dateItem = new QTableWidgetItem(); + QTableWidgetItem *timeItem = new QTableWidgetItem(); + QTableWidgetItem *addressItem = new QTableWidgetItem(); + QTableWidgetItem *messageItem = new QTableWidgetItem(); + QTableWidgetItem *functionItem = new QTableWidgetItem(); + QTableWidgetItem *alphaItem = new QTableWidgetItem(); + QTableWidgetItem *numericItem = new QTableWidgetItem(); + QTableWidgetItem *evenPEItem = new QTableWidgetItem(); + QTableWidgetItem *bchPEItem = new QTableWidgetItem(); + ui->messages->setItem(row, MESSAGE_COL_DATE, dateItem); + ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem); + ui->messages->setItem(row, MESSAGE_COL_ADDRESS, addressItem); + ui->messages->setItem(row, MESSAGE_COL_MESSAGE, messageItem); + ui->messages->setItem(row, MESSAGE_COL_FUNCTION, functionItem); + ui->messages->setItem(row, MESSAGE_COL_ALPHA, alphaItem); + ui->messages->setItem(row, MESSAGE_COL_NUMERIC, numericItem); + ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, evenPEItem); + ui->messages->setItem(row, MESSAGE_COL_BCH_PE, bchPEItem); + dateItem->setText(message.getDateTime().date().toString()); + timeItem->setText(message.getDateTime().time().toString()); + addressItem->setText(QString("%1").arg(message.getAddress(), 7, 10, QChar('0'))); + // Standard way of choosing numeric or alpha decode isn't followed widely + if (m_settings.m_decode == PagerDemodSettings::Standard) + { + // Encoding is based on function bits + if (message.getFunctionBits() == 0) { + messageItem->setText(message.getNumericMessage()); + } else { + messageItem->setText(message.getAlphaMessage()); + } + } + else if (m_settings.m_decode == PagerDemodSettings::Inverted) + { + // Encoding is based on function bits, but inverted from standard + if (message.getFunctionBits() == 3) { + messageItem->setText(message.getNumericMessage()); + } else { + messageItem->setText(message.getAlphaMessage()); + } + } + else if (m_settings.m_decode == PagerDemodSettings::Numeric) + { + // Always display as numeric + messageItem->setText(message.getNumericMessage()); + } + else if (m_settings.m_decode == PagerDemodSettings::Alphanumeric) + { + // Always display as alphanumeric + messageItem->setText(message.getAlphaMessage()); + } + else + { + // Guess at what the encoding is + QString numeric = message.getNumericMessage(); + QString alpha = message.getAlphaMessage(); + bool done = false; + if (!done) + { + // If alpha contains control characters, possibly numeric + for (int i = 0; i < alpha.size(); i++) + { + if (iscntrl(alpha[i].toLatin1()) && !isspace(alpha[i].toLatin1())) + { + messageItem->setText(numeric); + done = true; + break; + } + } + } + if (!done) { + // Possibly not likely to get only longer than 15 digits + if (numeric.size() > 15) + { + done = true; + messageItem->setText(alpha); + } + } + if (!done) { + // Default to alpha + messageItem->setText(alpha); + } + } + functionItem->setText(QString("%1").arg(message.getFunctionBits())); + alphaItem->setText(message.getAlphaMessage()); + numericItem->setText(message.getNumericMessage()); + evenPEItem->setText(QString("%1").arg(message.getEvenParityErrors())); + bchPEItem->setText(QString("%1").arg(message.getBCHParityErrors())); + ui->messages->setSortingEnabled(true); + ui->messages->scrollToItem(dateItem); // Will only scroll if not hidden + filterRow(row); +} + +bool PagerDemodGUI::handleMessage(const Message& message) +{ + if (PagerDemod::MsgConfigurePagerDemod::match(message)) + { + qDebug("PagerDemodGUI::handleMessage: PagerDemod::MsgConfigurePagerDemod"); + const PagerDemod::MsgConfigurePagerDemod& cfg = (PagerDemod::MsgConfigurePagerDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (PagerDemod::MsgPagerMessage::match(message)) + { + PagerDemod::MsgPagerMessage& report = (PagerDemod::MsgPagerMessage&) message; + messageReceived(report); + return true; + } + + return false; +} + +void PagerDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void PagerDemodGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void PagerDemodGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +void PagerDemodGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void PagerDemodGUI::on_rfBW_valueChanged(int value) +{ + float bw = value * 100.0f; + ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + m_channelMarker.setBandwidth(bw); + m_settings.m_rfBandwidth = bw; + applySettings(); +} + +void PagerDemodGUI::on_fmDev_valueChanged(int value) +{ + ui->fmDevText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + m_settings.m_fmDeviation = value * 100.0; + applySettings(); +} + +void PagerDemodGUI::on_baud_currentIndexChanged(int index) +{ + m_settings.m_baud = ui->baud->currentText().toInt(); + applySettings(); +} + +void PagerDemodGUI::on_decode_currentIndexChanged(int index) +{ + m_settings.m_decode = (PagerDemodSettings::Decode)index; + applySettings(); +} + +void PagerDemodGUI::on_filterAddress_editingFinished() +{ + m_settings.m_filterAddress = ui->filterAddress->text(); + filter(); + applySettings(); +} + +void PagerDemodGUI::on_clearTable_clicked() +{ + ui->messages->setRowCount(0); +} + +void PagerDemodGUI::on_udpEnabled_clicked(bool checked) +{ + m_settings.m_udpEnabled = checked; + applySettings(); +} + +void PagerDemodGUI::on_udpAddress_editingFinished() +{ + m_settings.m_udpAddress = ui->udpAddress->text(); + applySettings(); +} + +void PagerDemodGUI::on_udpPort_editingFinished() +{ + m_settings.m_udpPort = ui->udpPort->text().toInt(); + applySettings(); +} + +void PagerDemodGUI::on_channel1_currentIndexChanged(int index) +{ + m_settings.m_scopeCh1 = index; + applySettings(); +} + +void PagerDemodGUI::on_channel2_currentIndexChanged(int index) +{ + m_settings.m_scopeCh2 = index; + applySettings(); +} + +void PagerDemodGUI::filterRow(int row) +{ + bool hidden = false; + if (m_settings.m_filterAddress != "") + { + QRegExp re(m_settings.m_filterAddress); + QTableWidgetItem *fromItem = ui->messages->item(row, MESSAGE_COL_ADDRESS); + if (!re.exactMatch(fromItem->text())) { + hidden = true; + } + } + ui->messages->setRowHidden(row, hidden); +} + +void PagerDemodGUI::filter() +{ + for (int i = 0; i < ui->messages->rowCount(); i++) { + filterRow(i); + } +} + +void PagerDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void PagerDemodGUI::onMenuDialogCalled(const QPoint &p) +{ + if (m_contextMenuType == ContextMenuChannelSettings) + { + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); + dialog.move(p); + dialog.exec(); + + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine)) + { + DeviceStreamSelectionDialog dialog(this); + dialog.setNumberOfStreams(m_pagerDemod->getNumberOfDeviceStreams()); + dialog.setStreamIndex(m_settings.m_streamIndex); + dialog.move(p); + dialog.exec(); + + m_settings.m_streamIndex = dialog.getSelectedStreamIndex(); + m_channelMarker.clearStreamIndexes(); + m_channelMarker.addStreamIndex(m_settings.m_streamIndex); + displayStreamIndex(); + applySettings(); + } + + resetContextMenuType(); +} + +PagerDemodGUI::PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::PagerDemodGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_tickCount(0) +{ + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_pagerDemod = reinterpret_cast(rxChannel); + m_pagerDemod->setMessageQueueToGUI(getInputMessageQueue()); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + + m_scopeVis = m_pagerDemod->getScopeSink(); + m_scopeVis->setGLScope(ui->glScope); + m_scopeVis->setLiveRate(PagerDemodSettings::m_channelSampleRate); + ui->glScope->connectTimer(MainCore::instance()->getMasterTimer()); + ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::yellow); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle("Pager Demodulator"); + m_channelMarker.blockSignals(false); + m_channelMarker.setVisible(true); // activate signal on the last setting only + + setTitleColor(m_channelMarker.getColor()); + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setScopeGUI(ui->scopeGUI); + + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + // Resize the table using dummy data + resizeTable(); + // Allow user to reorder columns + ui->messages->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->messages->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + messagesMenu = new QMenu(ui->messages); + for (int i = 0; i < ui->messages->horizontalHeader()->count(); i++) + { + QString text = ui->messages->horizontalHeaderItem(i)->text(); + messagesMenu->addAction(createCheckableItem(text, i, true, SLOT(messagesColumnSelectMenuChecked()))); + } + ui->messages->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->messages->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(messagesColumnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->messages->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(messages_sectionMoved(int, int, int))); + connect(ui->messages->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(messages_sectionResized(int, int, int))); + ui->messages->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->messages, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint))); + + ui->scopeContainer->setVisible(false); + + displaySettings(); + applySettings(true); +} + +void PagerDemodGUI::customContextMenuRequested(QPoint pos) +{ + QTableWidgetItem *item = ui->messages->itemAt(pos); + if (item) + { + QMenu* tableContextMenu = new QMenu(ui->messages); + connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater); + QAction* copyAction = new QAction("Copy", tableContextMenu); + const QString text = item->text(); + connect(copyAction, &QAction::triggered, this, [text]()->void { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); + }); + tableContextMenu->addAction(copyAction); + tableContextMenu->popup(ui->messages->viewport()->mapToGlobal(pos)); + } +} + +PagerDemodGUI::~PagerDemodGUI() +{ + delete ui; +} + +void PagerDemodGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void PagerDemodGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + PagerDemod::MsgConfigurePagerDemod* message = PagerDemod::MsgConfigurePagerDemod::create( m_settings, force); + m_pagerDemod->getInputMessageQueue()->push(message); + } +} + +void PagerDemodGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + + if (m_settings.m_baud == 512) { + ui->baud->setCurrentIndex(0); + } else if (m_settings.m_baud == 1200) { + ui->baud->setCurrentIndex(1); + } else { + ui->baud->setCurrentIndex(2); + } + ui->decode->setCurrentIndex((int)m_settings.m_decode); + + ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1)); + ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0); + + ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1)); + ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0); + + displayStreamIndex(); + + ui->filterAddress->setText(m_settings.m_filterAddress); + + ui->udpEnabled->setChecked(m_settings.m_udpEnabled); + ui->udpAddress->setText(m_settings.m_udpAddress); + ui->udpPort->setText(QString::number(m_settings.m_udpPort)); + + ui->channel1->setCurrentIndex(m_settings.m_scopeCh1); + ui->channel2->setCurrentIndex(m_settings.m_scopeCh2); + + // Order and size columns + QHeaderView *header = ui->messages->horizontalHeader(); + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) + { + bool hidden = m_settings.m_messageColumnSizes[i] == 0; + header->setSectionHidden(i, hidden); + messagesMenu->actions().at(i)->setChecked(!hidden); + if (m_settings.m_messageColumnSizes[i] > 0) + ui->messages->setColumnWidth(i, m_settings.m_messageColumnSizes[i]); + header->moveSection(header->visualIndex(i), m_settings.m_messageColumnIndexes[i]); + } + filter(); + + blockApplySettings(false); +} + +void PagerDemodGUI::displayStreamIndex() +{ + if (m_deviceUISet->m_deviceMIMOEngine) { + setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex)); + } else { + setStreamIndicator("S"); // single channel indicator + } +} + +void PagerDemodGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void PagerDemodGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void PagerDemodGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_pagerDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + ui->channelPowerMeter->levelChanged( + (100.0f + powDbAvg) / 100.0f, + (100.0f + powDbPeak) / 100.0f, + nbMagsqSamples); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); + } + + m_tickCount++; +} diff --git a/plugins/channelrx/demodpager/pagerdemodgui.h b/plugins/channelrx/demodpager/pagerdemodgui.h new file mode 100644 index 000000000..d1bd09e7a --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodgui.h @@ -0,0 +1,128 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMODGUI_H +#define INCLUDE_PAGERDEMODGUI_H + +#include + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "util/messagequeue.h" + +#include "pagerdemodsettings.h" +#include "pagerdemod.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +class ScopeVis; +class PagerDemod; +class PagerDemodGUI; + +namespace Ui { + class PagerDemodGUI; +} +class PagerDemodGUI; + +class PagerDemodGUI : public ChannelGUI { + Q_OBJECT + +public: + static PagerDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +public slots: + void channelMarkerChangedByCursor(); + void channelMarkerHighlightedByCursor(); + +private: + Ui::PagerDemodGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + PagerDemodSettings m_settings; + bool m_doApplySettings; + ScopeVis* m_scopeVis; + + PagerDemod* m_pagerDemod; + uint32_t m_tickCount; + MessageQueue m_inputMessageQueue; + + QMenu *messagesMenu; // Column select context menu + + explicit PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~PagerDemodGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void displayStreamIndex(); + void messageReceived(const PagerDemod::MsgPagerMessage& message); + bool handleMessage(const Message& message); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + + void resizeTable(); + QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot); + + enum MessageCol { + MESSAGE_COL_DATE, + MESSAGE_COL_TIME, + MESSAGE_COL_ADDRESS, + MESSAGE_COL_MESSAGE, + MESSAGE_COL_FUNCTION, + MESSAGE_COL_ALPHA, + MESSAGE_COL_NUMERIC, + MESSAGE_COL_EVEN_PE, + MESSAGE_COL_BCH_PE + }; + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_rfBW_valueChanged(int index); + void on_fmDev_valueChanged(int value); + void on_baud_currentIndexChanged(int index); + void on_decode_currentIndexChanged(int index); + void on_filterAddress_editingFinished(); + void on_clearTable_clicked(); + void on_udpEnabled_clicked(bool checked); + void on_udpAddress_editingFinished(); + void on_udpPort_editingFinished(); + void on_channel1_currentIndexChanged(int index); + void on_channel2_currentIndexChanged(int index); + void filterRow(int row); + void filter(); + void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void messages_sectionResized(int logicalIndex, int oldSize, int newSize); + void messagesColumnSelectMenu(QPoint pos); + void messagesColumnSelectMenuChecked(bool checked = false); + void customContextMenuRequested(QPoint point); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void tick(); +}; + +#endif // INCLUDE_PAGERDEMODGUI_H diff --git a/plugins/channelrx/demodpager/pagerdemodgui.ui b/plugins/channelrx/demodpager/pagerdemodgui.ui new file mode 100644 index 000000000..44d748ee2 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodgui.ui @@ -0,0 +1,985 @@ + + + PagerDemodGUI + + + + 0 + 0 + 404 + 764 + + + + + 0 + 0 + + + + + 352 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + Pager Demodulator + + + Pager Demodulator + + + + + 0 + 0 + 390 + 171 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + Qt::Horizontal + + + + + + + + + BW + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + RF bandwidth + + + 10 + + + 400 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 10.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Dev + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Frequency deviation + + + 10 + + + 50 + + + 1 + + + 24 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 2.4k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Mod + + + + + + + + 76 + 0 + + + + Pager modulation + + + + POCSAC + + + + + + + + Qt::Vertical + + + + + + + Baud + + + + + + + + 60 + 0 + + + + Baud rate + + + 1 + + + + 512 + + + + + 1200 + + + + + 2400 + + + + + + + + Qt::Vertical + + + + + + + Decode + + + + + + + + 80 + 0 + + + + How to choose between decoding a message as numeric or alphanumeric + + + + Standard + + + + + Inverted + + + + + Numeric + + + + + Alphanumeric + + + + + Heuristic + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + Forward messages via UDP + + + Qt::RightToLeft + + + UDP + + + + + + + + 120 + 0 + + + + Qt::ClickFocus + + + Destination UDP address + + + 000.000.000.000 + + + 127.0.0.1 + + + + + + + : + + + Qt::AlignCenter + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Qt::ClickFocus + + + Destination UDP port + + + 00000 + + + 9998 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + Find + + + + + + + Display only messages where the address matches the specified regular expression + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear messages from table + + + + + + + :/bin.png:/bin.png + + + + + + + + + + + 0 + 210 + 391 + 171 + + + + + 0 + 0 + + + + Received Messages + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Received messages + + + QAbstractItemView::NoEditTriggers + + + + Date + + + Date message was received + + + + + Time + + + Time message was received + + + + + Address + + + 21-bit pager address the message is for + + + + + Message + + + + + Function + + + Function bits / source address + + + + + Alpha + + + Message decoded as 7-bit alphanumeric characters + + + + + Numeric + + + Message decoded as 4-bit BCD numeric characters + + + + + Even PE + + + Number of even parity errors detected in message + + + + + BCH PE + + + Number of BCH parity errors detected in message + + + + + + + + + + 20 + 400 + 351 + 341 + + + + Waveforms + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Real + + + + + + + + 0 + 0 + + + + Signal to send to scope as real part + + + + I + + + + + Q + + + + + Mag Sq + + + + + FM demod + + + + + Filt + + + + + DC offset + + + + + Data + + + + + Sample + + + + + Bit + + + + + GotSyncCode + + + + + + + + + 0 + 0 + + + + Imag + + + + + + + + 0 + 0 + + + + Signal to send to scope as imag part + + + + I + + + + + Q + + + + + Mag Sq + + + + + FM demod + + + + + Filt + + + + + DC offset + + + + + Data + + + + + Sample + + + + + Bit + + + + + GotSyncCode + + + + + + + + + + + 200 + 250 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + GLScope + QWidget +
gui/glscope.h
+ 1 +
+ + GLScopeGUI + QWidget +
gui/glscopegui.h
+ 1 +
+
+ + + + +
diff --git a/plugins/channelrx/demodpager/pagerdemodplugin.cpp b/plugins/channelrx/demodpager/pagerdemodplugin.cpp new file mode 100644 index 000000000..5ce60f6a7 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodplugin.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "pagerdemodgui.h" +#endif +#include "pagerdemod.h" +#include "pagerdemodwebapiadapter.h" +#include "pagerdemodplugin.h" + +const PluginDescriptor PagerDemodPlugin::m_pluginDescriptor = { + PagerDemod::m_channelId, + QStringLiteral("Pager Demodulator"), + QStringLiteral("6.16.0"), + QStringLiteral("(c) Jon Beniston, M7RCE"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +PagerDemodPlugin::PagerDemodPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& PagerDemodPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void PagerDemodPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerRxChannel(PagerDemod::m_channelIdURI, PagerDemod::m_channelId, this); +} + +void PagerDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + PagerDemod *instance = new PagerDemod(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* PagerDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) const +{ + (void) deviceUISet; + (void) rxChannel; + return 0; +} +#else +ChannelGUI* PagerDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const +{ + return PagerDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +ChannelWebAPIAdapter* PagerDemodPlugin::createChannelWebAPIAdapter() const +{ + return new PagerDemodWebAPIAdapter(); +} diff --git a/plugins/channelrx/demodpager/pagerdemodplugin.h b/plugins/channelrx/demodpager/pagerdemodplugin.h new file mode 100644 index 000000000..04fb5ec64 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMODPLUGIN_H +#define INCLUDE_PAGERDEMODPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class PagerDemodPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.pagerdemod") + +public: + explicit PagerDemodPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const; + virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const; + virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_PAGERDEMODPLUGIN_H diff --git a/plugins/channelrx/demodpager/pagerdemodsettings.cpp b/plugins/channelrx/demodpager/pagerdemodsettings.cpp new file mode 100644 index 000000000..37175dd6b --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodsettings.cpp @@ -0,0 +1,174 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "pagerdemodsettings.h" + +PagerDemodSettings::PagerDemodSettings() : + m_channelMarker(0), + m_scopeGUI(0) +{ + resetToDefaults(); +} + +void PagerDemodSettings::resetToDefaults() +{ + m_baud = 1200; + m_inputFrequencyOffset = 0; + m_rfBandwidth = 20000.0f; + m_fmDeviation = 4500.0f; + m_decode = Standard; + m_filterAddress = ""; + m_udpEnabled = false; + m_udpAddress = "127.0.0.1"; + m_udpPort = 9999; + m_scopeCh1 = 4; + m_scopeCh2 = 9; + m_rgbColor = QColor(200, 191, 231).rgb(); + m_title = "Pager Demodulator"; + m_streamIndex = 0; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; + + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) + { + m_messageColumnIndexes[i] = i; + m_messageColumnSizes[i] = -1; // Autosize + } +} + +QByteArray PagerDemodSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_inputFrequencyOffset); + s.writeFloat(2, m_rfBandwidth); + s.writeFloat(3, m_fmDeviation); + s.writeS32(4, m_baud); + s.writeString(5, m_filterAddress); + s.writeS32(6, (int)m_decode); + s.writeBool(7, m_udpEnabled); + s.writeString(8, m_udpAddress); + s.writeU32(9, m_udpPort); + s.writeS32(10, m_scopeCh1); + s.writeS32(11, m_scopeCh2); + s.writeU32(12, m_rgbColor); + s.writeString(13, m_title); + if (m_channelMarker) { + s.writeBlob(14, m_channelMarker->serialize()); + } + s.writeS32(15, m_streamIndex); + s.writeBool(16, m_useReverseAPI); + s.writeString(17, m_reverseAPIAddress); + s.writeU32(18, m_reverseAPIPort); + s.writeU32(19, m_reverseAPIDeviceIndex); + s.writeU32(20, m_reverseAPIChannelIndex); + s.writeBlob(21, m_scopeGUI->serialize()); + + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { + s.writeS32(100 + i, m_messageColumnIndexes[i]); + } + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { + s.writeS32(200 + i, m_messageColumnSizes[i]); + } + + return s.final(); +} + +bool PagerDemodSettings::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.readS32(1, &m_inputFrequencyOffset, 0); + d.readFloat(2, &m_rfBandwidth, 20000.0f); + d.readFloat(3, &m_fmDeviation, 4500.0f); + d.readS32(4, &m_baud, 1200); + d.readString(5, &m_filterAddress, ""); + d.readS32(6, (int*)&m_decode, (int)Standard); + d.readBool(7, &m_udpEnabled); + d.readString(8, &m_udpAddress); + d.readU32(9, &utmp); + if ((utmp > 1023) && (utmp < 65535)) { + m_udpPort = utmp; + } else { + m_udpPort = 9999; + } + d.readS32(10, &m_scopeCh1, 4); + d.readS32(11, &m_scopeCh2, 9); + d.readU32(12, &m_rgbColor, QColor(200, 191, 231).rgb()); + d.readString(13, &m_title, "Pager Demodulator"); + d.readBlob(14, &bytetmp); + if (m_channelMarker) { + m_channelMarker->deserialize(bytetmp); + } + d.readS32(15, &m_streamIndex, 0); + d.readBool(16, &m_useReverseAPI, false); + d.readString(17, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(18, &utmp, 0); + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + d.readU32(19, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(20, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + if (m_scopeGUI) + { + d.readBlob(21, &bytetmp); + m_scopeGUI->deserialize(bytetmp); + } + + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { + d.readS32(100 + i, &m_messageColumnIndexes[i], i); + } + for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { + d.readS32(200 + i, &m_messageColumnSizes[i], -1); + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/channelrx/demodpager/pagerdemodsettings.h b/plugins/channelrx/demodpager/pagerdemodsettings.h new file mode 100644 index 000000000..2df12a39b --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodsettings.h @@ -0,0 +1,76 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMODSETTINGS_H +#define INCLUDE_PAGERDEMODSETTINGS_H + +#include +#include + +#include "dsp/dsptypes.h" + +class Serializable; + +// Number of columns in the tables +#define PAGERDEMOD_MESSAGE_COLUMNS 9 + +struct PagerDemodSettings +{ + qint32 m_baud; //!< 512, 1200 or 2400 + qint32 m_inputFrequencyOffset; + Real m_rfBandwidth; + Real m_fmDeviation; //. // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "dsp/dspengine.h" +#include "dsp/datafifo.h" +#include "dsp/scopevis.h" +#include "util/db.h" +#include "util/popcount.h" +#include "maincore.h" + +#include "pagerdemod.h" +#include "pagerdemodsink.h" + +PagerDemodSink::PagerDemodSink(PagerDemod *pagerDemod) : + m_scopeSink(nullptr), + m_pagerDemod(pagerDemod), + m_channelSampleRate(PagerDemodSettings::m_channelSampleRate), + m_channelFrequencyOffset(0), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_messageQueueToChannel(nullptr), + m_dcOffset(0.0f), + m_dataPrev(0), + m_inverted(false), + m_bit(0), + m_gotSOP(false), + m_bits(0), + m_bitCount(0), + m_syncCount(75), + m_batchNumber(0), + m_wordCount(0), + m_addressValid(0) +{ + m_magsq = 0.0; + + m_demodBuffer.resize(1<<12); + m_demodBufferFill = 0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +PagerDemodSink::~PagerDemodSink() +{ +} + +void PagerDemodSink::sampleToScope(Complex sample) +{ + if (m_scopeSink) + { + ComplexVector m_sampleBuffer; + m_sampleBuffer.push_back(sample); + std::vector vbegin; + vbegin.push_back(m_sampleBuffer.begin()); + m_scopeSink->feed(vbegin, m_sampleBuffer.end() - m_sampleBuffer.begin()); + m_sampleBuffer.clear(); + } +} + +void PagerDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else // decimate + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } +} + +// XOR bits together for parity check +int PagerDemodSink::xorBits(quint32 word, int firstBit, int lastBit) +{ + int x = 0; + for (int i = firstBit; i <= lastBit; i++) + { + x ^= (word >> i) & 1; + } + return x; +} + +// Check for even parity +bool PagerDemodSink::evenParity(quint32 word, int firstBit, int lastBit, int parityBit) +{ + return xorBits(word, firstBit, lastBit) == parityBit; +} + +// Reverse order of bits +quint32 PagerDemodSink::reverse(quint32 x) +{ + x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1)); + x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2)); + x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4)); + x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8)); + return((x >> 16) | (x << 16)); +} + +// Calculate BCH parity and even parity bits +quint32 PagerDemodSink::bchEncode(const quint32 cw) +{ + quint32 bit = 0; + quint32 parity = 0; + quint32 localCW = cw & 0xFFFFF800; // Mask off BCH parity and even parity bits + quint32 cwE = localCW; + + // Calculate BCH bits + for (bit = 1; bit <= 21; bit++) + { + if (cwE & 0x80000000) { + cwE ^= 0xED200000; + } + cwE <<= 1; + } + localCW |= (cwE >> 21); + + return localCW; +} + +// Use BCH decoding to try to fix any bit errors +// Returns true if able to be decode/repair successfull +// See: https://www.eevblog.com/forum/microcontrollers/practical-guides-to-bch-fec/ +bool PagerDemodSink::bchDecode(const quint32 cw, quint32& correctedCW) +{ + // Calculate syndrome + // We do this by recalculating the BCH parity bits and XORing them against the received ones + quint32 syndrome = ((bchEncode(cw) ^ cw) >> 1) & 0x3FF; + + if (syndrome == 0) + { + // Syndrome of zero indicates no repair required + correctedCW = cw; + return true; + } + + // Meggitt decoder + + quint32 result = 0; + quint32 damagedCW = cw; + + // Calculate BCH bits + for (quint32 xbit = 0; xbit < 31; xbit++) + { + // Produce the next corrected bit in the high bit of the result + result <<= 1; + if ((syndrome == 0x3B4) || // 0x3B4: Syndrome when a single error is detected in the MSB + (syndrome == 0x26E) || // 0x26E: Two adjacent errors + (syndrome == 0x359) || // 0x359: Two errors, one OK bit between + (syndrome == 0x076) || // 0x076: Two errors, two OK bits between + (syndrome == 0x255) || // 0x255: Two errors, three OK bits between + (syndrome == 0x0F0) || // 0x0F0: Two errors, four OK bits between + (syndrome == 0x216) || + (syndrome == 0x365) || + (syndrome == 0x068) || + (syndrome == 0x25A) || + (syndrome == 0x343) || + (syndrome == 0x07B) || + (syndrome == 0x1E7) || + (syndrome == 0x129) || + (syndrome == 0x14E) || + (syndrome == 0x2C9) || + (syndrome == 0x0BE) || + (syndrome == 0x231) || + (syndrome == 0x0C2) || + (syndrome == 0x20F) || + (syndrome == 0x0DD) || + (syndrome == 0x1B4) || + (syndrome == 0x2B4) || + (syndrome == 0x334) || + (syndrome == 0x3F4) || + (syndrome == 0x394) || + (syndrome == 0x3A4) || + (syndrome == 0x3BC) || + (syndrome == 0x3B0) || + (syndrome == 0x3B6) || + (syndrome == 0x3B5) + ) + { + // Syndrome matches an error in the MSB + // Correct that error and adjust the syndrome to account for it + syndrome ^= 0x3B4; + result |= (~damagedCW & 0x80000000) >> 30; + } + else + { + // No error + result |= (damagedCW & 0x80000000) >> 30; + } + damagedCW <<= 1; + + // Handle syndrome shift register feedback + if (syndrome & 0x200) + { + syndrome <<= 1; + syndrome ^= 0x769; // 0x769 = POCSAG generator polynomial -- x^10 + x^9 + x^8 + x^6 + x^5 + x^3 + 1 + } + else + { + syndrome <<= 1; + } + // Mask off bits which fall off the end of the syndrome shift register + syndrome &= 0x3FF; + } + + // Check if error correction was successful + if (syndrome != 0) + { + // Syndrome nonzero at end indicates uncorrectable errors + correctedCW = cw; + return false; + } + + correctedCW = result; + return true; +} + +// Decode a batch of codewords to addresses and messages +// Messages may be spreadout over multiple batches +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-1-198607-S!!PDF-E.pdf +// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf +void PagerDemodSink::decodeBatch() +{ + int i = 1; + for (int frame = 0; frame < PAGERDEMOD_FRAMES_PER_BATCH; frame++) + { + for (int word = 0; word < PAGERDEMOD_CODEWORDS_PER_FRAME; word++) + { + bool addressCodeWord = ((m_codeWords[i] >> 31) & 1) == 0; + + // Stop decoding current message if we receive a new address + if (addressCodeWord && m_addressValid) + { + m_numericMessage = m_numericMessage.trimmed(); // Remove trailing spaces + if (getMessageQueueToChannel()) + { + PagerDemod::MsgPagerMessage *msg = PagerDemod::MsgPagerMessage::create(m_address, m_functionBits, m_alphaMessage, m_numericMessage, m_parityErrors, m_bchErrors); + getMessageQueueToChannel()->push(msg); + } + m_addressValid = false; + } + + // Check parity bit + bool parityError = !evenParity(m_codeWords[i], 1, 31, m_codeWords[i] & 0x1); + + if (m_codeWords[i] == PAGERDEMOD_POCSAG_IDLECODE) + { + // Idle + } + else if (addressCodeWord) + { + // Address + int checkBits = (m_codeWords[i] >> 1) & 0x3f; + m_functionBits = (m_codeWords[i] >> 11) & 0x3; + int addressBits = (m_codeWords[i] >> 13) & 0x3ffff; + m_address = (addressBits << 3) | frame; + m_numericMessage = ""; + m_alphaMessage = ""; + m_alphaBitBufferBits = 0; + m_alphaBitBuffer = 0; + m_parityErrors = parityError ? 1 : 0; + m_bchErrors = m_codeWordsBCHError[i] ? 1 : 0; + m_addressValid = true; + } + else + { + // Message - decode as both numeric and ASCII - not all operators use functionBits to indidcate encoding + int checkBits = (m_codeWords[i] >> 1) & 0x3f; + int messageBits = (m_codeWords[i] >> 11) & 0xfffff; + if (parityError) { + m_parityErrors++; + } + if (m_codeWordsBCHError[i]) { + m_bchErrors++; + } + + // Numeric format + for (int j = 16; j >= 0; j -= 4) + { + quint32 numericBits = (messageBits >> j) & 0xf; + numericBits = reverse(numericBits) >> (32-4); + // Spec has 0xa as 'spare', but other decoders treat is as . + const char numericChars[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'U', ' ', '-', ')', '(' + }; + char numericChar = numericChars[numericBits]; + m_numericMessage.append(numericChar); + } + + // 7-bit ASCII alpnanumeric format + m_alphaBitBuffer = (m_alphaBitBuffer << 20) | messageBits; + m_alphaBitBufferBits += 20; + while (m_alphaBitBufferBits >= 7) + { + // Extract next 7-bit character from bit buffer + char c = (m_alphaBitBuffer >> (m_alphaBitBufferBits-7)) & 0x7f; + // Reverse bit ordering + c = reverse(c) >> (32-7); + // Add to received message string (excluding, null, end of text, end ot transmission) + if (c != 0 && c != 0x3 && c != 0x4) { + m_alphaMessage.append(c); + } + // Remove from bit buffer + m_alphaBitBufferBits -= 7; + if (m_alphaBitBufferBits == 0) { + m_alphaBitBuffer = 0; + } else { + m_alphaBitBuffer &= (1 << m_alphaBitBufferBits) - 1; + } + } + } + + // Move to next codeword + i++; + } + } +} + +void PagerDemodSink::processOneSample(Complex &ci) +{ + Complex ca; + + // FM demodulation + double magsqRaw; + Real deviation; + Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); + + // Calculate average and peak levels for level meter + Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + m_magsqCount++; + + // Low pass filter + Real filt = m_lowpassBaud.filter(fmDemod); + + // An input frequency offset corresponds to a DC offset after FM demodulation + // To calculate what it is, we average part of the preamble, which should be zero + if (!m_gotSOP) + { + m_preambleMovingAverage(filt); + m_dcOffset = m_preambleMovingAverage.asDouble(); + } + + bool sample = false; + + // Slice data + int data = (filt - m_dcOffset) >= 0.0; + + // Look for edge - A PLL here would be less susceptible to noise + if (data != m_dataPrev) + { + // Center in middle of bit + m_syncCount = m_samplesPerSymbol/2; + } + else + { + // Wait until centre of bit to sample it + m_syncCount--; + if (m_syncCount <= 0) + { + // According to a variety of places on the web, high frequency is a 0, low is 1. + // While this seems to be correct in the UK, some IQ files I've obtained seem + // to be reversed, so we support both. + if (m_inverted) { + m_bit = data; + } else { + m_bit = !data; + } + sample = true; + + // Store in shift reg. MSB transmitted first + m_bits = (m_bits << 1) | m_bit; + m_bitCount++; + if (m_bitCount > 32) { + m_bitCount = 32; + } + + if ((m_bitCount == 32) && !m_gotSOP) + { + // Look for synccode that starts a batch - allow three errors that can be corrected + if (m_bits == PAGERDEMOD_POCSAG_SYNCCODE) + { + m_gotSOP = true; + m_inverted = false; + } + else if (m_bits == PAGERDEMOD_POCSAG_SYNCCODE_INV) + { + m_gotSOP = true; + m_inverted = true; + } + else if (popcount(m_bits ^ PAGERDEMOD_POCSAG_SYNCCODE) >= 29) + { + quint32 correctedCW; + if (bchDecode(m_bits, correctedCW) && (correctedCW == PAGERDEMOD_POCSAG_SYNCCODE)) + { + m_gotSOP = true; + m_inverted = false; + } + } + else if (popcount(m_bits ^ PAGERDEMOD_POCSAG_SYNCCODE_INV) >= 29) + { + quint32 correctedCW; + { + qDebug() << "Corrected inverted sync codeword"; + m_gotSOP = true; + m_inverted = true; + } + } + + if (m_gotSOP) + { + // Reset demod state + m_bits = 0; + m_bitCount = 0; + m_codeWords[0] = PAGERDEMOD_POCSAG_SYNCCODE; + m_wordCount = 1; + m_addressValid = false; + } + } + else if ((m_bitCount == 32) && m_gotSOP) + { + // Got a complete codeword - use BCH decoding to fix any bit errors + quint32 correctedCW; + m_codeWordsBCHError[m_wordCount] = !bchDecode(m_bits, correctedCW); + m_codeWords[m_wordCount] = correctedCW; + m_wordCount++; + + // Check for sync code at start of batch + if ((m_wordCount == 1) && (correctedCW != PAGERDEMOD_POCSAG_SYNCCODE)) + { + m_gotSOP = false; + //m_thresholdMet = false; + m_addressValid = false; + m_inverted = false; + } + + // Have we received a complete batch + if (m_wordCount == PAGERDEMOD_BATCH_WORDS) + { + // Decode it to addresses and messages + decodeBatch(); + + // Start a new batch + m_batchNumber++; + m_wordCount = 0; + } + + m_bits = 0; + m_bitCount = 0; + } + + m_syncCount = m_samplesPerSymbol; + } + } + + // Save data for edge detection + m_dataPrev = data; + + // Select signals to feed to scope + Complex scopeSample; + switch (m_settings.m_scopeCh1) + { + case 0: + scopeSample.real(ci.real() / SDR_RX_SCALEF); + break; + case 1: + scopeSample.real(ci.imag() / SDR_RX_SCALEF); + break; + case 2: + scopeSample.real(magsq); + break; + case 3: + scopeSample.real(fmDemod); + break; + case 4: + scopeSample.real(filt); + break; + case 5: + scopeSample.real(m_dcOffset); + break; + case 6: + scopeSample.real(data); + break; + case 7: + scopeSample.real(sample); + break; + case 8: + scopeSample.real(m_bit); + break; + case 9: + scopeSample.real(m_gotSOP); + break; + } + switch (m_settings.m_scopeCh2) + { + case 0: + scopeSample.imag(ci.real() / SDR_RX_SCALEF); + break; + case 1: + scopeSample.imag(ci.imag() / SDR_RX_SCALEF); + break; + case 2: + scopeSample.imag(magsq); + break; + case 3: + scopeSample.imag(fmDemod); + break; + case 4: + scopeSample.imag(filt); + break; + case 5: + scopeSample.imag(m_dcOffset); + break; + case 6: + scopeSample.imag(data); + break; + case 7: + scopeSample.imag(sample); + break; + case 8: + scopeSample.imag(m_bit); + break; + case 9: + scopeSample.imag(m_gotSOP); + break; + } + sampleToScope(scopeSample); + + // Send demod signal to Demod Analzyer feature + m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits::max(); + + if (m_demodBufferFill >= m_demodBuffer.size()) + { + QList *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod"); + + if (dataFifos) + { + QList::iterator it = dataFifos->begin(); + + for (; it != dataFifos->end(); ++it) { + (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16), DataFifo::DataTypeI16); + } + } + + m_demodBufferFill = 0; + } +} + +void PagerDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "PagerDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if ((m_channelFrequencyOffset != channelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); + m_interpolatorDistance = (Real) channelSampleRate / (Real) PagerDemodSettings::m_channelSampleRate; + m_interpolatorDistanceRemain = m_interpolatorDistance; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void PagerDemodSink::applySettings(const PagerDemodSettings& settings, bool force) +{ + qDebug() << "PagerDemodSink::applySettings:" + << " rfBandwidth: " << settings.m_rfBandwidth + << " fmDeviation: " << settings.m_fmDeviation + << " baud: " << settings.m_baud + << " force: " << force; + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) PagerDemodSettings::m_channelSampleRate; + m_interpolatorDistanceRemain = m_interpolatorDistance; + m_lowpass.create(301, PagerDemodSettings::m_channelSampleRate, settings.m_rfBandwidth / 2.0f); + } + + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) + { + m_phaseDiscri.setFMScaling(PagerDemodSettings::m_channelSampleRate / (2.0f * settings.m_fmDeviation)); + } + + if ((settings.m_baud != m_settings.m_baud) || force) + { + m_samplesPerSymbol = PagerDemodSettings::m_channelSampleRate / settings.m_baud; + qDebug() << "PagerDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol; + + // Signal is a square wave - so include several harmonics + m_lowpassBaud.create(301, PagerDemodSettings::m_channelSampleRate, settings.m_baud * 5.0f); + } + + m_settings = settings; +} diff --git a/plugins/channelrx/demodpager/pagerdemodsink.h b/plugins/channelrx/demodpager/pagerdemodsink.h new file mode 100644 index 000000000..9f15aded5 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodsink.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMODSINK_H +#define INCLUDE_PAGERDEMODSINK_H + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/firfilter.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" + +#include "pagerdemodsettings.h" + +#define PAGERDEMOD_FRAMES_PER_BATCH 8 +#define PAGERDEMOD_CODEWORDS_PER_FRAME 2 +#define PAGERDEMOD_BATCH_WORDS (1+(PAGERDEMOD_FRAMES_PER_BATCH*PAGERDEMOD_CODEWORDS_PER_FRAME)) +#define PAGERDEMOD_POCSAG_SYNCCODE 0x7CD215D8 +#define PAGERDEMOD_POCSAG_SYNCCODE_INV ((quint32)~PAGERDEMOD_POCSAG_SYNCCODE) +#define PAGERDEMOD_POCSAG_IDLECODE 0x7a89c197 /* 0x7ac9c197 in spec */ + +class ChannelAPI; +class PagerDemod; +class ScopeVis; + +class PagerDemodSink : public ChannelSampleSink { +public: + PagerDemodSink(PagerDemod *pagerDemod); + ~PagerDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void setScopeSink(ScopeVis* scopeSink) { m_scopeSink = scopeSink; } + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const PagerDemodSettings& settings, bool force = false); + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + void setChannel(ChannelAPI *channel) { m_channel = channel; } + + double getMagSq() const { return m_magsq; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + ScopeVis* m_scopeSink; // Scope GUI to display debug waveforms + PagerDemod *m_pagerDemod; + PagerDemodSettings m_settings; + ChannelAPI *m_channel; + int m_channelSampleRate; + int m_channelFrequencyOffset; + int m_samplesPerSymbol; // Number of samples per symbol + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MessageQueue *m_messageQueueToChannel; + + MovingAverageUtil m_preambleMovingAverage; + + MovingAverageUtil m_movingAverage; + + Lowpass m_lowpass; // RF input filter + PhaseDiscriminators m_phaseDiscri; // FM demodulator + Lowpass m_lowpassBaud; // Low pass filter for FM demod output + Real m_dcOffset; // Calculated DC offset of preamble + int m_dataPrev; // m_data for previous sample + bool m_inverted; // Whether low frequency is a 1 or 0 + int m_bit; // Sampled bit + bool m_gotSOP; // Set when sync word received + quint32 m_bits; // Received bit shift register + int m_bitCount; // Number of bits in m_bits + int m_syncCount; // Sample count to centre of bit + + int m_batchNumber; // Count of batches in current transmission + quint32 m_codeWords[PAGERDEMOD_BATCH_WORDS]; // Received codewords within a batch + bool m_codeWordsBCHError[PAGERDEMOD_BATCH_WORDS]; // Records if BCH error when decoding this codeword + int m_wordCount; // Count of number of receive codewords + + bool m_addressValid; // Indicates we received a (non-idle) address + quint32 m_address; // 21-bit address of current message + int m_functionBits; // 0 = Numeric only, 3 = 7-bit ASCII (CCITT Alphabet No. 5) + int m_parityErrors; // Count of parity errors in current message + int m_bchErrors; // Count of BCH errors in current message + QString m_numericMessage; // Message decoded in numeric character set + QString m_alphaMessage; // Message decoded in to alphanumeric character set + quint32 m_alphaBitBuffer; // Bit buffer to 7-bit chars spread across codewords + int m_alphaBitBufferBits; // Count of bits in m_alphaBitBuffer + + QVector m_demodBuffer; + int m_demodBufferFill; + + void processOneSample(Complex &ci); + MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } + void sampleToScope(Complex sample); + void decodeBatch(); + int xorBits(quint32 word, int firstBit, int lastBit); + bool evenParity(quint32 word, int firstBit, int lastBit, int parityBit); + quint32 reverse(quint32 x); + quint32 bchEncode(const quint32 cw); + bool bchDecode(const quint32 cw, quint32& correctedCW); + +}; + +#endif // INCLUDE_PAGERDEMODSINK_H diff --git a/plugins/channelrx/demodpager/pagerdemodwebapiadapter.cpp b/plugins/channelrx/demodpager/pagerdemodwebapiadapter.cpp new file mode 100644 index 000000000..4a582e951 --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGChannelSettings.h" +#include "pagerdemod.h" +#include "pagerdemodwebapiadapter.h" + +PagerDemodWebAPIAdapter::PagerDemodWebAPIAdapter() +{} + +PagerDemodWebAPIAdapter::~PagerDemodWebAPIAdapter() +{} + +int PagerDemodWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings()); + response.getPagerDemodSettings()->init(); + PagerDemod::webapiFormatChannelSettings(response, m_settings); + + return 200; +} + +int PagerDemodWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; + (void) errorMessage; + PagerDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response); + + return 200; +} diff --git a/plugins/channelrx/demodpager/pagerdemodwebapiadapter.h b/plugins/channelrx/demodpager/pagerdemodwebapiadapter.h new file mode 100644 index 000000000..6a037ea3e --- /dev/null +++ b/plugins/channelrx/demodpager/pagerdemodwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PAGERDEMOD_WEBAPIADAPTER_H +#define INCLUDE_PAGERDEMOD_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "pagerdemodsettings.h" + +/** + * Standalone API adapter only for the settings + */ +class PagerDemodWebAPIAdapter : public ChannelWebAPIAdapter { +public: + PagerDemodWebAPIAdapter(); + virtual ~PagerDemodWebAPIAdapter(); + + virtual QByteArray serialize() const { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + +private: + PagerDemodSettings m_settings; +}; + +#endif // INCLUDE_PAGERDEMOD_WEBAPIADAPTER_H diff --git a/plugins/channelrx/demodpager/readme.md b/plugins/channelrx/demodpager/readme.md new file mode 100644 index 000000000..b4b92c24e --- /dev/null +++ b/plugins/channelrx/demodpager/readme.md @@ -0,0 +1,95 @@ +

Pager demodulator plugin

+ +

Introduction

+ +This plugin can be used to demodulate POCSAG pager messages. + +

Interface

+ +![Pager Demodulator plugin GUI](../../../doc/img/PagerDemod_plugin.png) + +

1: Frequency shift from center frequency of reception

+ +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

2: Channel power

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

3: Level meter in dB

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

4: BW - RF Bandwidth

+ +This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. + +

5: Dev - Frequency deviation

+ +Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. POCSAG uses FSK with a +/- 4.5 kHz shift. + +

6: Mod - Modulation

+ +Species the pager modulation. Currently only POCSAG is supported. + +POCSAG uses FSK with 4.5kHz frequency shift, at 512, 1200 or 2400 baud. +High frequency is typically 0, with low 1, but occasionaly this appears to be reversed, so the demodulator supports either. +Data is framed as specified in ITU-R M.584-2: https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf + +

7: Baud

+ +Specifies the baud rate. For POCSAG, this can be 512, 1200 or 2400. + +

8: Decode

+ +Specifies how messages are decoded in the Message column in the table: + +* Standard - As per ITU-R M.584-2 - Function 0 = numeric, function 1-3 = alphanumeric. +* Inverted - Function 3 = numeric, function 0-2 = alphanumeric. +* Numeric - Always decode as numeric. +* Alphanumeric - Always decode as alphanumeric. +* Heuristic - The plugin will try to guess based on the content of the message. + +The table has Numeric and Alphanumeric columns which always display the corresponding decode. + +

9: Find

+ +Entering a regular expression in the Find field displays only messages where the address matches the given regular expression. + +

10: Clear Messages from table

+ +Pressing this button clears all messages from the table. + +

11: UDP

+ +When checked, received messages are forwarded to the specified UDP address (12) and port (13). + +The messages are forwarded as null termiated ASCII strings, in the format: data time address function alpha numeric + +

12: UDP address

+ +IP address of the host to forward received messages to via UDP. + +

13: UDP port

+ +UDP port number to forward received messages to. + +

Received Messages Table

+ +The received messages table displays each pager message received. + +![Pager Demodulator messages table](../../../doc/img/PagerDemod_plugin_messages.png) + +* Date - The date the message was received. +* Time - The time the message was received. +* Address - The 21-bit pager address the message is for. +* Message - The decoded message, as per Decode setting (8). This will be empty for "tone only" messages (i.e. when the pager beeps without a message). +* Function - Function bits. In some instances this is used to specify the encoding of the message (Numeric/alphanumeric). In others, it is the pager source address. +* Alpha - Message decoded as alphanumeric text, regardless of Decode setting (8) +* Numeric - Message decoded as numeric, regardless of Decode setting (8). +* Even PE - Number of even parity errors detected in the code words of the message. +* BCH PE - Number of uncorrectable BCH parity errors detected in the code words of the message. + +Right clicking on the table header allows you to select which columns to show. The columns can be reorderd by left clicking and dragging the column header. Right clicking on an item in the table allows you to copy the value to the clipboard. diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 9393bd8bf..abc72b147 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -3961,6 +3961,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setPacketModSettings(new SWGSDRangel::SWGPacketModSettings()); channelSettings->getPacketModSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "PagerDemodSettings") + { + channelSettings->setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings()); + channelSettings->getPagerDemodSettings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "RadioClockSettings") { channelSettings->setRadioClockSettings(new SWGSDRangel::SWGRadioClockSettings()); @@ -4659,6 +4664,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setNoiseFigureSettings(nullptr); channelSettings.setPacketDemodSettings(nullptr); channelSettings.setPacketModSettings(nullptr); + channelSettings.setPagerDemodSettings(nullptr); channelSettings.setRemoteSinkSettings(nullptr); channelSettings.setRemoteSourceSettings(nullptr); channelSettings.setSsbDemodSettings(nullptr); diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index 5ce7698d3..07fa35b6e 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -54,6 +54,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap {"sdrangel.channel.localsource", "LocalSourceSettings"}, {"sdrangel.channel.packetdemod", "PacketDemodSettings"}, + {"sdrangel.channel.pagerdemod", "PagerDemodSettings"}, {"sdrangel.channeltx.modpacket", "PacketModSettings"}, {"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"}, {"sdrangel.channel.radioclock", "RadioClockSettings"}, @@ -152,6 +153,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"NoiseFigure", "NoiseFigureSettings"}, {"PacketDemod", "PacketDemodSettings"}, {"PacketMod", "PacketModSettings"}, + {"PagerDemod", "PagerDemodSettings"}, {"LocalSink", "LocalSinkSettings"}, {"LocalSource", "LocalSourceSettings"}, {"RadioClock", "RadioClockSettings"}, diff --git a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml index eb4917333..b3cd88b35 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelReport.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelReport.yaml @@ -59,6 +59,8 @@ ChannelReport: $ref: "http://swgserver:8081/api/swagger/include/RemoteSource.yaml#/RemoteSourceReport" PacketModReport: $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModReport" + PagerDemodReport: + $ref: "http://swgserver:8081/api/swagger/include/PagerDemod.yaml#/PagerDemodReport" SigMFFileSinkReport: $ref: "http://swgserver:8081/api/swagger/include/SigMFFileSink.yaml#/SigMFFileSinkReport" SSBModReport: diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index 6ad687ace..1964ba6a2 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -79,6 +79,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/PacketDemod.yaml#/PacketDemodSettings" PacketModSettings: $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModSettings" + PagerDemodSettings: + $ref: "http://swgserver:8081/api/swagger/include/PagerDemod.yaml#/PagerDemodSettings" RadioClockSettings: $ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockSettings" RemoteSinkSettings: diff --git a/swagger/sdrangel/api/swagger/include/PagerDemod.yaml b/swagger/sdrangel/api/swagger/include/PagerDemod.yaml new file mode 100644 index 000000000..673520276 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/PagerDemod.yaml @@ -0,0 +1,58 @@ +PagerDemodSettings: + description: PagerDemod + properties: + baud: + description: "Baud rate" + type: integer + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + fmDeviation: + type: number + format: float + correlationThreshold: + type: number + format: float + udpEnabled: + description: "Whether to forward received messages to specified UDP port" + type: integer + udpAddress: + description: "UDP address to forward received messages to" + type: string + udpPort: + description: "UDP port to forward received messages to" + type: integer + udpFormat: + description: "0 for binary, 1 for NMEA" + type: integer + rgbColor: + type: integer + title: + type: string + streamIndex: + description: MIMO channel. Not relevant when connected to SI (single Rx). + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer + +PagerDemodReport: + description: PagerDemod + properties: + channelPowerDB: + description: power received in channel (dB) + type: number + format: float + channelSampleRate: + type: integer diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp index 316764c58..75eefce19 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.cpp @@ -80,6 +80,8 @@ SWGChannelReport::SWGChannelReport() { m_remote_source_report_isSet = false; packet_mod_report = nullptr; m_packet_mod_report_isSet = false; + pager_demod_report = nullptr; + m_pager_demod_report_isSet = false; sig_mf_file_sink_report = nullptr; m_sig_mf_file_sink_report_isSet = false; ssb_mod_report = nullptr; @@ -156,6 +158,8 @@ SWGChannelReport::init() { m_remote_source_report_isSet = false; packet_mod_report = new SWGPacketModReport(); m_packet_mod_report_isSet = false; + pager_demod_report = new SWGPagerDemodReport(); + m_pager_demod_report_isSet = false; sig_mf_file_sink_report = new SWGSigMFFileSinkReport(); m_sig_mf_file_sink_report_isSet = false; ssb_mod_report = new SWGSSBModReport(); @@ -252,6 +256,9 @@ SWGChannelReport::cleanup() { if(packet_mod_report != nullptr) { delete packet_mod_report; } + if(pager_demod_report != nullptr) { + delete pager_demod_report; + } if(sig_mf_file_sink_report != nullptr) { delete sig_mf_file_sink_report; } @@ -341,6 +348,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&packet_mod_report, pJson["PacketModReport"], "SWGPacketModReport", "SWGPacketModReport"); + ::SWGSDRangel::setValue(&pager_demod_report, pJson["PagerDemodReport"], "SWGPagerDemodReport", "SWGPagerDemodReport"); + ::SWGSDRangel::setValue(&sig_mf_file_sink_report, pJson["SigMFFileSinkReport"], "SWGSigMFFileSinkReport", "SWGSigMFFileSinkReport"); ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); @@ -451,6 +460,9 @@ SWGChannelReport::asJsonObject() { if((packet_mod_report != nullptr) && (packet_mod_report->isSet())){ toJsonValue(QString("PacketModReport"), packet_mod_report, obj, QString("SWGPacketModReport")); } + if((pager_demod_report != nullptr) && (pager_demod_report->isSet())){ + toJsonValue(QString("PagerDemodReport"), pager_demod_report, obj, QString("SWGPagerDemodReport")); + } if((sig_mf_file_sink_report != nullptr) && (sig_mf_file_sink_report->isSet())){ toJsonValue(QString("SigMFFileSinkReport"), sig_mf_file_sink_report, obj, QString("SWGSigMFFileSinkReport")); } @@ -739,6 +751,16 @@ SWGChannelReport::setPacketModReport(SWGPacketModReport* packet_mod_report) { this->m_packet_mod_report_isSet = true; } +SWGPagerDemodReport* +SWGChannelReport::getPagerDemodReport() { + return pager_demod_report; +} +void +SWGChannelReport::setPagerDemodReport(SWGPagerDemodReport* pager_demod_report) { + this->pager_demod_report = pager_demod_report; + this->m_pager_demod_report_isSet = true; +} + SWGSigMFFileSinkReport* SWGChannelReport::getSigMfFileSinkReport() { return sig_mf_file_sink_report; @@ -902,6 +924,9 @@ SWGChannelReport::isSet(){ if(packet_mod_report && packet_mod_report->isSet()){ isObjectUpdated = true; break; } + if(pager_demod_report && pager_demod_report->isSet()){ + isObjectUpdated = true; break; + } if(sig_mf_file_sink_report && sig_mf_file_sink_report->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h index 06a5be0d9..2b3ac035a 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelReport.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelReport.h @@ -43,6 +43,7 @@ #include "SWGNFMModReport.h" #include "SWGNoiseFigureReport.h" #include "SWGPacketModReport.h" +#include "SWGPagerDemodReport.h" #include "SWGRadioClockReport.h" #include "SWGRemoteSourceReport.h" #include "SWGSSBDemodReport.h" @@ -152,6 +153,9 @@ public: SWGPacketModReport* getPacketModReport(); void setPacketModReport(SWGPacketModReport* packet_mod_report); + SWGPagerDemodReport* getPagerDemodReport(); + void setPagerDemodReport(SWGPagerDemodReport* pager_demod_report); + SWGSigMFFileSinkReport* getSigMfFileSinkReport(); void setSigMfFileSinkReport(SWGSigMFFileSinkReport* sig_mf_file_sink_report); @@ -258,6 +262,9 @@ private: SWGPacketModReport* packet_mod_report; bool m_packet_mod_report_isSet; + SWGPagerDemodReport* pager_demod_report; + bool m_pager_demod_report_isSet; + SWGSigMFFileSinkReport* sig_mf_file_sink_report; bool m_sig_mf_file_sink_report_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index 1cc987963..fc37227fc 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -98,6 +98,8 @@ SWGChannelSettings::SWGChannelSettings() { m_packet_demod_settings_isSet = false; packet_mod_settings = nullptr; m_packet_mod_settings_isSet = false; + pager_demod_settings = nullptr; + m_pager_demod_settings_isSet = false; radio_clock_settings = nullptr; m_radio_clock_settings_isSet = false; remote_sink_settings = nullptr; @@ -200,6 +202,8 @@ SWGChannelSettings::init() { m_packet_demod_settings_isSet = false; packet_mod_settings = new SWGPacketModSettings(); m_packet_mod_settings_isSet = false; + pager_demod_settings = new SWGPagerDemodSettings(); + m_pager_demod_settings_isSet = false; radio_clock_settings = new SWGRadioClockSettings(); m_radio_clock_settings_isSet = false; remote_sink_settings = new SWGRemoteSinkSettings(); @@ -327,6 +331,9 @@ SWGChannelSettings::cleanup() { if(packet_mod_settings != nullptr) { delete packet_mod_settings; } + if(pager_demod_settings != nullptr) { + delete pager_demod_settings; + } if(radio_clock_settings != nullptr) { delete radio_clock_settings; } @@ -446,6 +453,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&packet_mod_settings, pJson["PacketModSettings"], "SWGPacketModSettings", "SWGPacketModSettings"); + ::SWGSDRangel::setValue(&pager_demod_settings, pJson["PagerDemodSettings"], "SWGPagerDemodSettings", "SWGPagerDemodSettings"); + ::SWGSDRangel::setValue(&radio_clock_settings, pJson["RadioClockSettings"], "SWGRadioClockSettings", "SWGRadioClockSettings"); ::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings"); @@ -591,6 +600,9 @@ SWGChannelSettings::asJsonObject() { if((packet_mod_settings != nullptr) && (packet_mod_settings->isSet())){ toJsonValue(QString("PacketModSettings"), packet_mod_settings, obj, QString("SWGPacketModSettings")); } + if((pager_demod_settings != nullptr) && (pager_demod_settings->isSet())){ + toJsonValue(QString("PagerDemodSettings"), pager_demod_settings, obj, QString("SWGPagerDemodSettings")); + } if((radio_clock_settings != nullptr) && (radio_clock_settings->isSet())){ toJsonValue(QString("RadioClockSettings"), radio_clock_settings, obj, QString("SWGRadioClockSettings")); } @@ -981,6 +993,16 @@ SWGChannelSettings::setPacketModSettings(SWGPacketModSettings* packet_mod_settin this->m_packet_mod_settings_isSet = true; } +SWGPagerDemodSettings* +SWGChannelSettings::getPagerDemodSettings() { + return pager_demod_settings; +} +void +SWGChannelSettings::setPagerDemodSettings(SWGPagerDemodSettings* pager_demod_settings) { + this->pager_demod_settings = pager_demod_settings; + this->m_pager_demod_settings_isSet = true; +} + SWGRadioClockSettings* SWGChannelSettings::getRadioClockSettings() { return radio_clock_settings; @@ -1211,6 +1233,9 @@ SWGChannelSettings::isSet(){ if(packet_mod_settings && packet_mod_settings->isSet()){ isObjectUpdated = true; break; } + if(pager_demod_settings && pager_demod_settings->isSet()){ + isObjectUpdated = true; break; + } if(radio_clock_settings && radio_clock_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 3d0438b73..58e8261d8 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -53,6 +53,7 @@ #include "SWGNoiseFigureSettings.h" #include "SWGPacketDemodSettings.h" #include "SWGPacketModSettings.h" +#include "SWGPagerDemodSettings.h" #include "SWGRadioClockSettings.h" #include "SWGRemoteSinkSettings.h" #include "SWGRemoteSourceSettings.h" @@ -190,6 +191,9 @@ public: SWGPacketModSettings* getPacketModSettings(); void setPacketModSettings(SWGPacketModSettings* packet_mod_settings); + SWGPagerDemodSettings* getPagerDemodSettings(); + void setPagerDemodSettings(SWGPagerDemodSettings* pager_demod_settings); + SWGRadioClockSettings* getRadioClockSettings(); void setRadioClockSettings(SWGRadioClockSettings* radio_clock_settings); @@ -335,6 +339,9 @@ private: SWGPacketModSettings* packet_mod_settings; bool m_packet_mod_settings_isSet; + SWGPagerDemodSettings* pager_demod_settings; + bool m_pager_demod_settings_isSet; + SWGRadioClockSettings* radio_clock_settings; bool m_radio_clock_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 9b0caa549..6d4480837 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -182,6 +182,8 @@ #include "SWGPacketModActions_tx.h" #include "SWGPacketModReport.h" #include "SWGPacketModSettings.h" +#include "SWGPagerDemodReport.h" +#include "SWGPagerDemodSettings.h" #include "SWGPerseusReport.h" #include "SWGPerseusSettings.h" #include "SWGPlutoSdrInputReport.h" @@ -1120,6 +1122,16 @@ namespace SWGSDRangel { obj->init(); return obj; } + if(QString("SWGPagerDemodReport").compare(type) == 0) { + SWGPagerDemodReport *obj = new SWGPagerDemodReport(); + obj->init(); + return obj; + } + if(QString("SWGPagerDemodSettings").compare(type) == 0) { + SWGPagerDemodSettings *obj = new SWGPagerDemodSettings(); + obj->init(); + return obj; + } if(QString("SWGPerseusReport").compare(type) == 0) { SWGPerseusReport *obj = new SWGPerseusReport(); obj->init(); diff --git a/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.cpp b/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.cpp new file mode 100644 index 000000000..665141da4 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.cpp @@ -0,0 +1,131 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPagerDemodReport.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPagerDemodReport::SWGPagerDemodReport(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPagerDemodReport::SWGPagerDemodReport() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +SWGPagerDemodReport::~SWGPagerDemodReport() { + this->cleanup(); +} + +void +SWGPagerDemodReport::init() { + channel_power_db = 0.0f; + m_channel_power_db_isSet = false; + channel_sample_rate = 0; + m_channel_sample_rate_isSet = false; +} + +void +SWGPagerDemodReport::cleanup() { + + +} + +SWGPagerDemodReport* +SWGPagerDemodReport::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPagerDemodReport::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", ""); + + ::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", ""); + +} + +QString +SWGPagerDemodReport::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPagerDemodReport::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_channel_power_db_isSet){ + obj->insert("channelPowerDB", QJsonValue(channel_power_db)); + } + if(m_channel_sample_rate_isSet){ + obj->insert("channelSampleRate", QJsonValue(channel_sample_rate)); + } + + return obj; +} + +float +SWGPagerDemodReport::getChannelPowerDb() { + return channel_power_db; +} +void +SWGPagerDemodReport::setChannelPowerDb(float channel_power_db) { + this->channel_power_db = channel_power_db; + this->m_channel_power_db_isSet = true; +} + +qint32 +SWGPagerDemodReport::getChannelSampleRate() { + return channel_sample_rate; +} +void +SWGPagerDemodReport::setChannelSampleRate(qint32 channel_sample_rate) { + this->channel_sample_rate = channel_sample_rate; + this->m_channel_sample_rate_isSet = true; +} + + +bool +SWGPagerDemodReport::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_channel_power_db_isSet){ + isObjectUpdated = true; break; + } + if(m_channel_sample_rate_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.h b/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.h new file mode 100644 index 000000000..44f8ea7db --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPagerDemodReport.h @@ -0,0 +1,64 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPagerDemodReport.h + * + * PagerDemod + */ + +#ifndef SWGPagerDemodReport_H_ +#define SWGPagerDemodReport_H_ + +#include + + + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPagerDemodReport: public SWGObject { +public: + SWGPagerDemodReport(); + SWGPagerDemodReport(QString* json); + virtual ~SWGPagerDemodReport(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPagerDemodReport* fromJson(QString &jsonString) override; + + float getChannelPowerDb(); + void setChannelPowerDb(float channel_power_db); + + qint32 getChannelSampleRate(); + void setChannelSampleRate(qint32 channel_sample_rate); + + + virtual bool isSet() override; + +private: + float channel_power_db; + bool m_channel_power_db_isSet; + + qint32 channel_sample_rate; + bool m_channel_sample_rate_isSet; + +}; + +} + +#endif /* SWGPagerDemodReport_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.cpp new file mode 100644 index 000000000..f46e911db --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.cpp @@ -0,0 +1,482 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGPagerDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGPagerDemodSettings::SWGPagerDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGPagerDemodSettings::SWGPagerDemodSettings() { + baud = 0; + m_baud_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + correlation_threshold = 0.0f; + m_correlation_threshold_isSet = false; + udp_enabled = 0; + m_udp_enabled_isSet = false; + udp_address = nullptr; + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + udp_format = 0; + m_udp_format_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGPagerDemodSettings::~SWGPagerDemodSettings() { + this->cleanup(); +} + +void +SWGPagerDemodSettings::init() { + baud = 0; + m_baud_isSet = false; + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + fm_deviation = 0.0f; + m_fm_deviation_isSet = false; + correlation_threshold = 0.0f; + m_correlation_threshold_isSet = false; + udp_enabled = 0; + m_udp_enabled_isSet = false; + udp_address = new QString(""); + m_udp_address_isSet = false; + udp_port = 0; + m_udp_port_isSet = false; + udp_format = 0; + m_udp_format_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGPagerDemodSettings::cleanup() { + + + + + + + if(udp_address != nullptr) { + delete udp_address; + } + + + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGPagerDemodSettings* +SWGPagerDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGPagerDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&baud, pJson["baud"], "qint32", ""); + + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "float", ""); + + ::SWGSDRangel::setValue(&correlation_threshold, pJson["correlationThreshold"], "float", ""); + + ::SWGSDRangel::setValue(&udp_enabled, pJson["udpEnabled"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&udp_format, pJson["udpFormat"], "qint32", ""); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGPagerDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGPagerDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_baud_isSet){ + obj->insert("baud", QJsonValue(baud)); + } + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(m_fm_deviation_isSet){ + obj->insert("fmDeviation", QJsonValue(fm_deviation)); + } + if(m_correlation_threshold_isSet){ + obj->insert("correlationThreshold", QJsonValue(correlation_threshold)); + } + if(m_udp_enabled_isSet){ + obj->insert("udpEnabled", QJsonValue(udp_enabled)); + } + if(udp_address != nullptr && *udp_address != QString("")){ + toJsonValue(QString("udpAddress"), udp_address, obj, QString("QString")); + } + if(m_udp_port_isSet){ + obj->insert("udpPort", QJsonValue(udp_port)); + } + if(m_udp_format_isSet){ + obj->insert("udpFormat", QJsonValue(udp_format)); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_stream_index_isSet){ + obj->insert("streamIndex", QJsonValue(stream_index)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +qint32 +SWGPagerDemodSettings::getBaud() { + return baud; +} +void +SWGPagerDemodSettings::setBaud(qint32 baud) { + this->baud = baud; + this->m_baud_isSet = true; +} + +qint64 +SWGPagerDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGPagerDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGPagerDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGPagerDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +float +SWGPagerDemodSettings::getFmDeviation() { + return fm_deviation; +} +void +SWGPagerDemodSettings::setFmDeviation(float fm_deviation) { + this->fm_deviation = fm_deviation; + this->m_fm_deviation_isSet = true; +} + +float +SWGPagerDemodSettings::getCorrelationThreshold() { + return correlation_threshold; +} +void +SWGPagerDemodSettings::setCorrelationThreshold(float correlation_threshold) { + this->correlation_threshold = correlation_threshold; + this->m_correlation_threshold_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getUdpEnabled() { + return udp_enabled; +} +void +SWGPagerDemodSettings::setUdpEnabled(qint32 udp_enabled) { + this->udp_enabled = udp_enabled; + this->m_udp_enabled_isSet = true; +} + +QString* +SWGPagerDemodSettings::getUdpAddress() { + return udp_address; +} +void +SWGPagerDemodSettings::setUdpAddress(QString* udp_address) { + this->udp_address = udp_address; + this->m_udp_address_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getUdpPort() { + return udp_port; +} +void +SWGPagerDemodSettings::setUdpPort(qint32 udp_port) { + this->udp_port = udp_port; + this->m_udp_port_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getUdpFormat() { + return udp_format; +} +void +SWGPagerDemodSettings::setUdpFormat(qint32 udp_format) { + this->udp_format = udp_format; + this->m_udp_format_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGPagerDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGPagerDemodSettings::getTitle() { + return title; +} +void +SWGPagerDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getStreamIndex() { + return stream_index; +} +void +SWGPagerDemodSettings::setStreamIndex(qint32 stream_index) { + this->stream_index = stream_index; + this->m_stream_index_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGPagerDemodSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGPagerDemodSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGPagerDemodSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGPagerDemodSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGPagerDemodSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGPagerDemodSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGPagerDemodSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGPagerDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_baud_isSet){ + isObjectUpdated = true; break; + } + if(m_input_frequency_offset_isSet){ + isObjectUpdated = true; break; + } + if(m_rf_bandwidth_isSet){ + isObjectUpdated = true; break; + } + if(m_fm_deviation_isSet){ + isObjectUpdated = true; break; + } + if(m_correlation_threshold_isSet){ + isObjectUpdated = true; break; + } + if(m_udp_enabled_isSet){ + isObjectUpdated = true; break; + } + if(udp_address && *udp_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_udp_port_isSet){ + isObjectUpdated = true; break; + } + if(m_udp_format_isSet){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_stream_index_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.h new file mode 100644 index 000000000..d626ca7ad --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGPagerDemodSettings.h @@ -0,0 +1,155 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGPagerDemodSettings.h + * + * PagerDemod + */ + +#ifndef SWGPagerDemodSettings_H_ +#define SWGPagerDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGPagerDemodSettings: public SWGObject { +public: + SWGPagerDemodSettings(); + SWGPagerDemodSettings(QString* json); + virtual ~SWGPagerDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGPagerDemodSettings* fromJson(QString &jsonString) override; + + qint32 getBaud(); + void setBaud(qint32 baud); + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + float getFmDeviation(); + void setFmDeviation(float fm_deviation); + + float getCorrelationThreshold(); + void setCorrelationThreshold(float correlation_threshold); + + qint32 getUdpEnabled(); + void setUdpEnabled(qint32 udp_enabled); + + QString* getUdpAddress(); + void setUdpAddress(QString* udp_address); + + qint32 getUdpPort(); + void setUdpPort(qint32 udp_port); + + qint32 getUdpFormat(); + void setUdpFormat(qint32 udp_format); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getStreamIndex(); + void setStreamIndex(qint32 stream_index); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + qint32 baud; + bool m_baud_isSet; + + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + float fm_deviation; + bool m_fm_deviation_isSet; + + float correlation_threshold; + bool m_correlation_threshold_isSet; + + qint32 udp_enabled; + bool m_udp_enabled_isSet; + + QString* udp_address; + bool m_udp_address_isSet; + + qint32 udp_port; + bool m_udp_port_isSet; + + qint32 udp_format; + bool m_udp_format_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + qint32 stream_index; + bool m_stream_index_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGPagerDemodSettings_H_ */