1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-11-14 20:23:29 -05:00

LoRa demodulator: implement basic messaging

This commit is contained in:
f4exb 2020-02-16 11:26:18 +01:00
parent d4ede8457b
commit 733edb2cb2
16 changed files with 869 additions and 51 deletions

View File

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

View File

@ -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);
@ -93,6 +98,42 @@ bool LoRaDemod::handleMessage(const Message& cmd)
LoRaDemodSettings settings = cfg.getSettings();
applySettings(settings, cfg.getForce());
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))
@ -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;
}
bool LoRaDemod::getDemodActive() const
{
return m_basebandSink->getDemodActive();
}

View File

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

View File

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

View File

@ -99,7 +99,7 @@ void LoRaDemodDecoder::decodeSymbolsTTY(const std::vector<unsigned int>& symbols
if (ttyState == TTYLetters) {
asciiChar = ttyLetters[ttyChar];
} else if (ttyState == TTYLetters) {
} else if (ttyState == TTYFigures) {
asciiChar = ttyFigures[ttyChar];
}

View File

@ -16,8 +16,8 @@
///////////////////////////////////////////////////////////////////////////////////
#include "device/deviceuiset.h"
#include <QDockWidget>
#include <QMainWindow>
#include <QScrollBar>
#include <QDebug>
#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,6 +252,8 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
m_LoRaDemod->setSpectrumSink(m_spectrumVis);
m_LoRaDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
ui->glSpectrum->setDisplayWaterfall(true);
ui->glSpectrum->setDisplayMaxHold(true);
@ -208,6 +261,10 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
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;
@ -296,3 +368,35 @@ void LoRaDemodGUI::setBandwidths()
ui->BWText->setText(QString("%1 Hz").arg(LoRaDemodSettings::bandwidths[index]));
}
}
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); }");
}
}
}

View File

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

View File

@ -6,14 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>500</height>
<width>410</width>
<height>620</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>500</height>
<width>380</width>
<height>620</height>
</size>
</property>
<property name="font">
@ -30,14 +30,14 @@
<rect>
<x>10</x>
<y>20</y>
<width>331</width>
<height>112</height>
<width>390</width>
<height>102</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>112</height>
<height>102</height>
</size>
</property>
<property name="windowTitle">
@ -74,7 +74,7 @@
<rect>
<x>30</x>
<y>50</y>
<width>180</width>
<width>251</width>
<height>16</height>
</rect>
</property>
@ -102,7 +102,7 @@
<rect>
<x>30</x>
<y>70</y>
<width>100</width>
<width>131</width>
<height>16</height>
</rect>
</property>
@ -131,7 +131,7 @@
<widget class="QLabel" name="SpreadText">
<property name="geometry">
<rect>
<x>140</x>
<x>170</x>
<y>70</y>
<width>30</width>
<height>16</height>
@ -153,7 +153,7 @@
<widget class="QLabel" name="BWText">
<property name="geometry">
<rect>
<x>240</x>
<x>300</x>
<y>50</y>
<width>80</width>
<height>16</height>
@ -175,7 +175,7 @@
<widget class="QLabel" name="deBitsLabel">
<property name="geometry">
<rect>
<x>190</x>
<x>220</x>
<y>70</y>
<width>22</width>
<height>16</height>
@ -188,7 +188,7 @@
<widget class="QLabel" name="deBitsText">
<property name="geometry">
<rect>
<x>290</x>
<x>350</x>
<y>70</y>
<width>30</width>
<height>16</height>
@ -210,14 +210,14 @@
<widget class="QSlider" name="deBits">
<property name="geometry">
<rect>
<x>220</x>
<x>250</x>
<y>70</y>
<width>60</width>
<width>91</width>
<height>16</height>
</rect>
</property>
<property name="toolTip">
<string>Low data rate optimize (DE) bits</string>
<string>Distance Enhancement bits i.e. log2 of number of FFT bins per effective sample</string>
</property>
<property name="minimum">
<number>0</number>
@ -243,7 +243,7 @@
<rect>
<x>10</x>
<y>10</y>
<width>311</width>
<width>371</width>
<height>26</height>
</rect>
</property>
@ -319,18 +319,378 @@
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="sLabel">
<property name="text">
<string>S</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>De-chirped signal level</string>
</property>
<property name="text">
<string>-50.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="snrLabel">
<property name="minimumSize">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>SNR</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="snrText">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>De-chirped SNR level</string>
</property>
<property name="text">
<string>10.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="payloadContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>130</y>
<width>390</width>
<height>160</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>160</height>
</size>
</property>
<property name="windowTitle">
<string>Payload</string>
</property>
<widget class="QLabel" name="messageLabel">
<property name="geometry">
<rect>
<x>2</x>
<y>40</y>
<width>32</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Msg</string>
</property>
</widget>
<widget class="QLineEdit" name="hexText">
<property name="geometry">
<rect>
<x>30</x>
<y>120</y>
<width>351</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="hexLabel">
<property name="geometry">
<rect>
<x>2</x>
<y>120</y>
<width>32</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Hex</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="messageText">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>351</width>
<height>75</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="schemeLabel">
<property name="geometry">
<rect>
<x>2</x>
<y>10</y>
<width>50</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Scheme</string>
</property>
</widget>
<widget class="QComboBox" name="scheme">
<property name="geometry">
<rect>
<x>60</x>
<y>8</y>
<width>86</width>
<height>20</height>
</rect>
</property>
<item>
<property name="text">
<string>LoRa</string>
</property>
</item>
<item>
<property name="text">
<string>ASCII</string>
</property>
</item>
<item>
<property name="text">
<string>TTY</string>
</property>
</item>
</widget>
<widget class="QPushButton" name="clear">
<property name="geometry">
<rect>
<x>4</x>
<y>60</y>
<width>20</width>
<height>20</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Clear text</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sweep.png</normaloff>:/sweep.png</iconset>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
<widget class="QToolButton" name="mute">
<property name="geometry">
<rect>
<x>150</x>
<y>8</y>
<width>20</width>
<height>20</height>
</rect>
</property>
<property name="toolTip">
<string>Run/Stop decoder</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/stop.png</normaloff>
<normalon>:/play.png</normalon>:/stop.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="eomSquelchLabel">
<property name="geometry">
<rect>
<x>180</x>
<y>10</y>
<width>35</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>EOM</string>
</property>
</widget>
<widget class="QDial" name="eomSquelch">
<property name="geometry">
<rect>
<x>210</x>
<y>8</y>
<width>22</width>
<height>22</height>
</rect>
</property>
<property name="toolTip">
<string>End Of Message squelch factor</string>
</property>
<property name="minimum">
<number>40</number>
</property>
<property name="maximum">
<number>121</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>60</number>
</property>
</widget>
<widget class="QLabel" name="eomSquelchText">
<property name="geometry">
<rect>
<x>230</x>
<y>10</y>
<width>28</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>10.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="messageLengthLabel">
<property name="geometry">
<rect>
<x>272</x>
<y>10</y>
<width>20</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>ML</string>
</property>
</widget>
<widget class="QDial" name="messageLength">
<property name="geometry">
<rect>
<x>290</x>
<y>8</y>
<width>22</width>
<height>22</height>
</rect>
</property>
<property name="toolTip">
<string>Message (payload) length in number of symbols</string>
</property>
<property name="minimum">
<number>20</number>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>255</number>
</property>
</widget>
<widget class="QLabel" name="messageLengthText">
<property name="geometry">
<rect>
<x>310</x>
<y>10</y>
<width>25</width>
<height>19</height>
</rect>
</property>
<property name="text">
<string>255</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLineEdit" name="syncWord">
<property name="geometry">
<rect>
<x>350</x>
<y>9</y>
<width>25</width>
<height>20</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Sync word (1 byte hex)</string>
</property>
<property name="inputMask">
<string>HH</string>
</property>
<property name="text">
<string>00</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="spectrumContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>140</y>
<width>331</width>
<height>341</height>
<y>300</y>
<width>390</width>
<height>310</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>373</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>De-chirped Spectrum</string>
</property>

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "lorademodmsg.h"
MESSAGE_CLASS_DEFINITION(LoRaDemodMsg::MsgDecodeSymbols, Message)

View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_LORADEMODMSG_H
#define INCLUDE_LORADEMODMSG_H
#include <QObject>
#include "util/message.h"
namespace LoRaDemodMsg
{
class MsgDecodeSymbols : public Message {
MESSAGE_CLASS_DECLARATION
public:
const std::vector<unsigned int>& 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<unsigned int> symbols) {
return new MsgDecodeSymbols(symbols);
}
private:
std::vector<unsigned int> 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<unsigned int> 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

View File

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

View File

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

View File

@ -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<<m_settings.m_spreadFactor)));
m_decodeMsg->setNoiseDb(CalcDb::dbPower(m_magsqOffAvg.asDouble() / (1<<m_settings.m_spreadFactor)));
if (m_decoderMsgQueue && m_settings.m_decodeActive) {
m_decoderMsgQueue->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<<m_settings.m_spreadFactor)));
m_decodeMsg->setNoiseDb(CalcDb::dbPower(m_magsqOffAvg.asDouble() / (1<<m_settings.m_spreadFactor)));
if (m_decoderMsgQueue && m_settings.m_decodeActive) {
m_decoderMsgQueue->push(m_decodeMsg);
} else {
delete m_decodeMsg;
}
}
}
}

View File

@ -19,17 +19,23 @@
#define INCLUDE_LORADEMODSINK_H
#include <vector>
#include <queue>
#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<double, double, 10> m_magsqOnAvg;
MovingAverageUtil<double, double, 10> m_magsqOffAvg;
std::queue<double> 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

View File

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

View File

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