Add packet demodulator

pull/746/head
Jon Beniston 2021-01-13 19:58:07 +00:00
rodzic 931a63dc8b
commit c74ec2c426
21 zmienionych plików z 3381 dodań i 0 usunięć

Wyświetl plik

@ -16,6 +16,7 @@ add_subdirectory(filesink)
add_subdirectory(freqtracker)
add_subdirectory(demodchirpchat)
add_subdirectory(demodvorsc)
add_subdirectory(demodpacket)
if(LIBDSDCC_FOUND AND LIBMBE_FOUND)
add_subdirectory(demoddsd)

Wyświetl plik

@ -0,0 +1,58 @@
project(demodpacket)
set(demodpacket_SOURCES
packetdemod.cpp
packetdemodsettings.cpp
packetdemodbaseband.cpp
packetdemodsink.cpp
packetdemodplugin.cpp
packetdemodwebapiadapter.cpp
)
set(demodpacket_HEADERS
packetdemod.h
packetdemodsettings.h
packetdemodbaseband.h
packetdemodsink.h
packetdemodplugin.h
packetdemodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(demodpacket_SOURCES
${demodpacket_SOURCES}
packetdemodgui.cpp
packetdemodgui.ui
)
set(demodpacket_HEADERS
${demodpacket_HEADERS}
packetdemodgui.h
)
set(TARGET_NAME demodpacket)
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demodpacketsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demodpacket_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

Wyświetl plik

@ -0,0 +1,434 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "packetdemod.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGPacketDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGMapItem.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(PacketDemod::MsgConfigurePacketDemod, Message)
const char * const PacketDemod::m_channelIdURI = "sdrangel.channelrx.packetdemod";
const char * const PacketDemod::m_channelId = "PacketDemod";
PacketDemod::PacketDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new PacketDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
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*)));
}
PacketDemod::~PacketDemod()
{
qDebug("PacketDemod::~PacketDemod");
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 PacketDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void PacketDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void PacketDemod::start()
{
qDebug("PacketDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void PacketDemod::stop()
{
qDebug("PacketDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool PacketDemod::handleMessage(const Message& cmd)
{
if (MsgConfigurePacketDemod::match(cmd))
{
MsgConfigurePacketDemod& cfg = (MsgConfigurePacketDemod&) cmd;
qDebug() << "PacketDemod::handleMessage: MsgConfigurePacketDemod";
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() << "PacketDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (m_guiMessageQueue)
{
rep = new DSPSignalNotification(notif);
m_guiMessageQueue->push(rep);
}
return true;
}
else if (MainCore::MsgPacket::match(cmd))
{
// Forward to GUI
MainCore::MsgPacket& report = (MainCore::MsgPacket&)cmd;
if (getMessageQueueToGUI())
{
MainCore::MsgPacket *msg = new MainCore::MsgPacket(report);
getMessageQueueToGUI()->push(msg);
}
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
// Forward to APRS and other packet features
QList<MessageQueue*> *packetMessageQueues = messagePipes.getMessageQueues(this, "packets");
if (packetMessageQueues)
{
QList<MessageQueue*>::iterator it = packetMessageQueues->begin();
for (; it != packetMessageQueues->end(); ++it)
{
MainCore::MsgPacket *msg = new MainCore::MsgPacket(report);
(*it)->push(msg);
}
}
return true;
}
else
{
return false;
}
}
void PacketDemod::applySettings(const PacketDemodSettings& settings, bool force)
{
qDebug() << "PacketDemod::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_inputFrequencyOffset != 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 (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");
}
PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::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 PacketDemod::serialize() const
{
return m_settings.serialize();
}
bool PacketDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int PacketDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
response.getPacketDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int PacketDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
PacketDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("PacketDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigurePacketDemod *msgToGUI = MsgConfigurePacketDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void PacketDemod::webapiUpdateChannelSettings(
PacketDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getPacketDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getPacketDemodSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getPacketDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getPacketDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getPacketDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getPacketDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getPacketDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getPacketDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getPacketDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getPacketDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getPacketDemodSettings()->getReverseApiChannelIndex();
}
}
void PacketDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const PacketDemodSettings& settings)
{
response.getPacketDemodSettings()->setFmDeviation(settings.m_fmDeviation);
response.getPacketDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getPacketDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getPacketDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getPacketDemodSettings()->getTitle()) {
*response.getPacketDemodSettings()->getTitle() = settings.m_title;
} else {
response.getPacketDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getPacketDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getPacketDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getPacketDemodSettings()->getReverseApiAddress()) {
*response.getPacketDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getPacketDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getPacketDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getPacketDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getPacketDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void PacketDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const PacketDemodSettings& 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 PacketDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const PacketDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("PacketDemod"));
swgChannelSettings->setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
SWGSDRangel::SWGPacketDemodSettings *swgPacketDemodSettings = swgChannelSettings->getPacketDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgPacketDemodSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgPacketDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgPacketDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgPacketDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgPacketDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgPacketDemodSettings->setStreamIndex(settings.m_streamIndex);
}
}
void PacketDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "PacketDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("PacketDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

Wyświetl plik

@ -0,0 +1,152 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_PACKETDEMOD_H
#define INCLUDE_PACKETDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QThread>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "packetdemodbaseband.h"
#include "packetdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class PacketDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigurePacketDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const PacketDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigurePacketDemod* create(const PacketDemodSettings& settings, bool force)
{
return new MsgConfigurePacketDemod(settings, force);
}
private:
PacketDemodSettings m_settings;
bool m_force;
MsgConfigurePacketDemod(const PacketDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
PacketDemod(DeviceAPI *deviceAPI);
virtual ~PacketDemod();
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 PacketDemodSettings& settings);
static void webapiUpdateChannelSettings(
PacketDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
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 {
BasebandSampleSink::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;
PacketDemodBaseband* m_basebandSink;
PacketDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const PacketDemodSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const PacketDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const PacketDemodSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_PACKETDEMOD_H

Wyświetl plik

@ -0,0 +1,170 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "packetdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(PacketDemodBaseband::MsgConfigurePacketDemodBaseband, Message)
PacketDemodBaseband::PacketDemodBaseband(PacketDemod *packetDemod) :
m_sink(packetDemod),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("PacketDemodBaseband::PacketDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
PacketDemodBaseband::~PacketDemodBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void PacketDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void PacketDemodBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&PacketDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void PacketDemodBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&PacketDemodBaseband::handleData
);
m_running = false;
}
void PacketDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void PacketDemodBaseband::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 PacketDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool PacketDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigurePacketDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigurePacketDemodBaseband& cfg = (MsgConfigurePacketDemodBaseband&) cmd;
qDebug() << "PacketDemodBaseband::handleMessage: MsgConfigurePacketDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "PacketDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else
{
return false;
}
}
void PacketDemodBaseband::applySettings(const PacketDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
void PacketDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

Wyświetl plik

@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_PACKETDEMODBASEBAND_H
#define INCLUDE_PACKETDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "packetdemodsink.h"
class DownChannelizer;
class PacketDemod;
class PacketDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigurePacketDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const PacketDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigurePacketDemodBaseband* create(const PacketDemodSettings& settings, bool force)
{
return new MsgConfigurePacketDemodBaseband(settings, force);
}
private:
PacketDemodSettings m_settings;
bool m_force;
MsgConfigurePacketDemodBaseband(const PacketDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
PacketDemodBaseband(PacketDemod *packetDemod);
~PacketDemodBaseband();
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);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
PacketDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
PacketDemodSettings m_settings;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void calculateOffset(PacketDemodSink *sink);
void applySettings(const PacketDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_PACKETDEMODBASEBAND_H

Wyświetl plik

@ -0,0 +1,560 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QMainWindow>
#include <QDebug>
#include <QQuickItem>
#include <QGeoLocation>
#include <QGeoCoordinate>
#include <QQmlContext>
#include <QMessageBox>
#include <QAction>
#include <QRegExp>
#include "packetdemodgui.h"
#include "util/ax25.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_packetdemodgui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "util/morse.h"
#include "util/units.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "dsp/dspengine.h"
#include "gui/crightclickenabler.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "packetdemod.h"
#include "packetdemodsink.h"
#define PACKET_COL_FROM 0
#define PACKET_COL_TO 1
#define PACKET_COL_VIA 2
#define PACKET_COL_TYPE 3
#define PACKET_COL_PID 4
#define PACKET_COL_DATA_ASCII 5
#define PACKET_COL_DATA_HEX 6
void PacketDemodGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
// Trailing spaces are for sort arrow
int row = ui->packets->rowCount();
ui->packets->setRowCount(row + 1);
ui->packets->setItem(row, PACKET_COL_FROM, new QTableWidgetItem("123456-15-"));
ui->packets->setItem(row, PACKET_COL_TO, new QTableWidgetItem("123456-15-"));
ui->packets->setItem(row, PACKET_COL_VIA, new QTableWidgetItem("123456-15-"));
ui->packets->setItem(row, PACKET_COL_TYPE, new QTableWidgetItem("Type-"));
ui->packets->setItem(row, PACKET_COL_PID, new QTableWidgetItem("PID-"));
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->packets->setItem(row, PACKET_COL_DATA_HEX, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->packets->resizeColumnsToContents();
ui->packets->removeRow(row);
}
// Columns in table reordered
void PacketDemodGUI::packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void PacketDemodGUI::packets_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_columnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void PacketDemodGUI::columnSelectMenu(QPoint pos)
{
menu->popup(ui->packets->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void PacketDemodGUI::columnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->packets->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *PacketDemodGUI::createCheckableItem(QString &text, int idx, bool checked)
{
QAction *action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
return action;
}
PacketDemodGUI* PacketDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
PacketDemodGUI* gui = new PacketDemodGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void PacketDemodGUI::destroy()
{
delete this;
}
void PacketDemodGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray PacketDemodGUI::serialize() const
{
return m_settings.serialize();
}
bool PacketDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
// Add row to table
void PacketDemodGUI::packetReceived(QByteArray packet)
{
AX25Packet ax25;
if (ax25.decode(packet))
{
ui->packets->setSortingEnabled(false);
int row = ui->packets->rowCount();
ui->packets->setRowCount(row + 1);
QTableWidgetItem *fromItem = new QTableWidgetItem();
QTableWidgetItem *toItem = new QTableWidgetItem();
QTableWidgetItem *viaItem = new QTableWidgetItem();
QTableWidgetItem *typeItem = new QTableWidgetItem();
QTableWidgetItem *pidItem = new QTableWidgetItem();
QTableWidgetItem *dataASCIIItem = new QTableWidgetItem();
QTableWidgetItem *dataHexItem = new QTableWidgetItem();
QTableWidgetItem *dataAPRSItem = new QTableWidgetItem();
ui->packets->setItem(row, PACKET_COL_FROM, fromItem);
ui->packets->setItem(row, PACKET_COL_TO, toItem);
ui->packets->setItem(row, PACKET_COL_VIA, viaItem);
ui->packets->setItem(row, PACKET_COL_TYPE, typeItem);
ui->packets->setItem(row, PACKET_COL_PID, pidItem);
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, dataASCIIItem);
ui->packets->setItem(row, PACKET_COL_DATA_HEX, dataHexItem);
fromItem->setText(ax25.m_from);
toItem->setText(ax25.m_to);
viaItem->setText(ax25.m_via);
typeItem->setText(ax25.m_type);
pidItem->setText(ax25.m_pid);
dataASCIIItem->setText(ax25.m_dataASCII);
dataHexItem->setText(ax25.m_dataHex);
ui->packets->setSortingEnabled(true);
ui->packets->scrollToItem(fromItem);
filterRow(row);
}
else
qDebug() << "Unsupported AX.25 packet: " << packet;
}
bool PacketDemodGUI::handleMessage(const Message& message)
{
if (PacketDemod::MsgConfigurePacketDemod::match(message))
{
qDebug("PacketDemodGUI::handleMessage: PacketDemod::MsgConfigurePacketDemod");
const PacketDemod::MsgConfigurePacketDemod& cfg = (PacketDemod::MsgConfigurePacketDemod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
m_basebandSampleRate = notif.getSampleRate();
return true;
}
else if (MainCore::MsgPacket::match(message))
{
MainCore::MsgPacket& report = (MainCore::MsgPacket&) message;
packetReceived(report.getPacket());
return true;
}
return false;
}
void PacketDemodGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void PacketDemodGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void PacketDemodGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void PacketDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void PacketDemodGUI::on_mode_currentIndexChanged(int value)
{
QString mode = ui->mode->currentText();
// TODO: Support 9600 FSK
}
void PacketDemodGUI::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 PacketDemodGUI::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 PacketDemodGUI::on_filterFrom_editingFinished()
{
m_settings.m_filterFrom = ui->filterFrom->text();
filter();
applySettings();
}
void PacketDemodGUI::on_filterTo_editingFinished()
{
m_settings.m_filterTo = ui->filterTo->text();
filter();
applySettings();
}
void PacketDemodGUI::on_filterPID_stateChanged(int state)
{
m_settings.m_filterPID = state==Qt::Checked ? "f0" : "";
filter();
applySettings();
}
void PacketDemodGUI::on_clearTable_clicked()
{
ui->packets->setRowCount(0);
}
void PacketDemodGUI::filterRow(int row)
{
bool hidden = false;
if (m_settings.m_filterFrom != "")
{
QRegExp re(m_settings.m_filterFrom);
QTableWidgetItem *fromItem = ui->packets->item(row, PACKET_COL_FROM);
if (!re.exactMatch(fromItem->text()))
hidden = true;
}
if (m_settings.m_filterTo != "")
{
QRegExp re(m_settings.m_filterTo);
QTableWidgetItem *toItem = ui->packets->item(row, PACKET_COL_TO);
if (!re.exactMatch(toItem->text()))
hidden = true;
}
if (m_settings.m_filterPID != "")
{
QTableWidgetItem *pidItem = ui->packets->item(row, PACKET_COL_PID);
if (pidItem->text() != m_settings.m_filterPID)
hidden = true;
}
ui->packets->setRowHidden(row, hidden);
}
void PacketDemodGUI::filter()
{
for (int i = 0; i < ui->packets->rowCount(); i++)
{
filterRow(i);
}
}
void PacketDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void PacketDemodGUI::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_packetDemod->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();
}
PacketDemodGUI::PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::PacketDemodGUI),
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_packetDemod = reinterpret_cast<PacketDemod*>(rxChannel);
m_packetDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
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("Packet 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_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->packets->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->packets->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
menu = new QMenu(ui->packets);
for (int i = 0; i < ui->packets->horizontalHeader()->count(); i++)
{
QString text = ui->packets->horizontalHeaderItem(i)->text();
menu->addAction(createCheckableItem(text, i, true));
}
ui->packets->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->packets->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->packets->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(packets_sectionMoved(int, int, int)));
connect(ui->packets->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(packets_sectionResized(int, int, int)));
displaySettings();
applySettings(true);
}
PacketDemodGUI::~PacketDemodGUI()
{
delete ui;
}
void PacketDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void PacketDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
PacketDemod::MsgConfigurePacketDemod* message = PacketDemod::MsgConfigurePacketDemod::create( m_settings, force);
m_packetDemod->getInputMessageQueue()->push(message);
}
}
void PacketDemodGUI::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());
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->filterFrom->setText(m_settings.m_filterFrom);
ui->filterTo->setText(m_settings.m_filterTo);
ui->filterPID->setChecked(m_settings.m_filterPID == "f0");
// Order and size columns
QHeaderView *header = ui->packets->horizontalHeader();
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
{
bool hidden = m_settings.m_columnSizes[i] == 0;
header->setSectionHidden(i, hidden);
menu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_columnSizes[i] > 0)
ui->packets->setColumnWidth(i, m_settings.m_columnSizes[i]);
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
}
filter();
blockApplySettings(false);
}
void PacketDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void PacketDemodGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void PacketDemodGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void PacketDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_packetDemod->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++;
}

Wyświetl plik

@ -0,0 +1,119 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_PACKETDEMODGUI_H
#define INCLUDE_PACKETDEMODGUI_H
#include <QIcon>
#include <QAbstractListModel>
#include <QModelIndex>
#include <QProgressDialog>
#include <QGeoLocation>
#include <QGeoCoordinate>
#include <QTableWidgetItem>
#include <QPushButton>
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "util/messagequeue.h"
#include "packetdemodsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class PacketDemod;
class PacketDemodGUI;
namespace Ui {
class PacketDemodGUI;
}
class PacketDemodGUI;
class PacketDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static PacketDemodGUI* 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::PacketDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
PacketDemodSettings m_settings;
bool m_doApplySettings;
PacketDemod* m_packetDemod;
int m_basebandSampleRate;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QMenu *menu; // Column select context menu
explicit PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~PacketDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
void packetReceived(QByteArray packet);
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_mode_currentIndexChanged(int value);
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_filterFrom_editingFinished();
void on_filterTo_editingFinished();
void on_filterPID_stateChanged(int state);
void on_clearTable_clicked();
void filterRow(int row);
void filter();
void packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void packets_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_PACKETDEMODGUI_H

Wyświetl plik

@ -0,0 +1,579 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PacketDemodGUI</class>
<widget class="RollupWidget" name="PacketDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>519</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>Packet Demodulator</string>
</property>
<property name="statusTip">
<string>Packet Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>101</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="buttonLayout">
<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>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="mode">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Baud rate and modulation</string>
</property>
<item>
<property name="text">
<string>1200 AFSK</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>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<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>60</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</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>5.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="filterFromLabel">
<property name="text">
<string>From</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterFrom">
<property name="toolTip">
<string>Display only packets where the source address (From) matches the specified regular expression</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterToLabel">
<property name="text">
<string>To:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterTo">
<property name="toolTip">
<string>Display only packets where the destination address (To) matches the specified regular expression</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="filterPID">
<property name="toolTip">
<string>Check to display only packets with PID set to No L3 (f0). This is typically used for APRS and BBS packets.</string>
</property>
<property name="text">
<string>PID No L3</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 packets 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="dataContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>110</y>
<width>391</width>
<height>261</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Received Packets</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="packets">
<property name="toolTip">
<string>Received packets</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>From</string>
</property>
<property name="toolTip">
<string>Source callsign/address</string>
</property>
</column>
<column>
<property name="text">
<string>To</string>
</property>
<property name="toolTip">
<string>Destination callsign/address</string>
</property>
</column>
<column>
<property name="text">
<string>Via</string>
</property>
<property name="toolTip">
<string>Repeater addresses</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>AX.25 frame type</string>
</property>
</column>
<column>
<property name="text">
<string>PID</string>
</property>
<property name="toolTip">
<string>Layer 3 protocol ID</string>
</property>
</column>
<column>
<property name="text">
<string>Data (ASCII)</string>
</property>
<property name="toolTip">
<string>Packet data as ASCII</string>
</property>
</column>
<column>
<property name="text">
<string>Data (Hex)</string>
</property>
<property name="toolTip">
<string>Packet data as hex</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>packets</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

Wyświetl plik

@ -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 "packetdemodgui.h"
#endif
#include "packetdemod.h"
#include "packetdemodwebapiadapter.h"
#include "packetdemodplugin.h"
const PluginDescriptor PacketDemodPlugin::m_pluginDescriptor = {
PacketDemod::m_channelId,
QStringLiteral("Packet Demodulator"),
QStringLiteral("6.4.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
PacketDemodPlugin::PacketDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& PacketDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void PacketDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(PacketDemod::m_channelIdURI, PacketDemod::m_channelId, this);
}
void PacketDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
PacketDemod *instance = new PacketDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* PacketDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* PacketDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return PacketDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* PacketDemodPlugin::createChannelWebAPIAdapter() const
{
return new PacketDemodWebAPIAdapter();
}

Wyświetl plik

@ -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_PACKETDEMODPLUGIN_H
#define INCLUDE_PACKETDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class PacketDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.packetdemod")
public:
explicit PacketDemodPlugin(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_PACKETDEMODPLUGIN_H

Wyświetl plik

@ -0,0 +1,145 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "packetdemodsettings.h"
PacketDemodSettings::PacketDemodSettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void PacketDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_baud = 1200;
m_rfBandwidth = 12500.0f;
m_fmDeviation = 2500.0f;
m_filterFrom = "";
m_filterTo = "";
m_filterPID = "";
m_rgbColor = QColor(255, 255, 0).rgb();
m_title = "Packet 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 < PACKETDEMOD_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray PacketDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_streamIndex);
s.writeString(4, m_filterFrom);
s.writeString(4, m_filterTo);
s.writeString(5, m_filterPID);
if (m_channelMarker) {
s.writeBlob(6, m_channelMarker->serialize());
}
s.writeU32(7, m_rgbColor);
s.writeString(9, m_title);
s.writeBool(14, m_useReverseAPI);
s.writeString(15, m_reverseAPIAddress);
s.writeU32(16, m_reverseAPIPort);
s.writeU32(17, m_reverseAPIDeviceIndex);
s.writeU32(18, m_reverseAPIChannelIndex);
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]);
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
s.writeS32(200 + i, m_columnSizes[i]);
return s.final();
}
bool PacketDemodSettings::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.readS32(2, &m_streamIndex, 0);
d.readString(3, &m_filterFrom, "");
d.readString(4, &m_filterTo, "");
d.readString(5, &m_filterPID, "");
d.readBlob(6, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readU32(7, &m_rgbColor);
d.readString(9, &m_title, "Packet Demodulator");
d.readBool(14, &m_useReverseAPI, false);
d.readString(15, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(16, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(17, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(18, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i);
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
d.readS32(200 + i, &m_columnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}

Wyświetl plik

@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_PACKETDEMODSETTINGS_H
#define INCLUDE_PACKETDEMODSETTINGS_H
#include <QByteArray>
#include <QHash>
class Serializable;
// Number of columns in the table
#define PACKETDEMOD_COLUMNS 7
struct PacketDemodSettings
{
qint32 m_inputFrequencyOffset;
qint32 m_baud;
Real m_rfBandwidth;
Real m_fmDeviation;
QString m_filterFrom;
QString m_filterTo;
QString m_filterPID;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
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;
int m_columnIndexes[PACKETDEMOD_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[PACKETDEMOD_COLUMNS]; //!< Size of the columns in the table
PacketDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_PACKETDEMODSETTINGS_H */

Wyświetl plik

@ -0,0 +1,306 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/dspengine.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "pipes/pipeendpoint.h"
#include "maincore.h"
#include "packetdemod.h"
#include "packetdemodsink.h"
PacketDemodSink::PacketDemodSink(PacketDemod *packetDemod) :
m_packetDemod(packetDemod),
m_channelSampleRate(PACKETDEMOD_CHANNEL_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_f1(nullptr),
m_f0(nullptr),
m_corrBuf(nullptr),
m_corrIdx(0),
m_corrCnt(0)
{
m_magsq = 0.0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
PacketDemodSink::~PacketDemodSink()
{
}
void PacketDemodSink::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;
}
}
}
}
void PacketDemodSink::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++;
m_corrBuf[m_corrIdx] = fmDemod;
if (m_corrCnt >= m_correlationLength && magsq > 1e-7)
{
// Correlate with 1200 + 2200 baud complex exponentials
Complex corrF0 = 0.0f;
Complex corrF1 = 0.0f;
for (int i = 0; i < m_correlationLength; i++)
{
int j = m_corrIdx - i;
if (j < 0)
j += m_correlationLength;
corrF0 += m_f0[i] * m_corrBuf[j];
corrF1 += m_f1[i] * m_corrBuf[j];
}
m_corrCnt--; // Avoid overflow in increment below
// Low pass filter, to minimize changes above the baud rate
Real f0Filt = m_lowpassF0.filter(std::abs(corrF0));
Real f1Filt = m_lowpassF1.filter(std::abs(corrF1));
// Determine which is the closest match and then quantise to 1 or -1
// FIXME: We should try to account for the fact that higher frequencies can have preemphasis
float diff = f1Filt - f0Filt;
int sample = diff >= 0.0f ? 1 : 0;
// Look for edge
if (sample != m_samplePrev)
{
m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud/2;
}
else
{
m_syncCount--;
if (m_syncCount <= 0)
{
// HDLC deframing
// Should be in the middle of the symbol
// NRZI decoding
int bit;
if (sample != m_symbolPrev)
bit = 0;
else
bit = 1;
m_symbolPrev = sample;
// Store in shift reg
m_bits |= bit << m_bitCount;
m_bitCount++;
if (bit == 1)
{
m_onesCount++;
// Shouldn't ever get 7 1s in a row
if ((m_onesCount == 7) && m_gotSOP)
{
m_gotSOP = false;
m_byteCount = 0;
}
}
else if (bit == 0)
{
if (m_onesCount == 5)
{
// Remove bit-stuffing (5 1s followed by a 0)
m_bitCount--;
}
else if (m_onesCount == 6)
{
// Start/end of packet
if ((m_bitCount == 8) && (m_bits == 0x7e) && (m_byteCount > 0))
{
// End of packet
// Check CRC is valid
m_crc.init();
m_crc.calculate(m_bytes, m_byteCount - 2);
uint16_t calcCrc = m_crc.get();
uint16_t rxCrc = m_bytes[m_byteCount-2] | (m_bytes[m_byteCount-1] << 8);
if (calcCrc == rxCrc)
{
QByteArray rxPacket((char *)m_bytes, m_byteCount);
qDebug() << "RX: " << Qt::hex << rxPacket;
if (getMessageQueueToChannel())
{
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(m_packetDemod, rxPacket, QDateTime::currentDateTime()); // FIXME pointer
getMessageQueueToChannel()->push(msg);
}
}
else
qDebug() << "CRC mismatch: " << Qt::hex << calcCrc << " " << Qt::hex << rxCrc;
// Reset state to start receiving next packet
m_gotSOP = false;
m_bits = 0;
m_bitCount = 0;
m_byteCount = 0;
}
else
{
// Start of packet
m_gotSOP = true;
m_bits = 0;
m_bitCount = 0;
m_byteCount = 0;
}
}
m_onesCount = 0;
}
if (m_gotSOP)
{
if (m_bitCount == 8)
{
if (m_byteCount >= 512)
{
// Too many bytes
m_gotSOP = false;
m_byteCount = 0;
}
else
{
m_bytes[m_byteCount] = m_bits;
m_byteCount++;
}
m_bits = 0;
m_bitCount = 0;
}
}
m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud;
}
}
m_samplePrev = sample;
}
m_corrIdx = (m_corrIdx + 1) % m_correlationLength;
m_corrCnt++;
}
void PacketDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "PacketDemodSink::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, PACKETDEMOD_CHANNEL_BANDWIDTH);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) PACKETDEMOD_CHANNEL_SAMPLE_RATE;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void PacketDemodSink::applySettings(const PacketDemodSettings& settings, bool force)
{
qDebug() << "PacketDemodSink::applySettings:"
<< " force: " << force;
if (force)
{
m_lowpass.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f);
m_phaseDiscri.setFMScaling(PACKETDEMOD_CHANNEL_SAMPLE_RATE / (2.0f * settings.m_fmDeviation));
delete m_f1;
delete m_f0;
delete m_corrBuf;
m_correlationLength = PACKETDEMOD_CHANNEL_SAMPLE_RATE/settings.m_baud;
m_f1 = new Complex[m_correlationLength]();
m_f0 = new Complex[m_correlationLength]();
m_corrBuf = new Complex[m_correlationLength]();
m_corrIdx = 0;
m_corrCnt = 0;
Real f0 = 0.0f;
Real f1 = 0.0f;
for (int i = 0; i < m_correlationLength; i++)
{
m_f0[i] = Complex(cos(f0), sin(f0));
m_f1[i] = Complex(cos(f1), sin(f1));
f0 += 2.0f*(Real)M_PI*2200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE;
f1 += 2.0f*(Real)M_PI*1200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE;
}
m_lowpassF1.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f);
m_lowpassF0.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f);
m_samplePrev = 0;
m_syncCount = 0;
m_symbolPrev = 0;
m_bits = 0;
m_bitCount = 0;
m_onesCount = 0;
m_gotSOP = false;
m_byteCount = 0;
}
m_settings = settings;
}

Wyświetl plik

@ -0,0 +1,135 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_PACKETDEMODSINK_H
#define INCLUDE_PACKETDEMODSINK_H
#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/doublebufferfifo.h"
#include "util/messagequeue.h"
#include "util/crc.h"
#include "packetdemodsettings.h"
#include <vector>
#include <iostream>
#include <fstream>
#define PACKETDEMOD_CHANNEL_BANDWIDTH 9600
// Must be integer multiple of m_baud=1200
#define PACKETDEMOD_CHANNEL_SAMPLE_RATE 38400
class PacketDemod;
class PacketDemodSink : public ChannelSampleSink {
public:
PacketDemodSink(PacketDemod *packetDemod);
~PacketDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const PacketDemodSettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
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;
};
PacketDemod *m_packetDemod;
PacketDemodSettings m_settings;
int m_channelSampleRate;
int m_channelFrequencyOffset;
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, 16> m_movingAverage;
Lowpass<Complex> m_lowpass;
PhaseDiscriminators m_phaseDiscri;
int m_correlationLength;
Complex *m_f1;
Complex *m_f0;
Complex *m_corrBuf;
int m_corrIdx;
int m_corrCnt;
Lowpass<Real> m_lowpassF1;
Lowpass<Real> m_lowpassF0;
int m_samplePrev;
int m_syncCount;
int m_symbolPrev;
unsigned char m_bits;
int m_bitCount;
int m_onesCount;
bool m_gotSOP;
unsigned char m_bytes[512]; // Info field can be 256 bytes
int m_byteCount;
crc16x25 m_crc;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
};
#endif // INCLUDE_PACKETDEMODSINK_H

Wyświetl plik

@ -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 "packetdemod.h"
#include "packetdemodwebapiadapter.h"
PacketDemodWebAPIAdapter::PacketDemodWebAPIAdapter()
{}
PacketDemodWebAPIAdapter::~PacketDemodWebAPIAdapter()
{}
int PacketDemodWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
response.getPacketDemodSettings()->init();
PacketDemod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int PacketDemodWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
PacketDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

Wyświetl plik

@ -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_PACKETDEMOD_WEBAPIADAPTER_H
#define INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "packetdemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class PacketDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
PacketDemodWebAPIAdapter();
virtual ~PacketDemodWebAPIAdapter();
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:
PacketDemodSettings m_settings;
};
#endif // INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H

Wyświetl plik

@ -0,0 +1,63 @@
<h1>Packet radio demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate packet radio (APRS/AX.25) data packets. Received packets can be sent to the APRS Feature for decoding and display.
<h2>Interface</h2>
![Packet Demodulator plugin GUI](../../../doc/img/PacketDemod_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: Modulation</h3>
This specifies the baud rate and modulation that is used for the packet transmission. Currently 1200 baud AFSK is supported.
<h3>5: RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth.
<h3>6: Frequency deviation</h3>
Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. Typical values are 2.5 kHz and 5 kHz.
<h3>7: Filter Packets From</h3>
Entering a regular expression in the From field displays only packets where the source address, displayed in the From column, matches the regular expression.
<h3>8: Filter Packets To</h3>
Entering a regular expression in the To field displays only packets where the destination address, displayed in the To column, matches the regular expression.
<h3>9: Filter PID No L3</h3>
Checking this option displays only packets where the PID (Protocol ID) field is 0xf0 (no L3). This value is used by APRS and BBS data packets, and helps to filter out control packets.
<h3>10: Clear Packets from table</h3>
Pressing this button clears all packets from the table.
<h3>Received Packets Table</h3>
The received packets table displays the contexts of the packets that have been received. Only packets with valid CRCs are displayed.
* From - The source address / callsign of the sender of the packet.
* To - The destination address.
* Via - List of addresses of repeaters the packet has passed through or directed via.
* Type - The AX.25 frame type.
* PID - Protocol Identifier.
* Data (ASCII) - The AX.25 information field displayed as ASCII.
* Data (Hex) - The AX.25 information field displayed as hexidecimal.

Wyświetl plik

@ -158,6 +158,7 @@ set(sdrbase_SOURCES
feature/feature.cpp
feature/featureset.cpp
feature/featureutils.cpp
feature/featurewebapiutils.cpp
limerfe/limerfeusbcalib.cpp
@ -174,6 +175,7 @@ set(sdrbase_SOURCES
settings/preset.cpp
settings/mainsettings.cpp
util/ax25.cpp
util/azel.cpp
util/crc.cpp
util/CRC64.cpp
@ -182,6 +184,7 @@ set(sdrbase_SOURCES
util/fixedtraits.cpp
util/httpdownloadmanager.cpp
util/lfsr.cpp
util/maidenhead.cpp
util/message.cpp
util/messagequeue.cpp
util/morse.cpp
@ -339,6 +342,7 @@ set(sdrbase_HEADERS
feature/feature.h
feature/featureset.h
feature/featureutils.h
feature/featurewebapiutils.h
limerfe/limerfeusbcalib.h
@ -361,6 +365,7 @@ set(sdrbase_HEADERS
settings/preset.h
settings/mainsettings.h
util/ax25.h
util/azel.h
util/CRC64.h
util/csv.h
@ -372,6 +377,7 @@ set(sdrbase_HEADERS
util/incrementalarray.h
util/incrementalvector.h
util/lfsr.h
util/maidenhead.h
util/message.h
util/messagequeue.h
util/morse.h

Wyświetl plik

@ -0,0 +1,213 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "ax25.h"
// CRC is assumed to be correct (checked in packetdemodsink)
bool AX25Packet::decode(QByteArray packet)
{
int i, j;
char destAddress[7];
unsigned char destSSID;
char sourceAddress[7];
unsigned char sourceSSID;
char repeaterAddress[7];
unsigned char repeaterSSID;
unsigned char ssid;
unsigned char control;
// Check for minimum size packet. Addresses, control and CRC
if (packet.size() < 7+7+1+2)
return false;
// Address - ASCII shifted right one bit
for (i = 0; i < 6; i++)
destAddress[i] = (packet[i] >> 1) & 0x7f;
destAddress[6] = NULL;
destSSID = packet[6];
for (i = 0; i < 6; i++)
sourceAddress[i] = (packet[7+i] >> 1) & 0x7f;
sourceAddress[6] = NULL;
sourceSSID = packet[13];
// From = source address
m_from = QString(sourceAddress).trimmed();
ssid = (sourceSSID >> 1) & 0xf;
if (ssid != 0)
m_from = QString("%1-%2").arg(m_from).arg(ssid);
// To = destination address
m_to = QString(destAddress).trimmed();
ssid = (destSSID >> 1) & 0xf;
if (ssid != 0)
m_to = QString("%1-%2").arg(m_to).arg(ssid);
// List of repeater addresses for via field
m_via = QString("");
i = 13;
while ((packet[i] & 1) == 0)
{
i++;
for (j = 0; j < 6; j++)
repeaterAddress[j] = (packet[i+j] >> 1) & 0x7f;
repeaterAddress[j] = NULL;
i += 6;
repeaterSSID = packet[i];
ssid = (repeaterSSID >> 1) & 0xf;
QString repeater = QString(repeaterAddress).trimmed();
QString ssidString = (ssid != 0) ? QString("%2-%3").arg(repeater).arg(ssid) : QString(repeater);
if (m_via == "")
m_via = ssidString;
else
m_via = QString("%1,%2").arg(m_via).arg(ssidString);
}
i++;
// Control can be 1 or 2 bytes - how to know if 2?
// I, U and S frames
control = packet[i++];
if ((control & 1) == 0)
m_type = QString("I");
else if ((control & 3) == 3)
{
// See figure 4.4 of AX.25 spec
switch (control & 0xef)
{
case 0x6f:
m_type = QString("SABME");
break;
case 0x2f:
m_type = QString("SABM");
break;
case 0x43:
m_type = QString("DISC");
break;
case 0x0f:
m_type = QString("DM");
break;
case 0x63:
m_type = QString("UA");
break;
case 0x87:
m_type = QString("FR");
break;
case 0x03:
m_type = QString("UI");
break;
case 0xaf:
m_type = QString("XID");
break;
case 0xe3:
m_type = QString("TEST");
break;
default:
m_type = QString("U");
break;
}
}
else
m_type = QString("S");
// APRS packets use UI frames, which are a subype of U frames
// Only I and UI frames have Layer 3 Protocol ID (PID).
if ((m_type == "I") || (m_type == "UI"))
m_pid = QString("%1").arg(((unsigned)packet[i++]) & 0xff, 2, 16, QLatin1Char('0'));
else
m_pid = QString("");
int infoStart, infoEnd;
infoStart = i;
infoEnd = packet.size()-2-i;
QByteArray info(packet.mid(infoStart, infoEnd));
m_dataASCII = QString::fromLatin1(info);
m_dataHex = QString(info.toHex());
return true;
}
bool AX25Packet::ssid(QByteArray& b, int i, int len, uint8_t& ssid)
{
if (b[i] == '-')
{
if (len > i + 1)
{
ssid = b[i+1] - '0';
if ((len > i + 2) && isdigit(b[i+2])) {
ssid = (ssid*10) + (b[i+2] - '0');
}
if (ssid >= 16)
{
// FIXME: APRS-IS seems to support 2 letter SSIDs
// These can't be sent over RF, as not enough bits in AX.25 packet
qDebug() << "AX25Packet::ssid: SSID greater than 15 not supported";
ssid = 0;
return false;
}
else
{
return true;
}
}
else
{
qDebug() << "AX25Packet::ssid: SSID number missing";
return false;
}
}
else
return false;
}
QByteArray AX25Packet::encodeAddress(QString address, uint8_t crrl)
{
int len;
int i;
QByteArray encodedAddress;
QByteArray b;
uint8_t ssid = 0;
bool hyphenSeen = false;
len = address.length();
b = address.toUtf8();
ssid = 0;
for (i = 0; i < 6; i++)
{
if ((i < len) && !hyphenSeen)
{
if (b[i] == '-')
{
AX25Packet::ssid(b, i, len, ssid);
hyphenSeen = true;
encodedAddress.append(' ' << 1);
}
else
{
encodedAddress.append(b[i] << 1);
}
}
else
{
encodedAddress.append(' ' << 1);
}
}
if (b[i] == '-')
{
AX25Packet::ssid(b, i, len, ssid);
}
encodedAddress.append(crrl | (ssid << 1));
return encodedAddress;
}

Wyświetl plik

@ -0,0 +1,42 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_AX25_H
#define INCLUDE_AX25_H
#include <QString>
#include <QByteArray>
#include "export.h"
struct SDRBASE_API AX25Packet {
QString m_from;
QString m_to;
QString m_via;
QString m_type;
QString m_pid;
QString m_dataASCII;
QString m_dataHex;
bool decode(QByteArray packet);
static bool ssid(QByteArray& b, int i, int len, uint8_t& ssid);
static QByteArray encodeAddress(QString address, uint8_t crrl=0);
};
#endif // INCLUDE_AX25_H