From 733edb2cb2b0cd605c25e4baf24d97e9805f49fd Mon Sep 17 00:00:00 2001 From: f4exb Date: Sun, 16 Feb 2020 11:26:18 +0100 Subject: [PATCH] LoRa demodulator: implement basic messaging --- plugins/channelrx/demodlora/CMakeLists.txt | 2 + plugins/channelrx/demodlora/lorademod.cpp | 61 ++- plugins/channelrx/demodlora/lorademod.h | 82 +++- .../channelrx/demodlora/lorademodbaseband.h | 2 + .../channelrx/demodlora/lorademoddecoder.cpp | 2 +- plugins/channelrx/demodlora/lorademodgui.cpp | 114 ++++- plugins/channelrx/demodlora/lorademodgui.h | 9 + plugins/channelrx/demodlora/lorademodgui.ui | 400 +++++++++++++++++- plugins/channelrx/demodlora/lorademodmsg.cpp | 20 + plugins/channelrx/demodlora/lorademodmsg.h | 79 ++++ .../channelrx/demodlora/lorademodsettings.cpp | 14 + .../channelrx/demodlora/lorademodsettings.h | 7 +- plugins/channelrx/demodlora/lorademodsink.cpp | 72 +++- plugins/channelrx/demodlora/lorademodsink.h | 18 +- plugins/channelrx/demodlora/loraplugin.cpp | 19 +- plugins/channelrx/demodlora/loraplugin.h | 19 +- 16 files changed, 869 insertions(+), 51 deletions(-) create mode 100644 plugins/channelrx/demodlora/lorademodmsg.cpp create mode 100644 plugins/channelrx/demodlora/lorademodmsg.h diff --git a/plugins/channelrx/demodlora/CMakeLists.txt b/plugins/channelrx/demodlora/CMakeLists.txt index 6d9ad7a37..82b6d51de 100644 --- a/plugins/channelrx/demodlora/CMakeLists.txt +++ b/plugins/channelrx/demodlora/CMakeLists.txt @@ -8,6 +8,7 @@ set(lora_SOURCES lorademodbaseband.cpp loraplugin.cpp lorademoddecoder.cpp + lorademodmsg.cpp lorademodgui.ui ) @@ -18,6 +19,7 @@ set(lora_HEADERS lorademodsink.h lorademodbaseband.h lorademoddecoder.h + lorademodmsg.h loraplugin.h ) diff --git a/plugins/channelrx/demodlora/lorademod.cpp b/plugins/channelrx/demodlora/lorademod.cpp index 7fd9e7b05..af78ccd4e 100644 --- a/plugins/channelrx/demodlora/lorademod.cpp +++ b/plugins/channelrx/demodlora/lorademod.cpp @@ -2,7 +2,7 @@ // Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // // written by Christian Daniel // // (c) 2015 John Greb // -// (c) 2020 Edouard Griffiths // +// (c) 2020 Edouard Griffiths, F4EXB // // // // 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 // @@ -27,21 +27,26 @@ #include "dsp/dspcommands.h" #include "device/deviceapi.h" +#include "lorademodmsg.h" #include "lorademod.h" MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureLoRaDemod, Message) +MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgReportDecodeBytes, Message) +MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgReportDecodeString, Message) const QString LoRaDemod::m_channelIdURI = "sdrangel.channel.lorademod"; const QString LoRaDemod::m_channelId = "LoRaDemod"; LoRaDemod::LoRaDemod(DeviceAPI* deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), - m_deviceAPI(deviceAPI) + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0) { setObjectName(m_channelId); m_thread = new QThread(this); m_basebandSink = new LoRaDemodBaseband(); + m_basebandSink->setDecoderMessageQueue(getInputMessageQueue()); // Decoder held on the main thread m_basebandSink->moveToThread(m_thread); applySettings(m_settings, true); @@ -95,6 +100,42 @@ bool LoRaDemod::handleMessage(const Message& cmd) return true; } + else if (LoRaDemodMsg::MsgDecodeSymbols::match(cmd)) + { + qDebug() << "LoRaDemod::handleMessage: MsgDecodeSymbols"; + LoRaDemodMsg::MsgDecodeSymbols& msg = (LoRaDemodMsg::MsgDecodeSymbols&) cmd; + + if (m_settings.m_codingScheme == LoRaDemodSettings::CodingLoRa) + { + QByteArray payload; + m_decoder.decodeSymbols(msg.getSymbols(), payload); + + if (getMessageQueueToGUI()) + { + MsgReportDecodeBytes *msgToGUI = MsgReportDecodeBytes::create(payload); + msgToGUI->setSyncWord(msg.getSyncWord()); + msgToGUI->setSignalDb(msg.getSingalDb()); + msgToGUI->setNoiseDb(msg.getNoiseDb()); + getMessageQueueToGUI()->push(msgToGUI); + } + } + else + { + QString payload; + m_decoder.decodeSymbols(msg.getSymbols(), payload); + + if (getMessageQueueToGUI()) + { + MsgReportDecodeString *msgToGUI = MsgReportDecodeString::create(payload); + msgToGUI->setSyncWord(msg.getSyncWord()); + msgToGUI->setSignalDb(msg.getSingalDb()); + msgToGUI->setNoiseDb(msg.getNoiseDb()); + getMessageQueueToGUI()->push(msgToGUI); + } + } + + return true; + } else if (DSPSignalNotification::match(cmd)) { DSPSignalNotification& notif = (DSPSignalNotification&) cmd; @@ -150,8 +191,22 @@ void LoRaDemod::applySettings(const LoRaDemodSettings& settings, bool force) << " m_title: " << settings.m_title << " force: " << force; + if ((settings.m_spreadFactor != m_settings.m_spreadFactor) + || (settings.m_deBits != m_settings.m_deBits) || force) { + m_decoder.setNbSymbolBits(settings.m_spreadFactor - settings.m_deBits); + } + + if ((settings.m_codingScheme != m_settings.m_codingScheme) || force) { + m_decoder.setCodingScheme(settings.m_codingScheme); + } + LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband *msg = LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband::create(settings, force); m_basebandSink->getInputMessageQueue()->push(msg); m_settings = settings; -} \ No newline at end of file +} + +bool LoRaDemod::getDemodActive() const +{ + return m_basebandSink->getDemodActive(); +} diff --git a/plugins/channelrx/demodlora/lorademod.h b/plugins/channelrx/demodlora/lorademod.h index cca244ed6..17bda16f7 100644 --- a/plugins/channelrx/demodlora/lorademod.h +++ b/plugins/channelrx/demodlora/lorademod.h @@ -1,6 +1,7 @@ /////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // // (C) 2015 John Greb // +// (C) 2020 Edouard Griffiths, F4EXB // // // // 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 // @@ -26,6 +27,7 @@ #include "util/message.h" #include "lorademodbaseband.h" +#include "lorademoddecoder.h" class DeviceAPI; class QThread; @@ -55,6 +57,81 @@ public: { } }; + class MsgReportDecodeBytes : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QByteArray& getBytes() const { return m_bytes; } + unsigned int getSyncWord() const { return m_syncWord; } + float getSingalDb() const { return m_signalDb; } + float getNoiseDb() const { return m_noiseDb; } + + static MsgReportDecodeBytes* create(const QByteArray& bytes) { + return new MsgReportDecodeBytes(bytes); + } + void setSyncWord(unsigned int syncWord) { + m_syncWord = syncWord; + } + void setSignalDb(float db) { + m_signalDb = db; + } + void setNoiseDb(float db) { + m_noiseDb = db; + } + + private: + QByteArray m_bytes; + unsigned int m_syncWord; + float m_signalDb; + float m_noiseDb; + + MsgReportDecodeBytes(const QByteArray& bytes) : + Message(), + m_bytes(bytes), + m_syncWord(0), + m_signalDb(0.0), + m_noiseDb(0.0) + { } + }; + + class MsgReportDecodeString : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getString() const { return m_str; } + unsigned int getSyncWord() const { return m_syncWord; } + float getSingalDb() const { return m_signalDb; } + float getNoiseDb() const { return m_noiseDb; } + + static MsgReportDecodeString* create(const QString& str) + { + return new MsgReportDecodeString(str); + } + void setSyncWord(unsigned int syncWord) { + m_syncWord = syncWord; + } + void setSignalDb(float db) { + m_signalDb = db; + } + void setNoiseDb(float db) { + m_noiseDb = db; + } + + private: + QString m_str; + unsigned int m_syncWord; + float m_signalDb; + float m_noiseDb; + + MsgReportDecodeString(const QString& str) : + Message(), + m_str(str), + m_syncWord(0), + m_signalDb(0.0), + m_noiseDb(0.0) + { } + }; + LoRaDemod(DeviceAPI* deviceAPI); virtual ~LoRaDemod(); virtual void destroy() { delete this; } @@ -82,6 +159,8 @@ public: return 0; } + bool getDemodActive() const; + static const QString m_channelIdURI; static const QString m_channelId; @@ -89,8 +168,9 @@ private: DeviceAPI *m_deviceAPI; QThread *m_thread; LoRaDemodBaseband* m_basebandSink; + LoRaDemodDecoder m_decoder; LoRaDemodSettings m_settings; - int m_basebandSampleRate; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink void applySettings(const LoRaDemodSettings& settings, bool force = false); }; diff --git a/plugins/channelrx/demodlora/lorademodbaseband.h b/plugins/channelrx/demodlora/lorademodbaseband.h index 0e49bf9a4..f4b235b26 100644 --- a/plugins/channelrx/demodlora/lorademodbaseband.h +++ b/plugins/channelrx/demodlora/lorademodbaseband.h @@ -62,7 +62,9 @@ public: void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication int getChannelSampleRate() const; + bool getDemodActive() const { return m_sink.getDemodActive(); } void setBasebandSampleRate(int sampleRate); + void setDecoderMessageQueue(MessageQueue *messageQueue) { m_sink.setDecoderMessageQueue(messageQueue); } void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_sink.setSpectrumSink(spectrumSink); } private: diff --git a/plugins/channelrx/demodlora/lorademoddecoder.cpp b/plugins/channelrx/demodlora/lorademoddecoder.cpp index 160469be0..549c9d6d0 100644 --- a/plugins/channelrx/demodlora/lorademoddecoder.cpp +++ b/plugins/channelrx/demodlora/lorademoddecoder.cpp @@ -99,7 +99,7 @@ void LoRaDemodDecoder::decodeSymbolsTTY(const std::vector& symbols if (ttyState == TTYLetters) { asciiChar = ttyLetters[ttyChar]; - } else if (ttyState == TTYLetters) { + } else if (ttyState == TTYFigures) { asciiChar = ttyFigures[ttyChar]; } diff --git a/plugins/channelrx/demodlora/lorademodgui.cpp b/plugins/channelrx/demodlora/lorademodgui.cpp index 7247e8828..23d016538 100644 --- a/plugins/channelrx/demodlora/lorademodgui.cpp +++ b/plugins/channelrx/demodlora/lorademodgui.cpp @@ -16,8 +16,8 @@ /////////////////////////////////////////////////////////////////////////////////// #include "device/deviceuiset.h" -#include -#include +#include +#include #include "ui_lorademodgui.h" #include "dsp/spectrumvis.h" @@ -27,6 +27,7 @@ #include "gui/glspectrumgui.h" #include "plugin/pluginapi.h" #include "util/simpleserializer.h" +#include "mainwindow.h" #include "lorademod.h" #include "lorademodgui.h" @@ -105,6 +106,23 @@ bool LoRaDemodGUI::handleMessage(const Message& message) return true; } + else if (LoRaDemod::MsgReportDecodeBytes::match(message)) + { + const LoRaDemod::MsgReportDecodeBytes& msg = (LoRaDemod::MsgReportDecodeBytes&) message; + QByteArray bytes = msg.getBytes(); + ui->hexText->setText(bytes.toHex()); + ui->syncWord->setText((tr("%1").arg(msg.getSyncWord(), 2, 16))); + ui->sText->setText(tr("%1").arg(msg.getSingalDb(), 0, 'f', 1)); + ui->snrText->setText(tr("%1").arg(msg.getSingalDb() - msg.getNoiseDb(), 0, 'f', 1)); + } + else if (LoRaDemod::MsgReportDecodeString::match(message)) + { + const LoRaDemod::MsgReportDecodeString& msg = (LoRaDemod::MsgReportDecodeString&) message; + addText(msg.getString()); + ui->syncWord->setText((tr("%1").arg(msg.getSyncWord(), 2, 16))); + ui->sText->setText(tr("%1").arg(msg.getSingalDb(), 0, 'f', 1)); + ui->snrText->setText(tr("%1").arg(msg.getSingalDb() - msg.getNoiseDb(), 0, 'f', 1)); + } else { return false; @@ -177,6 +195,38 @@ void LoRaDemodGUI::on_deBits_valueChanged(int value) applySettings(); } +void LoRaDemodGUI::on_scheme_currentIndexChanged(int index) +{ + m_settings.m_codingScheme = (LoRaDemodSettings::CodingScheme) index; + applySettings(); +} + +void LoRaDemodGUI::on_mute_toggled(bool checked) +{ + m_settings.m_decodeActive = !checked; + applySettings(); +} + +void LoRaDemodGUI::on_clear_clicked(bool checked) +{ + (void) checked; + ui->messageText->clear(); +} + +void LoRaDemodGUI::on_eomSquelch_valueChanged(int value) +{ + m_settings.m_eomSquelchTenths = value; + displaySquelch(); + applySettings(); +} + +void LoRaDemodGUI::on_messageLength_valueChanged(int value) +{ + m_settings.m_nbSymbolsMax = value; + ui->messageLengthText->setText(tr("%1").arg(m_settings.m_nbSymbolsMax)); + applySettings(); +} + void LoRaDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -190,7 +240,8 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_deviceUISet(deviceUISet), m_channelMarker(this), m_basebandSampleRate(250000), - m_doApplySettings(true) + m_doApplySettings(true), + m_tickCount(0) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); @@ -201,13 +252,19 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb m_LoRaDemod->setSpectrumSink(m_spectrumVis); m_LoRaDemod->setMessageQueueToGUI(getInputMessageQueue()); - ui->glSpectrum->setDisplayWaterfall(true); + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + ui->glSpectrum->setDisplayWaterfall(true); ui->glSpectrum->setDisplayMaxHold(true); ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + ui->messageText->setReadOnly(true); + ui->syncWord->setReadOnly(true); + ui->hexText->setReadOnly(true); + m_channelMarker.setMovable(true); m_channelMarker.setVisible(true); @@ -276,10 +333,25 @@ void LoRaDemodGUI::displaySettings() ui->SpreadText->setText(tr("%1").arg(m_settings.m_spreadFactor)); ui->deBits->setValue(m_settings.m_deBits); ui->deBitsText->setText(tr("%1").arg(m_settings.m_deBits)); + ui->scheme->setCurrentIndex((int) m_settings.m_codingScheme); + ui->messageLengthText->setText(tr("%1").arg(m_settings.m_nbSymbolsMax)); + ui->messageLength->setValue(m_settings.m_nbSymbolsMax); ui->spectrumGUI->setFFTSize(m_settings.m_spreadFactor); + displaySquelch(); blockApplySettings(false); } +void LoRaDemodGUI::displaySquelch() +{ + ui->eomSquelch->setValue(m_settings.m_eomSquelchTenths); + + if (m_settings.m_eomSquelchTenths == ui->eomSquelch->maximum()) { + ui->eomSquelchText->setText("---"); + } else { + ui->eomSquelchText->setText(tr("%1").arg(m_settings.m_eomSquelchTenths / 10.0, 0, 'f', 1)); + } +} + void LoRaDemodGUI::setBandwidths() { int maxBandwidth = m_basebandSampleRate/LoRaDemodSettings::oversampling; @@ -295,4 +367,36 @@ void LoRaDemodGUI::setBandwidths() int index = ui->BW->value(); ui->BWText->setText(QString("%1 Hz").arg(LoRaDemodSettings::bandwidths[index])); } -} \ No newline at end of file +} + +void LoRaDemodGUI::addText(const QString& text) +{ + QDateTime dt = QDateTime::currentDateTime(); + QString dateStr = dt.toString("HH:mm:ss"); + QTextCursor cursor = ui->messageText->textCursor(); + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + if (!ui->messageText->document()->isEmpty()) { + cursor.insertText("\n"); + } + cursor.insertText(tr("%1 %2").arg(dateStr).arg(text)); + ui->messageText->verticalScrollBar()->setValue(ui->messageText->verticalScrollBar()->maximum()); +} + + +void LoRaDemodGUI::tick() +{ + if (m_tickCount < 10) + { + m_tickCount++; + } + else + { + m_tickCount = 0; + + if (m_LoRaDemod->getDemodActive()) { + ui->mute->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->mute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + } +} diff --git a/plugins/channelrx/demodlora/lorademodgui.h b/plugins/channelrx/demodlora/lorademodgui.h index 2de112fea..9f8ddc78f 100644 --- a/plugins/channelrx/demodlora/lorademodgui.h +++ b/plugins/channelrx/demodlora/lorademodgui.h @@ -59,9 +59,15 @@ private slots: void on_BW_valueChanged(int value); void on_Spread_valueChanged(int value); void on_deBits_valueChanged(int value); + void on_scheme_currentIndexChanged(int index); + void on_mute_toggled(bool checked); + void on_clear_clicked(bool checked); + void on_eomSquelch_valueChanged(int value); + void on_messageLength_valueChanged(int value); void onWidgetRolled(QWidget* widget, bool rollDown); void channelMarkerHighlightedByCursor(); void handleInputMessages(); + void tick(); private: Ui::LoRaDemodGUI* ui; @@ -75,6 +81,7 @@ private: LoRaDemod* m_LoRaDemod; SpectrumVis* m_spectrumVis; MessageQueue m_inputMessageQueue; + unsigned int m_tickCount; explicit LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); virtual ~LoRaDemodGUI(); @@ -82,7 +89,9 @@ private: void blockApplySettings(bool block); void applySettings(bool force = false); void displaySettings(); + void displaySquelch(); void setBandwidths(); + void addText(const QString& text); }; #endif // INCLUDE_LoRaDEMODGUI_H diff --git a/plugins/channelrx/demodlora/lorademodgui.ui b/plugins/channelrx/demodlora/lorademodgui.ui index 6ac029ee2..af938a025 100644 --- a/plugins/channelrx/demodlora/lorademodgui.ui +++ b/plugins/channelrx/demodlora/lorademodgui.ui @@ -6,14 +6,14 @@ 0 0 - 350 - 500 + 410 + 620 - 350 - 500 + 380 + 620 @@ -30,14 +30,14 @@ 10 20 - 331 - 112 + 390 + 102 0 - 112 + 102 @@ -74,7 +74,7 @@ 30 50 - 180 + 251 16 @@ -102,7 +102,7 @@ 30 70 - 100 + 131 16 @@ -131,7 +131,7 @@ - 140 + 170 70 30 16 @@ -153,7 +153,7 @@ - 240 + 300 50 80 16 @@ -175,7 +175,7 @@ - 190 + 220 70 22 16 @@ -188,7 +188,7 @@ - 290 + 350 70 30 16 @@ -210,14 +210,14 @@ - 220 + 250 70 - 60 + 91 16 - Low data rate optimize (DE) bits + Distance Enhancement bits i.e. log2 of number of FFT bins per effective sample 0 @@ -243,7 +243,7 @@ 10 10 - 311 + 371 26 @@ -319,18 +319,378 @@ + + + + S + + + + + + + + 30 + 0 + + + + De-chirped signal level + + + -50.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 35 + 0 + + + + SNR + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 25 + 0 + + + + De-chirped SNR level + + + 10.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + dB + + + + + + + 10 + 130 + 390 + 160 + + + + + 0 + 160 + + + + Payload + + + + + 2 + 40 + 32 + 16 + + + + Msg + + + + + + 30 + 120 + 351 + 20 + + + + + + + 2 + 120 + 32 + 16 + + + + Hex + + + + + + 30 + 40 + 351 + 75 + + + + + + + 2 + 10 + 50 + 16 + + + + Scheme + + + + + + 60 + 8 + 86 + 20 + + + + + LoRa + + + + + ASCII + + + + + TTY + + + + + + + 4 + 60 + 20 + 20 + + + + + 24 + 24 + + + + Clear text + + + + + + + :/sweep.png:/sweep.png + + + false + + + + + + 150 + 8 + 20 + 20 + + + + Run/Stop decoder + + + + + + + :/stop.png + :/play.png:/stop.png + + + true + + + + + + 180 + 10 + 35 + 19 + + + + EOM + + + + + + 210 + 8 + 22 + 22 + + + + End Of Message squelch factor + + + 40 + + + 121 + + + 1 + + + 60 + + + + + + 230 + 10 + 28 + 19 + + + + 10.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 272 + 10 + 20 + 19 + + + + ML + + + + + + 290 + 8 + 22 + 22 + + + + Message (payload) length in number of symbols + + + 20 + + + 255 + + + 1 + + + 255 + + + + + + 310 + 10 + 25 + 19 + + + + 255 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 350 + 9 + 25 + 20 + + + + Qt::ClickFocus + + + Sync word (1 byte hex) + + + HH + + + 00 + + + 10 - 140 - 331 - 341 + 300 + 390 + 310 + + + 373 + 0 + + De-chirped Spectrum diff --git a/plugins/channelrx/demodlora/lorademodmsg.cpp b/plugins/channelrx/demodlora/lorademodmsg.cpp new file mode 100644 index 000000000..458d89238 --- /dev/null +++ b/plugins/channelrx/demodlora/lorademodmsg.cpp @@ -0,0 +1,20 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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 "lorademodmsg.h" + +MESSAGE_CLASS_DEFINITION(LoRaDemodMsg::MsgDecodeSymbols, Message) diff --git a/plugins/channelrx/demodlora/lorademodmsg.h b/plugins/channelrx/demodlora/lorademodmsg.h new file mode 100644 index 000000000..4cab52ce2 --- /dev/null +++ b/plugins/channelrx/demodlora/lorademodmsg.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// 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_LORADEMODMSG_H +#define INCLUDE_LORADEMODMSG_H + +#include +#include "util/message.h" + +namespace LoRaDemodMsg +{ + class MsgDecodeSymbols : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const std::vector& getSymbols() const { return m_symbols; } + unsigned int getSyncWord() const { return m_syncWord; } + float getSingalDb() const { return m_signalDb; } + float getNoiseDb() const { return m_noiseDb; } + + void pushBackSymbol(unsigned int symbol) { + m_symbols.push_back(symbol); + } + void popSymbol() { + m_symbols.pop_back(); + } + void setSyncWord(unsigned char syncWord) { + m_syncWord = syncWord; + } + void setSignalDb(float db) { + m_signalDb = db; + } + void setNoiseDb(float db) { + m_noiseDb = db; + } + + static MsgDecodeSymbols* create() { + return new MsgDecodeSymbols(); + } + static MsgDecodeSymbols* create(const std::vector symbols) { + return new MsgDecodeSymbols(symbols); + } + + private: + std::vector m_symbols; + unsigned int m_syncWord; + float m_signalDb; + float m_noiseDb; + + MsgDecodeSymbols() : //!< create an empty message + Message(), + m_syncWord(0), + m_signalDb(0.0), + m_noiseDb(0.0) + {} + MsgDecodeSymbols(const std::vector symbols) : //!< create a message with symbols copy + Message(), + m_syncWord(0), + m_signalDb(0.0), + m_noiseDb(0.0) + { m_symbols = symbols; } + }; +} + +#endif // INCLUDE_LORADEMODMSG_H diff --git a/plugins/channelrx/demodlora/lorademodsettings.cpp b/plugins/channelrx/demodlora/lorademodsettings.cpp index 683f3dd32..2e458ff51 100644 --- a/plugins/channelrx/demodlora/lorademodsettings.cpp +++ b/plugins/channelrx/demodlora/lorademodsettings.cpp @@ -41,6 +41,10 @@ void LoRaDemodSettings::resetToDefaults() m_bandwidthIndex = 5; m_spreadFactor = 7; m_deBits = 0; + m_codingScheme = CodingLoRa; + m_decodeActive = true; + m_eomSquelchTenths = 60; + m_nbSymbolsMax = 255; m_rgbColor = QColor(255, 0, 255).rgb(); m_title = "LoRa Demodulator"; } @@ -62,6 +66,10 @@ QByteArray LoRaDemodSettings::serialize() const s.writeString(6, m_title); s.writeS32(7, m_deBits); + s.writeS32(8, m_codingScheme); + s.writeBool(9, m_decodeActive); + s.writeS32(10, m_eomSquelchTenths); + s.writeS32(11, m_nbSymbolsMax); return s.final(); } @@ -79,6 +87,7 @@ bool LoRaDemodSettings::deserialize(const QByteArray& data) if(d.getVersion() == 1) { QByteArray bytetmp; + int tmp; d.readS32(1, &m_inputFrequencyOffset, 0); d.readS32(2, &m_bandwidthIndex, 0); @@ -96,6 +105,11 @@ bool LoRaDemodSettings::deserialize(const QByteArray& data) d.readString(6, &m_title, "LoRa Demodulator"); d.readS32(7, &m_deBits, 0); + d.readS32(8, &tmp); + m_codingScheme = (CodingScheme) tmp; + d.readBool(9, &m_decodeActive, true); + d.readS32(10, &m_eomSquelchTenths, 60); + d.readS32(11, &m_nbSymbolsMax, 255); return true; } diff --git a/plugins/channelrx/demodlora/lorademodsettings.h b/plugins/channelrx/demodlora/lorademodsettings.h index 639235a13..8d2c0657d 100644 --- a/plugins/channelrx/demodlora/lorademodsettings.h +++ b/plugins/channelrx/demodlora/lorademodsettings.h @@ -30,9 +30,9 @@ struct LoRaDemodSettings { enum CodingScheme { - CodingTTY, //!< plain TTY (5 bits) + CodingLoRa, //!< Standard LoRa CodingASCII, //!< plain ASCII (7 bits) - CodingLoRa //!< Standard LoRa + CodingTTY //!< plain TTY (5 bits) }; int m_inputFrequencyOffset; @@ -40,6 +40,9 @@ struct LoRaDemodSettings int m_spreadFactor; int m_deBits; //!< Low data rate optmize (DE) bits CodingScheme m_codingScheme; + bool m_decodeActive; + int m_eomSquelchTenths; //!< Squelch factor to trigger end of message (/10) + int m_nbSymbolsMax; //!< Maximum number of symbols in a payload uint32_t m_rgbColor; QString m_title; diff --git a/plugins/channelrx/demodlora/lorademodsink.cpp b/plugins/channelrx/demodlora/lorademodsink.cpp index 0160ced3a..e07a24f58 100644 --- a/plugins/channelrx/demodlora/lorademodsink.cpp +++ b/plugins/channelrx/demodlora/lorademodsink.cpp @@ -22,10 +22,14 @@ #include "dsp/dsptypes.h" #include "dsp/basebandsamplesink.h" #include "dsp/fftengine.h" +#include "util/db.h" +#include "lorademodmsg.h" #include "lorademodsink.h" LoRaDemodSink::LoRaDemodSink() : + m_decodeMsg(nullptr), + m_decoderMsgQueue(nullptr), m_spectrumSink(nullptr), m_spectrumBuffer(nullptr), m_downChirps(nullptr), @@ -33,6 +37,7 @@ LoRaDemodSink::LoRaDemodSink() : m_fftBuffer(nullptr), m_spectrumLine(nullptr) { + m_demodActive = false; m_bandwidth = LoRaDemodSettings::bandwidths[0]; m_channelSampleRate = 96000; m_channelFrequencyOffset = 0; @@ -134,6 +139,7 @@ void LoRaDemodSink::processSample(const Complex& ci) { if (m_state == LoRaStateReset) // start over { + m_demodActive = false; reset(); m_state = LoRaStateDetectPreamble; } @@ -147,21 +153,22 @@ void LoRaDemodSink::processSample(const Complex& ci) std::fill(m_fft->in()+m_fftLength, m_fft->in()+m_fftInterpolation*m_fftLength, Complex{0.0, 0.0}); m_fft->transform(); m_fftCounter = 0; + double magsq; unsigned int imax = argmax( m_fft->out(), m_fftInterpolation, m_fftLength, - m_magsq, + magsq, m_spectrumBuffer, m_fftInterpolation ) / m_fftInterpolation; - // Debug: - // if (m_spectrumSink) { - // m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols); - // } + if (m_magsqQueue.size() > m_requiredPreambleChirps + 1) { + m_magsqQueue.pop(); + } + m_magsqQueue.push(magsq); m_argMaxHistory[m_argMaxHistoryCounter++] = imax; if (m_argMaxHistoryCounter == m_requiredPreambleChirps) @@ -178,19 +185,23 @@ void LoRaDemodSink::processSample(const Complex& ci) } } - if ((preambleFound) && (m_magsq > 1e-9)) + if ((preambleFound) && (magsq > 1e-9)) { if (m_spectrumSink) { m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols); } - qDebug("LoRaDemodSink::processSample: preamble found: %u|%f", m_argMaxHistory[0], m_magsq); + qDebug("LoRaDemodSink::processSample: preamble found: %u|%f", m_argMaxHistory[0], magsq); m_chirp = m_argMaxHistory[0]; m_fftCounter = m_chirp; m_chirp0 = 0; m_chirpCount = 0; m_state = LoRaStatePreambleResyc; } + else + { + m_magsqOffAvg(m_magsqQueue.front()); + } } } } @@ -205,6 +216,7 @@ void LoRaDemodSink::processSample(const Complex& ci) } m_fftCounter = 0; + m_demodActive = true; m_state = LoRaStatePreamble; } } @@ -281,7 +293,6 @@ void LoRaDemodSink::processSample(const Complex& ci) m_fftCounter = m_fftLength - m_sfdSkip + zadj; m_chirp += zadj; //std::copy(m_fftBuffer+m_sfdSkip, m_fftBuffer+(m_fftLength-m_sfdSkip), m_fftBuffer); // prepare sliding fft - m_magsq = magsqSFD; m_state = LoRaStateSkipSFD; //LoRaStateSlideSFD; } } @@ -296,7 +307,7 @@ void LoRaDemodSink::processSample(const Complex& ci) } qDebug("LoRaDemodSink::processSample: SFD search: up: %4u|%11.6f - down: %4u|%11.6f", imax, magsq, imaxSFD, magsqSFD); - m_magsq = magsq; + m_magsqOnAvg(magsq); } } } @@ -311,12 +322,15 @@ void LoRaDemodSink::processSample(const Complex& ci) if (m_sfdSkipCounter == m_sfdFourths) // 1.25 SFD chips left { + qDebug("LoRaDemodSink::processSample: SFD skipped"); m_chirp = m_chirp0; m_fftCounter = 0; m_chirpCount = 0; int correction = 0; - qDebug("LoRaDemodSink::processSample: SFD skipped"); - m_state = LoRaStateReadPayload; //LoRaStateReadPayload; + m_magsqMax = 0.0; + m_decodeMsg = LoRaDemodMsg::MsgDecodeSymbols::create(); + m_decodeMsg->setSyncWord(m_syncWord); + m_state = LoRaStateReadPayload; } } } @@ -353,11 +367,14 @@ void LoRaDemodSink::processSample(const Complex& ci) if (m_sfdSkipCounter == m_sfdFourths) // 1.25 SFD chips length { + qDebug("LoRaDemodSink::processSample: SFD done"); m_chirp = m_chirp0; m_fftCounter = 0; m_chirpCount = 0; int correction = 0; - qDebug("LoRaDemodSink::processSample: SFD done"); + m_magsqMax = 0.0; + m_decodeMsg = LoRaDemodMsg::MsgDecodeSymbols::create(); + m_decodeMsg->setSyncWord(m_syncWord); m_state = LoRaStateReadPayload; //LoRaStateReadPayload; } } @@ -390,22 +407,47 @@ void LoRaDemodSink::processSample(const Complex& ci) m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols); } - if ((m_chirpCount == 0) || (10.0*magsq > m_magsq)) + if (magsq > m_magsqMax) { + m_magsqMax = magsq; + } + + m_decodeMsg->pushBackSymbol(symbol); + + if ((m_chirpCount == 0) + || (m_settings.m_eomSquelchTenths == 121) // max - disable squelch + || ((m_settings.m_eomSquelchTenths*magsq)/10.0 > m_magsqMax)) { qDebug("LoRaDemodSink::processSample: symbol %02u: %4u|%11.6f", m_chirpCount, symbol, magsq); - m_magsq = magsq; + m_magsqOnAvg(magsq); m_chirpCount++; - if (m_chirpCount > 255) + if (m_chirpCount > m_settings.m_nbSymbolsMax) { qDebug("LoRaDemodSink::processSample: message length exceeded"); m_state = LoRaStateReset; + m_decodeMsg->setSignalDb(CalcDb::dbPower(m_magsqOnAvg.asDouble() / (1<setNoiseDb(CalcDb::dbPower(m_magsqOffAvg.asDouble() / (1<push(m_decodeMsg); + } else { + delete m_decodeMsg; + } } } else { qDebug("LoRaDemodSink::processSample: end of message"); m_state = LoRaStateReset; + m_decodeMsg->popSymbol(); // last symbol is garbage + m_decodeMsg->setSignalDb(CalcDb::dbPower(m_magsqOnAvg.asDouble() / (1<setNoiseDb(CalcDb::dbPower(m_magsqOffAvg.asDouble() / (1<push(m_decodeMsg); + } else { + delete m_decodeMsg; + } } } } diff --git a/plugins/channelrx/demodlora/lorademodsink.h b/plugins/channelrx/demodlora/lorademodsink.h index f7fd39e9c..baab0ffd2 100644 --- a/plugins/channelrx/demodlora/lorademodsink.h +++ b/plugins/channelrx/demodlora/lorademodsink.h @@ -19,17 +19,23 @@ #define INCLUDE_LORADEMODSINK_H #include +#include #include "dsp/channelsamplesink.h" #include "dsp/nco.h" #include "dsp/interpolator.h" -#include "util/message.h" #include "dsp/fftwindow.h" +#include "util/message.h" +#include "util/movingaverage.h" #include "lorademodsettings.h" class BasebandSampleSink; class FFTEngine; +namespace LoRaDemodMsg { + class MsgDecodeSymbols; +} +class MessageQueue; class LoRaDemodSink : public ChannelSampleSink { public: @@ -38,6 +44,8 @@ public: virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + bool getDemodActive() const { return m_demodActive; } + void setDecoderMessageQueue(MessageQueue *messageQueue) { m_decoderMsgQueue = messageQueue; } void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; } void applyChannelSettings(int channelSampleRate, int bandwidth, int channelFrequencyOffset, bool force = false); void applySettings(const LoRaDemodSettings& settings, bool force = false); @@ -57,6 +65,9 @@ private: LoRaDemodSettings m_settings; LoRaState m_state; + bool m_demodActive; + LoRaDemodMsg::MsgDecodeSymbols *m_decodeMsg; + MessageQueue *m_decoderMsgQueue; int m_bandwidth; int m_channelSampleRate; int m_channelFrequencyOffset; @@ -80,7 +91,10 @@ private: unsigned int m_argMaxHistoryCounter; unsigned int m_preambleHistory[m_maxSFDSearchChirps]; unsigned int m_syncWord; - double m_magsq; + double m_magsqMax; + MovingAverageUtil m_magsqOnAvg; + MovingAverageUtil m_magsqOffAvg; + std::queue m_magsqQueue; unsigned int m_chirpCount; //!< Generic chirp counter unsigned int m_sfdSkip; //!< Number of samples in a SFD skip or slide (1/4) period unsigned int m_sfdSkipCounter; //!< Counter of skip or slide periods diff --git a/plugins/channelrx/demodlora/loraplugin.cpp b/plugins/channelrx/demodlora/loraplugin.cpp index 3693cdde6..7cd8ffba0 100644 --- a/plugins/channelrx/demodlora/loraplugin.cpp +++ b/plugins/channelrx/demodlora/loraplugin.cpp @@ -1,3 +1,20 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2020 Edouard Griffiths, F4EXB // +// // +// 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" @@ -17,7 +34,7 @@ const PluginDescriptor LoRaPlugin::m_pluginDescriptor = { LoRaPlugin::LoRaPlugin(QObject* parent) : QObject(parent), - m_pluginAPI(0) + m_pluginAPI(nullptr) { } diff --git a/plugins/channelrx/demodlora/loraplugin.h b/plugins/channelrx/demodlora/loraplugin.h index 88b8d7705..0111339d5 100644 --- a/plugins/channelrx/demodlora/loraplugin.h +++ b/plugins/channelrx/demodlora/loraplugin.h @@ -1,3 +1,20 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2020 Edouard Griffiths, F4EXB // +// // +// 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_LoRaPLUGIN_H #define INCLUDE_LoRaPLUGIN_H @@ -13,7 +30,7 @@ class LoRaPlugin : public QObject, PluginInterface { Q_PLUGIN_METADATA(IID "sdrangel.channel.lorademod") public: - explicit LoRaPlugin(QObject* parent = NULL); + explicit LoRaPlugin(QObject* parent = nullptr); const PluginDescriptor& getPluginDescriptor() const; void initPlugin(PluginAPI* pluginAPI);