diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 36623f77b..fc20e0df6 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -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) diff --git a/plugins/channelrx/demodpacket/CMakeLists.txt b/plugins/channelrx/demodpacket/CMakeLists.txt new file mode 100644 index 000000000..2c50abb49 --- /dev/null +++ b/plugins/channelrx/demodpacket/CMakeLists.txt @@ -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}) diff --git a/plugins/channelrx/demodpacket/packetdemod.cpp b/plugins/channelrx/demodpacket/packetdemod.cpp new file mode 100644 index 000000000..242968501 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemod.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "packetdemod.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 *packetMessageQueues = messagePipes.getMessageQueues(this, "packets"); + if (packetMessageQueues) + { + QList::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 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& 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& 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(); +} diff --git a/plugins/channelrx/demodpacket/packetdemod.h b/plugins/channelrx/demodpacket/packetdemod.h new file mode 100644 index 000000000..3bff4f0ef --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemod.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMOD_H +#define INCLUDE_PACKETDEMOD_H + +#include + +#include +#include + +#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& channelSettingsKeys, const PacketDemodSettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const PacketDemodSettings& settings, + bool force + ); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + +}; + +#endif // INCLUDE_PACKETDEMOD_H diff --git a/plugins/channelrx/demodpacket/packetdemodbaseband.cpp b/plugins/channelrx/demodpacket/packetdemodbaseband.cpp new file mode 100644 index 000000000..61b729f0d --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodbaseband.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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()); +} diff --git a/plugins/channelrx/demodpacket/packetdemodbaseband.h b/plugins/channelrx/demodpacket/packetdemodbaseband.h new file mode 100644 index 000000000..cee2cd39a --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodbaseband.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODBASEBAND_H +#define INCLUDE_PACKETDEMODBASEBAND_H + +#include +#include + +#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 diff --git a/plugins/channelrx/demodpacket/packetdemodgui.cpp b/plugins/channelrx/demodpacket/packetdemodgui.cpp new file mode 100644 index 000000000..c11cdfb58 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(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++; +} diff --git a/plugins/channelrx/demodpacket/packetdemodgui.h b/plugins/channelrx/demodpacket/packetdemodgui.h new file mode 100644 index 000000000..898df575b --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODGUI_H +#define INCLUDE_PACKETDEMODGUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/plugins/channelrx/demodpacket/packetdemodgui.ui b/plugins/channelrx/demodpacket/packetdemodgui.ui new file mode 100644 index 000000000..b4c67fada --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodgui.ui @@ -0,0 +1,579 @@ + + + PacketDemodGUI + + + + 0 + 0 + 398 + 519 + + + + + 0 + 0 + + + + + 352 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + Packet Demodulator + + + Packet Demodulator + + + + + 0 + 0 + 390 + 101 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + + + + 100 + 0 + + + + Baud rate and modulation + + + + 1200 AFSK + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + BW + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + RF bandwidth + + + 10 + + + 400 + + + 1 + + + 100 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 10.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Dev + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Frequency deviation + + + 10 + + + 60 + + + 1 + + + 50 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 5.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + From + + + + + + + Display only packets where the source address (From) matches the specified regular expression + + + + + + + To: + + + + + + + Display only packets where the destination address (To) matches the specified regular expression + + + + + + + + + + Check to display only packets with PID set to No L3 (f0). This is typically used for APRS and BBS packets. + + + PID No L3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear packets from table + + + + + + + :/bin.png:/bin.png + + + + + + + + + + + 0 + 110 + 391 + 261 + + + + + 0 + 0 + + + + Received Packets + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Received packets + + + QAbstractItemView::NoEditTriggers + + + + From + + + Source callsign/address + + + + + To + + + Destination callsign/address + + + + + Via + + + Repeater addresses + + + + + Type + + + AX.25 frame type + + + + + PID + + + Layer 3 protocol ID + + + + + Data (ASCII) + + + Packet data as ASCII + + + + + Data (Hex) + + + Packet data as hex + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+
+ + packets + + + + + +
diff --git a/plugins/channelrx/demodpacket/packetdemodplugin.cpp b/plugins/channelrx/demodpacket/packetdemodplugin.cpp new file mode 100644 index 000000000..27bfe534e --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodplugin.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "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(); +} diff --git a/plugins/channelrx/demodpacket/packetdemodplugin.h b/plugins/channelrx/demodpacket/packetdemodplugin.h new file mode 100644 index 000000000..f8e3c3dd1 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODPLUGIN_H +#define INCLUDE_PACKETDEMODPLUGIN_H + +#include +#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 diff --git a/plugins/channelrx/demodpacket/packetdemodsettings.cpp b/plugins/channelrx/demodpacket/packetdemodsettings.cpp new file mode 100644 index 000000000..3d8d1546f --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsettings.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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; + } +} + + diff --git a/plugins/channelrx/demodpacket/packetdemodsettings.h b/plugins/channelrx/demodpacket/packetdemodsettings.h new file mode 100644 index 000000000..326f09fa3 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsettings.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_PACKETDEMODSETTINGS_H +#define INCLUDE_PACKETDEMODSETTINGS_H + +#include +#include + +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 */ diff --git a/plugins/channelrx/demodpacket/packetdemodsink.cpp b/plugins/channelrx/demodpacket/packetdemodsink.cpp new file mode 100644 index 000000000..572c16fd7 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsink.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#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; +} diff --git a/plugins/channelrx/demodpacket/packetdemodsink.h b/plugins/channelrx/demodpacket/packetdemodsink.h new file mode 100644 index 000000000..45ebb8de7 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodsink.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#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 +#include +#include + +#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 m_movingAverage; + + Lowpass m_lowpass; + PhaseDiscriminators m_phaseDiscri; + + int m_correlationLength; + Complex *m_f1; + Complex *m_f0; + Complex *m_corrBuf; + int m_corrIdx; + int m_corrCnt; + + Lowpass m_lowpassF1; + Lowpass 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 diff --git a/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp new file mode 100644 index 000000000..60ba79449 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "SWGChannelSettings.h" +#include "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; +} diff --git a/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h new file mode 100644 index 000000000..79f3660b5 --- /dev/null +++ b/plugins/channelrx/demodpacket/packetdemodwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_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 diff --git a/plugins/channelrx/demodpacket/readme.md b/plugins/channelrx/demodpacket/readme.md new file mode 100644 index 000000000..803f074fd --- /dev/null +++ b/plugins/channelrx/demodpacket/readme.md @@ -0,0 +1,63 @@ +

Packet radio demodulator plugin

+ +

Introduction

+ +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. + +

Interface

+ +![Packet Demodulator plugin GUI](../../../doc/img/PacketDemod_plugin.png) + +

1: Frequency shift from center frequency of reception

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

2: Channel power

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

3: Level meter in dB

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

4: Modulation

+ +This specifies the baud rate and modulation that is used for the packet transmission. Currently 1200 baud AFSK is supported. + +

5: RF Bandwidth

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

6: Frequency deviation

+ +Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. Typical values are 2.5 kHz and 5 kHz. + +

7: Filter Packets From

+ +Entering a regular expression in the From field displays only packets where the source address, displayed in the From column, matches the regular expression. + +

8: Filter Packets To

+ +Entering a regular expression in the To field displays only packets where the destination address, displayed in the To column, matches the regular expression. + +

9: Filter PID No L3

+ +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. + +

10: Clear Packets from table

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

Received Packets Table

+ +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. diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index 87ba1431b..2735611a2 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -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 diff --git a/sdrbase/util/ax25.cpp b/sdrbase/util/ax25.cpp new file mode 100644 index 000000000..6d562d8e2 --- /dev/null +++ b/sdrbase/util/ax25.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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; +} diff --git a/sdrbase/util/ax25.h b/sdrbase/util/ax25.h new file mode 100644 index 000000000..44a45ffc3 --- /dev/null +++ b/sdrbase/util/ax25.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AX25_H +#define INCLUDE_AX25_H + +#include +#include + +#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