1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

Add Pager demodulator plugin that supports POCSAG as per #738

This commit is contained in:
Jon Beniston 2021-07-09 12:06:23 +01:00
parent 6089f0aa42
commit ade4246ac2
33 changed files with 5168 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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})

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "pagerdemod.h"
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#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<QString> 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<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(this, "reportdemod");
if (messageQueues)
{
QList<MessageQueue*>::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<QString>& 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<QString>& 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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMOD_H
#define INCLUDE_PAGERDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QDateTime>
#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<QString>& channelSettingsKeys, const PagerDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const PagerDemodSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleChannelMessages();
};
#endif // INCLUDE_PAGERDEMOD_H

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#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());
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODBASEBAND_H
#define INCLUDE_PAGERDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <ctype.h>
#include <QDockWidget>
#include <QDebug>
#include <QAction>
#include <QRegExp>
#include <QClipboard>
#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<QAction*>(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<PagerDemod*>(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++;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODGUI_H
#define INCLUDE_PAGERDEMODGUI_H
#include <QMenu>
#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

View File

@ -0,0 +1,985 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PagerDemodGUI</class>
<widget class="RollupWidget" name="PagerDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>404</width>
<height>764</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Pager Demodulator</string>
</property>
<property name="statusTip">
<string>Pager Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>171</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="powerLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="phySettingsLayout">
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rfBW">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>RF bandwidth</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
<string>Dev</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="fmDev">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Frequency deviation</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>24</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>2.4k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="phySettingsLayout_2">
<item>
<widget class="QLabel" name="modulationLabel">
<property name="text">
<string>Mod</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="modulation">
<property name="minimumSize">
<size>
<width>76</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Pager modulation</string>
</property>
<item>
<property name="text">
<string>POCSAC</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="Line" name="line_10">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="baudLabel">
<property name="text">
<string>Baud</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="baud">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Baud rate</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>512</string>
</property>
</item>
<item>
<property name="text">
<string>1200</string>
</property>
</item>
<item>
<property name="text">
<string>2400</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="Line" name="line_11">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="decodeLabel">
<property name="text">
<string>Decode</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="decode">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>How to choose between decoding a message as numeric or alphanumeric</string>
</property>
<item>
<property name="text">
<string>Standard</string>
</property>
</item>
<item>
<property name="text">
<string>Inverted</string>
</property>
</item>
<item>
<property name="text">
<string>Numeric</string>
</property>
</item>
<item>
<property name="text">
<string>Alphanumeric</string>
</property>
</item>
<item>
<property name="text">
<string>Heuristic</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="udpLayout">
<item>
<widget class="QCheckBox" name="udpEnabled">
<property name="toolTip">
<string>Forward messages via UDP</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>UDP</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpAddress">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP address</string>
</property>
<property name="inputMask">
<string>000.000.000.000</string>
</property>
<property name="text">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="udpSeparator">
<property name="text">
<string>:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpPort">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP port</string>
</property>
<property name="inputMask">
<string>00000</string>
</property>
<property name="text">
<string>9998</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="filterLayout">
<item>
<widget class="QLabel" name="filterAddressLabel">
<property name="text">
<string>Find</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterAddress">
<property name="toolTip">
<string>Display only messages where the address matches the specified regular expression</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="clearTable">
<property name="toolTip">
<string>Clear messages from table</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="messageContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>210</y>
<width>391</width>
<height>171</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Received Messages</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Received messages</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Date</string>
</property>
<property name="toolTip">
<string>Date message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Time</string>
</property>
<property name="toolTip">
<string>Time message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
<property name="toolTip">
<string>21-bit pager address the message is for</string>
</property>
</column>
<column>
<property name="text">
<string>Message</string>
</property>
</column>
<column>
<property name="text">
<string>Function</string>
</property>
<property name="toolTip">
<string>Function bits / source address</string>
</property>
</column>
<column>
<property name="text">
<string>Alpha</string>
</property>
<property name="toolTip">
<string>Message decoded as 7-bit alphanumeric characters</string>
</property>
</column>
<column>
<property name="text">
<string>Numeric</string>
</property>
<property name="toolTip">
<string>Message decoded as 4-bit BCD numeric characters</string>
</property>
</column>
<column>
<property name="text">
<string>Even PE</string>
</property>
<property name="toolTip">
<string>Number of even parity errors detected in message</string>
</property>
</column>
<column>
<property name="text">
<string>BCH PE</string>
</property>
<property name="toolTip">
<string>Number of BCH parity errors detected in message</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="scopeContainer" native="true">
<property name="geometry">
<rect>
<x>20</x>
<y>400</y>
<width>351</width>
<height>341</height>
</rect>
</property>
<property name="windowTitle">
<string>Waveforms</string>
</property>
<layout class="QVBoxLayout" name="transmittedLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="scopelLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Real</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channel1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Signal to send to scope as real part</string>
</property>
<item>
<property name="text">
<string>I</string>
</property>
</item>
<item>
<property name="text">
<string>Q</string>
</property>
</item>
<item>
<property name="text">
<string>Mag Sq</string>
</property>
</item>
<item>
<property name="text">
<string>FM demod</string>
</property>
</item>
<item>
<property name="text">
<string>Filt</string>
</property>
</item>
<item>
<property name="text">
<string>DC offset</string>
</property>
</item>
<item>
<property name="text">
<string>Data</string>
</property>
</item>
<item>
<property name="text">
<string>Sample</string>
</property>
</item>
<item>
<property name="text">
<string>Bit</string>
</property>
</item>
<item>
<property name="text">
<string>GotSyncCode</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Imag</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channel2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Signal to send to scope as imag part</string>
</property>
<item>
<property name="text">
<string>I</string>
</property>
</item>
<item>
<property name="text">
<string>Q</string>
</property>
</item>
<item>
<property name="text">
<string>Mag Sq</string>
</property>
</item>
<item>
<property name="text">
<string>FM demod</string>
</property>
</item>
<item>
<property name="text">
<string>Filt</string>
</property>
</item>
<item>
<property name="text">
<string>DC offset</string>
</property>
</item>
<item>
<property name="text">
<string>Data</string>
</property>
</item>
<item>
<property name="text">
<string>Sample</string>
</property>
</item>
<item>
<property name="text">
<string>Bit</string>
</property>
</item>
<item>
<property name="text">
<string>GotSyncCode</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="GLScope" name="glScope" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLScopeGUI" name="scopeGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScope</class>
<extends>QWidget</extends>
<header>gui/glscope.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeGUI</class>
<extends>QWidget</extends>
<header>gui/glscopegui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#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();
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODPLUGIN_H
#define INCLUDE_PAGERDEMODPLUGIN_H
#include <QObject>
#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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODSETTINGS_H
#define INCLUDE_PAGERDEMODSETTINGS_H
#include <QByteArray>
#include <QString>
#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; //<! 4.5k for POCSAG
enum Decode {
Standard,
Inverted,
Numeric,
Alphanumeric,
Heuristic
} m_decode; //!< Whether to decode as numeric or alphanumeric
bool m_udpEnabled;
QString m_udpAddress;
uint16_t m_udpPort;
QString m_filterAddress; //!< Filter messages by address in GUI
int m_scopeCh1; //!< What signals the scope shows
int m_scopeCh2;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
Serializable *m_scopeGUI;
int m_messageColumnIndexes[PAGERDEMOD_MESSAGE_COLUMNS];//!< How the columns are ordered in the table
int m_messageColumnSizes[PAGERDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table
static const int m_channelSampleRate = 38400; //!< lcm(512,2400) baud rates
PagerDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_PAGERDEMODSETTINGS_H */

View File

@ -0,0 +1,644 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <complex.h>
#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<ComplexVector::const_iterator> 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<int16_t>::max();
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
if (dataFifos)
{
QList<DataFifo*>::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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODSINK_H
#define INCLUDE_PAGERDEMODSINK_H
#include <QVector>
#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<Real, double, 2048> m_preambleMovingAverage;
MovingAverageUtil<Real, double, 16> m_movingAverage;
Lowpass<Complex> m_lowpass; // RF input filter
PhaseDiscriminators m_phaseDiscri; // FM demodulator
Lowpass<Real> 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<qint16> 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

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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;
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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

View File

@ -0,0 +1,95 @@
<h1>Pager demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate POCSAG pager messages.
<h2>Interface</h2>
![Pager Demodulator plugin GUI](../../../doc/img/PagerDemod_plugin.png)
<h3>1: Frequency shift from center frequency of reception</h3>
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.
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>4: BW - RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth.
<h3>5: Dev - Frequency deviation</h3>
Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. POCSAG uses FSK with a +/- 4.5 kHz shift.
<h3>6: Mod - Modulation</h3>
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
<h3>7: Baud</h3>
Specifies the baud rate. For POCSAG, this can be 512, 1200 or 2400.
<h3>8: Decode</h3>
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.
<h3>9: Find</h3>
Entering a regular expression in the Find field displays only messages where the address matches the given regular expression.
<h3>10: Clear Messages from table</h3>
Pressing this button clears all messages from the table.
<h3>11: UDP</h3>
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
<h3>12: UDP address</h3>
IP address of the host to forward received messages to via UDP.
<h3>13: UDP port</h3>
UDP port number to forward received messages to.
<h3>Received Messages Table</h3>
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.

View File

@ -3961,6 +3961,11 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setPacketModSettings(new SWGSDRangel::SWGPacketModSettings()); channelSettings->setPacketModSettings(new SWGSDRangel::SWGPacketModSettings());
channelSettings->getPacketModSettings()->fromJsonObject(settingsJsonObject); channelSettings->getPacketModSettings()->fromJsonObject(settingsJsonObject);
} }
else if (channelSettingsKey == "PagerDemodSettings")
{
channelSettings->setPagerDemodSettings(new SWGSDRangel::SWGPagerDemodSettings());
channelSettings->getPagerDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "RadioClockSettings") else if (channelSettingsKey == "RadioClockSettings")
{ {
channelSettings->setRadioClockSettings(new SWGSDRangel::SWGRadioClockSettings()); channelSettings->setRadioClockSettings(new SWGSDRangel::SWGRadioClockSettings());
@ -4659,6 +4664,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings&
channelSettings.setNoiseFigureSettings(nullptr); channelSettings.setNoiseFigureSettings(nullptr);
channelSettings.setPacketDemodSettings(nullptr); channelSettings.setPacketDemodSettings(nullptr);
channelSettings.setPacketModSettings(nullptr); channelSettings.setPacketModSettings(nullptr);
channelSettings.setPagerDemodSettings(nullptr);
channelSettings.setRemoteSinkSettings(nullptr); channelSettings.setRemoteSinkSettings(nullptr);
channelSettings.setRemoteSourceSettings(nullptr); channelSettings.setRemoteSourceSettings(nullptr);
channelSettings.setSsbDemodSettings(nullptr); channelSettings.setSsbDemodSettings(nullptr);

View File

@ -54,6 +54,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap {"sdrangel.channel.localsink", "LocalSinkSettings"}, // remap
{"sdrangel.channel.localsource", "LocalSourceSettings"}, {"sdrangel.channel.localsource", "LocalSourceSettings"},
{"sdrangel.channel.packetdemod", "PacketDemodSettings"}, {"sdrangel.channel.packetdemod", "PacketDemodSettings"},
{"sdrangel.channel.pagerdemod", "PagerDemodSettings"},
{"sdrangel.channeltx.modpacket", "PacketModSettings"}, {"sdrangel.channeltx.modpacket", "PacketModSettings"},
{"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"}, {"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"},
{"sdrangel.channel.radioclock", "RadioClockSettings"}, {"sdrangel.channel.radioclock", "RadioClockSettings"},
@ -152,6 +153,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"NoiseFigure", "NoiseFigureSettings"}, {"NoiseFigure", "NoiseFigureSettings"},
{"PacketDemod", "PacketDemodSettings"}, {"PacketDemod", "PacketDemodSettings"},
{"PacketMod", "PacketModSettings"}, {"PacketMod", "PacketModSettings"},
{"PagerDemod", "PagerDemodSettings"},
{"LocalSink", "LocalSinkSettings"}, {"LocalSink", "LocalSinkSettings"},
{"LocalSource", "LocalSourceSettings"}, {"LocalSource", "LocalSourceSettings"},
{"RadioClock", "RadioClockSettings"}, {"RadioClock", "RadioClockSettings"},

View File

@ -59,6 +59,8 @@ ChannelReport:
$ref: "http://swgserver:8081/api/swagger/include/RemoteSource.yaml#/RemoteSourceReport" $ref: "http://swgserver:8081/api/swagger/include/RemoteSource.yaml#/RemoteSourceReport"
PacketModReport: PacketModReport:
$ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModReport" $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModReport"
PagerDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/PagerDemod.yaml#/PagerDemodReport"
SigMFFileSinkReport: SigMFFileSinkReport:
$ref: "http://swgserver:8081/api/swagger/include/SigMFFileSink.yaml#/SigMFFileSinkReport" $ref: "http://swgserver:8081/api/swagger/include/SigMFFileSink.yaml#/SigMFFileSinkReport"
SSBModReport: SSBModReport:

View File

@ -79,6 +79,8 @@ ChannelSettings:
$ref: "http://swgserver:8081/api/swagger/include/PacketDemod.yaml#/PacketDemodSettings" $ref: "http://swgserver:8081/api/swagger/include/PacketDemod.yaml#/PacketDemodSettings"
PacketModSettings: PacketModSettings:
$ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModSettings" $ref: "http://swgserver:8081/api/swagger/include/PacketMod.yaml#/PacketModSettings"
PagerDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/PagerDemod.yaml#/PagerDemodSettings"
RadioClockSettings: RadioClockSettings:
$ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockSettings" $ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockSettings"
RemoteSinkSettings: RemoteSinkSettings:

View File

@ -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

View File

@ -80,6 +80,8 @@ SWGChannelReport::SWGChannelReport() {
m_remote_source_report_isSet = false; m_remote_source_report_isSet = false;
packet_mod_report = nullptr; packet_mod_report = nullptr;
m_packet_mod_report_isSet = false; m_packet_mod_report_isSet = false;
pager_demod_report = nullptr;
m_pager_demod_report_isSet = false;
sig_mf_file_sink_report = nullptr; sig_mf_file_sink_report = nullptr;
m_sig_mf_file_sink_report_isSet = false; m_sig_mf_file_sink_report_isSet = false;
ssb_mod_report = nullptr; ssb_mod_report = nullptr;
@ -156,6 +158,8 @@ SWGChannelReport::init() {
m_remote_source_report_isSet = false; m_remote_source_report_isSet = false;
packet_mod_report = new SWGPacketModReport(); packet_mod_report = new SWGPacketModReport();
m_packet_mod_report_isSet = false; m_packet_mod_report_isSet = false;
pager_demod_report = new SWGPagerDemodReport();
m_pager_demod_report_isSet = false;
sig_mf_file_sink_report = new SWGSigMFFileSinkReport(); sig_mf_file_sink_report = new SWGSigMFFileSinkReport();
m_sig_mf_file_sink_report_isSet = false; m_sig_mf_file_sink_report_isSet = false;
ssb_mod_report = new SWGSSBModReport(); ssb_mod_report = new SWGSSBModReport();
@ -252,6 +256,9 @@ SWGChannelReport::cleanup() {
if(packet_mod_report != nullptr) { if(packet_mod_report != nullptr) {
delete packet_mod_report; delete packet_mod_report;
} }
if(pager_demod_report != nullptr) {
delete pager_demod_report;
}
if(sig_mf_file_sink_report != nullptr) { if(sig_mf_file_sink_report != nullptr) {
delete sig_mf_file_sink_report; 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(&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(&sig_mf_file_sink_report, pJson["SigMFFileSinkReport"], "SWGSigMFFileSinkReport", "SWGSigMFFileSinkReport");
::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport"); ::SWGSDRangel::setValue(&ssb_mod_report, pJson["SSBModReport"], "SWGSSBModReport", "SWGSSBModReport");
@ -451,6 +460,9 @@ SWGChannelReport::asJsonObject() {
if((packet_mod_report != nullptr) && (packet_mod_report->isSet())){ if((packet_mod_report != nullptr) && (packet_mod_report->isSet())){
toJsonValue(QString("PacketModReport"), packet_mod_report, obj, QString("SWGPacketModReport")); 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())){ if((sig_mf_file_sink_report != nullptr) && (sig_mf_file_sink_report->isSet())){
toJsonValue(QString("SigMFFileSinkReport"), sig_mf_file_sink_report, obj, QString("SWGSigMFFileSinkReport")); 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; 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* SWGSigMFFileSinkReport*
SWGChannelReport::getSigMfFileSinkReport() { SWGChannelReport::getSigMfFileSinkReport() {
return sig_mf_file_sink_report; return sig_mf_file_sink_report;
@ -902,6 +924,9 @@ SWGChannelReport::isSet(){
if(packet_mod_report && packet_mod_report->isSet()){ if(packet_mod_report && packet_mod_report->isSet()){
isObjectUpdated = true; break; 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()){ if(sig_mf_file_sink_report && sig_mf_file_sink_report->isSet()){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }

View File

@ -43,6 +43,7 @@
#include "SWGNFMModReport.h" #include "SWGNFMModReport.h"
#include "SWGNoiseFigureReport.h" #include "SWGNoiseFigureReport.h"
#include "SWGPacketModReport.h" #include "SWGPacketModReport.h"
#include "SWGPagerDemodReport.h"
#include "SWGRadioClockReport.h" #include "SWGRadioClockReport.h"
#include "SWGRemoteSourceReport.h" #include "SWGRemoteSourceReport.h"
#include "SWGSSBDemodReport.h" #include "SWGSSBDemodReport.h"
@ -152,6 +153,9 @@ public:
SWGPacketModReport* getPacketModReport(); SWGPacketModReport* getPacketModReport();
void setPacketModReport(SWGPacketModReport* packet_mod_report); void setPacketModReport(SWGPacketModReport* packet_mod_report);
SWGPagerDemodReport* getPagerDemodReport();
void setPagerDemodReport(SWGPagerDemodReport* pager_demod_report);
SWGSigMFFileSinkReport* getSigMfFileSinkReport(); SWGSigMFFileSinkReport* getSigMfFileSinkReport();
void setSigMfFileSinkReport(SWGSigMFFileSinkReport* sig_mf_file_sink_report); void setSigMfFileSinkReport(SWGSigMFFileSinkReport* sig_mf_file_sink_report);
@ -258,6 +262,9 @@ private:
SWGPacketModReport* packet_mod_report; SWGPacketModReport* packet_mod_report;
bool m_packet_mod_report_isSet; bool m_packet_mod_report_isSet;
SWGPagerDemodReport* pager_demod_report;
bool m_pager_demod_report_isSet;
SWGSigMFFileSinkReport* sig_mf_file_sink_report; SWGSigMFFileSinkReport* sig_mf_file_sink_report;
bool m_sig_mf_file_sink_report_isSet; bool m_sig_mf_file_sink_report_isSet;

View File

@ -98,6 +98,8 @@ SWGChannelSettings::SWGChannelSettings() {
m_packet_demod_settings_isSet = false; m_packet_demod_settings_isSet = false;
packet_mod_settings = nullptr; packet_mod_settings = nullptr;
m_packet_mod_settings_isSet = false; m_packet_mod_settings_isSet = false;
pager_demod_settings = nullptr;
m_pager_demod_settings_isSet = false;
radio_clock_settings = nullptr; radio_clock_settings = nullptr;
m_radio_clock_settings_isSet = false; m_radio_clock_settings_isSet = false;
remote_sink_settings = nullptr; remote_sink_settings = nullptr;
@ -200,6 +202,8 @@ SWGChannelSettings::init() {
m_packet_demod_settings_isSet = false; m_packet_demod_settings_isSet = false;
packet_mod_settings = new SWGPacketModSettings(); packet_mod_settings = new SWGPacketModSettings();
m_packet_mod_settings_isSet = false; m_packet_mod_settings_isSet = false;
pager_demod_settings = new SWGPagerDemodSettings();
m_pager_demod_settings_isSet = false;
radio_clock_settings = new SWGRadioClockSettings(); radio_clock_settings = new SWGRadioClockSettings();
m_radio_clock_settings_isSet = false; m_radio_clock_settings_isSet = false;
remote_sink_settings = new SWGRemoteSinkSettings(); remote_sink_settings = new SWGRemoteSinkSettings();
@ -327,6 +331,9 @@ SWGChannelSettings::cleanup() {
if(packet_mod_settings != nullptr) { if(packet_mod_settings != nullptr) {
delete packet_mod_settings; delete packet_mod_settings;
} }
if(pager_demod_settings != nullptr) {
delete pager_demod_settings;
}
if(radio_clock_settings != nullptr) { if(radio_clock_settings != nullptr) {
delete radio_clock_settings; delete radio_clock_settings;
} }
@ -446,6 +453,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&packet_mod_settings, pJson["PacketModSettings"], "SWGPacketModSettings", "SWGPacketModSettings"); ::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(&radio_clock_settings, pJson["RadioClockSettings"], "SWGRadioClockSettings", "SWGRadioClockSettings");
::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings"); ::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings");
@ -591,6 +600,9 @@ SWGChannelSettings::asJsonObject() {
if((packet_mod_settings != nullptr) && (packet_mod_settings->isSet())){ if((packet_mod_settings != nullptr) && (packet_mod_settings->isSet())){
toJsonValue(QString("PacketModSettings"), packet_mod_settings, obj, QString("SWGPacketModSettings")); 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())){ if((radio_clock_settings != nullptr) && (radio_clock_settings->isSet())){
toJsonValue(QString("RadioClockSettings"), radio_clock_settings, obj, QString("SWGRadioClockSettings")); 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; 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* SWGRadioClockSettings*
SWGChannelSettings::getRadioClockSettings() { SWGChannelSettings::getRadioClockSettings() {
return radio_clock_settings; return radio_clock_settings;
@ -1211,6 +1233,9 @@ SWGChannelSettings::isSet(){
if(packet_mod_settings && packet_mod_settings->isSet()){ if(packet_mod_settings && packet_mod_settings->isSet()){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }
if(pager_demod_settings && pager_demod_settings->isSet()){
isObjectUpdated = true; break;
}
if(radio_clock_settings && radio_clock_settings->isSet()){ if(radio_clock_settings && radio_clock_settings->isSet()){
isObjectUpdated = true; break; isObjectUpdated = true; break;
} }

View File

@ -53,6 +53,7 @@
#include "SWGNoiseFigureSettings.h" #include "SWGNoiseFigureSettings.h"
#include "SWGPacketDemodSettings.h" #include "SWGPacketDemodSettings.h"
#include "SWGPacketModSettings.h" #include "SWGPacketModSettings.h"
#include "SWGPagerDemodSettings.h"
#include "SWGRadioClockSettings.h" #include "SWGRadioClockSettings.h"
#include "SWGRemoteSinkSettings.h" #include "SWGRemoteSinkSettings.h"
#include "SWGRemoteSourceSettings.h" #include "SWGRemoteSourceSettings.h"
@ -190,6 +191,9 @@ public:
SWGPacketModSettings* getPacketModSettings(); SWGPacketModSettings* getPacketModSettings();
void setPacketModSettings(SWGPacketModSettings* packet_mod_settings); void setPacketModSettings(SWGPacketModSettings* packet_mod_settings);
SWGPagerDemodSettings* getPagerDemodSettings();
void setPagerDemodSettings(SWGPagerDemodSettings* pager_demod_settings);
SWGRadioClockSettings* getRadioClockSettings(); SWGRadioClockSettings* getRadioClockSettings();
void setRadioClockSettings(SWGRadioClockSettings* radio_clock_settings); void setRadioClockSettings(SWGRadioClockSettings* radio_clock_settings);
@ -335,6 +339,9 @@ private:
SWGPacketModSettings* packet_mod_settings; SWGPacketModSettings* packet_mod_settings;
bool m_packet_mod_settings_isSet; bool m_packet_mod_settings_isSet;
SWGPagerDemodSettings* pager_demod_settings;
bool m_pager_demod_settings_isSet;
SWGRadioClockSettings* radio_clock_settings; SWGRadioClockSettings* radio_clock_settings;
bool m_radio_clock_settings_isSet; bool m_radio_clock_settings_isSet;

View File

@ -182,6 +182,8 @@
#include "SWGPacketModActions_tx.h" #include "SWGPacketModActions_tx.h"
#include "SWGPacketModReport.h" #include "SWGPacketModReport.h"
#include "SWGPacketModSettings.h" #include "SWGPacketModSettings.h"
#include "SWGPagerDemodReport.h"
#include "SWGPagerDemodSettings.h"
#include "SWGPerseusReport.h" #include "SWGPerseusReport.h"
#include "SWGPerseusSettings.h" #include "SWGPerseusSettings.h"
#include "SWGPlutoSdrInputReport.h" #include "SWGPlutoSdrInputReport.h"
@ -1120,6 +1122,16 @@ namespace SWGSDRangel {
obj->init(); obj->init();
return obj; 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) { if(QString("SWGPerseusReport").compare(type) == 0) {
SWGPerseusReport *obj = new SWGPerseusReport(); SWGPerseusReport *obj = new SWGPerseusReport();
obj->init(); obj->init();

View File

@ -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 <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
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;
}
}

View File

@ -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 <QJsonObject>
#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_ */

View File

@ -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 <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
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;
}
}

View File

@ -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 <QJsonObject>
#include <QString>
#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_ */