From be2ca2d0fd08baf5771138cefc74dc25c76c5f50 Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 19 Feb 2020 09:26:42 +0100 Subject: [PATCH] LoRa demod: implement LoRa --- plugins/channelrx/demodlora/CMakeLists.txt | 6 + plugins/channelrx/demodlora/lorademod.cpp | 36 +- plugins/channelrx/demodlora/lorademod.h | 44 +- .../channelrx/demodlora/lorademoddecoder.cpp | 119 ++--- .../channelrx/demodlora/lorademoddecoder.h | 44 +- .../demodlora/lorademoddecoderascii.cpp | 30 ++ .../demodlora/lorademoddecoderascii.h | 30 ++ .../demodlora/lorademoddecoderlora.cpp | 330 ++++++++++++++ .../demodlora/lorademoddecoderlora.h | 327 ++++++++++++++ .../demodlora/lorademoddecodertty.cpp | 67 +++ .../channelrx/demodlora/lorademoddecodertty.h | 42 ++ plugins/channelrx/demodlora/lorademodgui.cpp | 188 +++++++- plugins/channelrx/demodlora/lorademodgui.h | 9 + plugins/channelrx/demodlora/lorademodgui.ui | 426 +++++++++++++++--- plugins/channelrx/demodlora/lorademodmsg.h | 10 +- .../channelrx/demodlora/lorademodsettings.cpp | 18 + .../channelrx/demodlora/lorademodsettings.h | 6 + plugins/channelrx/demodlora/lorademodsink.cpp | 8 +- plugins/channelrx/demodlora/lorademodsink.h | 81 ---- .../channeltx/modlora/loramodencoderlora.cpp | 10 +- .../channeltx/modlora/loramodencoderlora.h | 4 + 21 files changed, 1592 insertions(+), 243 deletions(-) create mode 100644 plugins/channelrx/demodlora/lorademoddecoderascii.cpp create mode 100644 plugins/channelrx/demodlora/lorademoddecoderascii.h create mode 100644 plugins/channelrx/demodlora/lorademoddecoderlora.cpp create mode 100644 plugins/channelrx/demodlora/lorademoddecoderlora.h create mode 100644 plugins/channelrx/demodlora/lorademoddecodertty.cpp create mode 100644 plugins/channelrx/demodlora/lorademoddecodertty.h diff --git a/plugins/channelrx/demodlora/CMakeLists.txt b/plugins/channelrx/demodlora/CMakeLists.txt index 82b6d51de..996cf5a5e 100644 --- a/plugins/channelrx/demodlora/CMakeLists.txt +++ b/plugins/channelrx/demodlora/CMakeLists.txt @@ -8,6 +8,9 @@ set(lora_SOURCES lorademodbaseband.cpp loraplugin.cpp lorademoddecoder.cpp + lorademoddecodertty.cpp + lorademoddecoderascii.cpp + lorademoddecoderlora.cpp lorademodmsg.cpp lorademodgui.ui ) @@ -19,6 +22,9 @@ set(lora_HEADERS lorademodsink.h lorademodbaseband.h lorademoddecoder.h + lorademoddecodertty.h + lorademoddecoderascii.h + lorademoddecoderlora.h lorademodmsg.h loraplugin.h ) diff --git a/plugins/channelrx/demodlora/lorademod.cpp b/plugins/channelrx/demodlora/lorademod.cpp index af78ccd4e..3211bca2e 100644 --- a/plugins/channelrx/demodlora/lorademod.cpp +++ b/plugins/channelrx/demodlora/lorademod.cpp @@ -116,6 +116,13 @@ bool LoRaDemod::handleMessage(const Message& cmd) msgToGUI->setSyncWord(msg.getSyncWord()); msgToGUI->setSignalDb(msg.getSingalDb()); msgToGUI->setNoiseDb(msg.getNoiseDb()); + msgToGUI->setPacketSize(m_decoder.getPacketLength()); + msgToGUI->setNbParityBits(m_decoder.getNbParityBits()); + msgToGUI->setHasCRC(m_decoder.getHasCRC()); + msgToGUI->setHeaderParityStatus(m_decoder.getHeaderParityStatus()); + msgToGUI->setHeaderCRCStatus(m_decoder.getHeaderCRCStatus()); + msgToGUI->setPayloadParityStatus(m_decoder.getPayloadParityStatus()); + msgToGUI->setPayloadCRCStatus(m_decoder.getPayloadCRCStatus()); getMessageQueueToGUI()->push(msgToGUI); } } @@ -187,19 +194,46 @@ void LoRaDemod::applySettings(const LoRaDemodSettings& settings, bool force) << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << " m_bandwidthIndex: " << settings.m_bandwidthIndex << " m_spreadFactor: " << settings.m_spreadFactor + << " m_deBits: " << settings.m_deBits + << " m_codingScheme: " << settings.m_codingScheme + << " m_hasHeader: " << settings.m_hasHeader + << " m_hasCRC: " << settings.m_hasCRC + << " m_nbParityBits: " << settings.m_nbParityBits + << " m_packetLength: " << settings.m_packetLength + << " m_errorCheck: " << settings.m_errorCheck << " m_rgbColor: " << settings.m_rgbColor << " 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); + m_decoder.setNbSymbolBits(settings.m_spreadFactor, settings.m_deBits); } if ((settings.m_codingScheme != m_settings.m_codingScheme) || force) { m_decoder.setCodingScheme(settings.m_codingScheme); } + if ((settings.m_hasHeader != m_settings.m_hasHeader) || force) { + m_decoder.setLoRaHasHeader(settings.m_hasHeader); + } + + if ((settings.m_hasCRC != m_settings.m_hasCRC) || force) { + m_decoder.setLoRaHasCRC(settings.m_hasCRC); + } + + if ((settings.m_nbParityBits != m_settings.m_nbParityBits) || force) { + m_decoder.setLoRaParityBits(settings.m_nbParityBits); + } + + if ((settings.m_packetLength != m_settings.m_packetLength) || force) { + m_decoder.setLoRaPacketLength(settings.m_packetLength); + } + + if ((settings.m_errorCheck != m_settings.m_errorCheck) || force) { + m_decoder.setErrorCheck(settings.m_errorCheck); + } + LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband *msg = LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband::create(settings, force); m_basebandSink->getInputMessageQueue()->push(msg); diff --git a/plugins/channelrx/demodlora/lorademod.h b/plugins/channelrx/demodlora/lorademod.h index 17bda16f7..a31cb2c38 100644 --- a/plugins/channelrx/demodlora/lorademod.h +++ b/plugins/channelrx/demodlora/lorademod.h @@ -65,6 +65,13 @@ public: unsigned int getSyncWord() const { return m_syncWord; } float getSingalDb() const { return m_signalDb; } float getNoiseDb() const { return m_noiseDb; } + unsigned int getPacketSize() const { return m_packetSize; } + unsigned int getNbParityBits() const { return m_nbParityBits; } + bool getHasCRC() const { return m_hasCRC; } + bool getHeaderParityStatus() const { return m_headerParityStatus; } + bool getHeaderCRCStatus() const { return m_headerCRCStatus; } + bool getPayloadParityStatus() const { return m_payloadParityStatus; } + bool getPayloadCRCStatus() const { return m_payloadCRCStatus; } static MsgReportDecodeBytes* create(const QByteArray& bytes) { return new MsgReportDecodeBytes(bytes); @@ -78,19 +85,54 @@ public: void setNoiseDb(float db) { m_noiseDb = db; } + void setPacketSize(unsigned int packetSize) { + m_packetSize = packetSize; + } + void setNbParityBits(unsigned int nbParityBits) { + m_nbParityBits = nbParityBits; + } + void setHasCRC(bool hasCRC) { + m_hasCRC = hasCRC; + } + void setHeaderParityStatus(bool headerParityStatus) { + m_headerParityStatus = headerParityStatus; + } + void setHeaderCRCStatus(bool headerCRCStatus) { + m_headerCRCStatus = headerCRCStatus; + } + void setPayloadParityStatus(bool payloadParityStatus) { + m_payloadParityStatus = payloadParityStatus; + } + void setPayloadCRCStatus(bool payloadCRCStatus) { + m_payloadCRCStatus = payloadCRCStatus; + } private: QByteArray m_bytes; unsigned int m_syncWord; float m_signalDb; float m_noiseDb; + unsigned int m_packetSize; + unsigned int m_nbParityBits; + bool m_hasCRC; + bool m_headerParityStatus; + bool m_headerCRCStatus; + bool m_payloadParityStatus; + bool m_payloadCRCStatus; MsgReportDecodeBytes(const QByteArray& bytes) : Message(), m_bytes(bytes), m_syncWord(0), m_signalDb(0.0), - m_noiseDb(0.0) + m_noiseDb(0.0), + m_packetSize(0), + m_nbParityBits(0), + m_hasCRC(false), + m_headerParityStatus(false), + m_headerCRCStatus(false), + m_payloadParityStatus(false), + m_payloadCRCStatus(false) { } }; diff --git a/plugins/channelrx/demodlora/lorademoddecoder.cpp b/plugins/channelrx/demodlora/lorademoddecoder.cpp index 549c9d6d0..b6e0bdd14 100644 --- a/plugins/channelrx/demodlora/lorademoddecoder.cpp +++ b/plugins/channelrx/demodlora/lorademoddecoder.cpp @@ -16,98 +16,75 @@ /////////////////////////////////////////////////////////////////////////////////// #include "lorademoddecoder.h" - -const char LoRaDemodDecoder::ttyLetters[32] = { - '\0', 'E', '\n', 'A', ' ', 'S', 'I', 'U', - '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', - 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', - 'O', 'B', 'G', ' ', 'M', 'X', 'V', ' ' -}; - -const char LoRaDemodDecoder::ttyFigures[32] = { // U.S. standard - '\0', '3', '\n', '-', ' ', '\a', '8', '7', - '\r', '$', '4', '\'', ',', '!', ':', '(', - '5', '"', ')', '2', '#', '6', '0', '1', - '9', '?', '&', ' ', '.', '/', ';', ' ' -}; - +#include "lorademoddecodertty.h" +#include "lorademoddecoderascii.h" +#include "lorademoddecoderlora.h" LoRaDemodDecoder::LoRaDemodDecoder() : m_codingScheme(LoRaDemodSettings::CodingTTY), - m_nbSymbolBits(5) + m_nbSymbolBits(5), + m_nbParityBits(1), + m_hasCRC(true), + m_hasHeader(true) {} LoRaDemodDecoder::~LoRaDemodDecoder() {} -void LoRaDemodDecoder::decodeSymbols(const std::vector& symbols, QString& str) +void LoRaDemodDecoder::setNbSymbolBits(unsigned int spreadFactor, unsigned int deBits) +{ + m_spreadFactor = spreadFactor; + + if (deBits >= spreadFactor) { + m_deBits = m_spreadFactor - 1; + } else { + m_deBits = deBits; + } + + m_nbSymbolBits = m_spreadFactor - m_deBits; +} + +void LoRaDemodDecoder::decodeSymbols(const std::vector& symbols, QString& str) { switch(m_codingScheme) { case LoRaDemodSettings::CodingTTY: - decodeSymbolsTTY(symbols, str); + if (m_nbSymbolBits == 5) { + LoRaDemodDecoderTTY::decodeSymbols(symbols, str); + } break; case LoRaDemodSettings::CodingASCII: - decodeSymbolsASCII(symbols, str); + if (m_nbSymbolBits == 5) { + LoRaDemodDecoderASCII::decodeSymbols(symbols, str); + } + break; + default: break; } } -void LoRaDemodDecoder::decodeSymbols(const std::vector& symbols, QByteArray& bytes) +void LoRaDemodDecoder::decodeSymbols(const std::vector& symbols, QByteArray& bytes) { - -} - -void LoRaDemodDecoder::decodeSymbolsASCII(const std::vector& symbols, QString& str) -{ - if (m_nbSymbolBits != 7) { - return; - } - - std::vector::const_iterator it = symbols.begin(); - QByteArray bytes; - - for (; it != symbols.end(); ++it) { - bytes.push_back(*it & 0x7F); - } - - str = QString(bytes.toStdString().c_str()); -} - -void LoRaDemodDecoder::decodeSymbolsTTY(const std::vector& symbols, QString& str) -{ - if (m_nbSymbolBits != 5) { - return; - } - - std::vector::const_iterator it = symbols.begin(); - QByteArray bytes; - TTYState ttyState = TTYLetters; - - for (; it != symbols.end(); ++it) + switch(m_codingScheme) { - char ttyChar = *it & 0x1F; - - if (ttyChar == lettersTag) { - ttyState = TTYLetters; - } else if (ttyChar == figuresTag) { - ttyState = TTYFigures; - } - else + case LoRaDemodSettings::CodingLoRa: + if (m_nbSymbolBits >= 5) { - char asciiChar = -1; - - if (ttyState == TTYLetters) { - asciiChar = ttyLetters[ttyChar]; - } else if (ttyState == TTYFigures) { - asciiChar = ttyFigures[ttyChar]; - } - - if (asciiChar >= 0) { - bytes.push_back(asciiChar); - } + LoRaDemodDecoderLoRa::decodeBytes( + bytes, + symbols, + m_nbSymbolBits, + m_hasHeader, + m_hasCRC, + m_nbParityBits, + m_packetLength, + m_errorCheck, + m_headerParityStatus, + m_headerCRCStatus, + m_payloadParityStatus, + m_payloadCRCStatus + ); } + break; } - - str = QString(bytes.toStdString().c_str()); } diff --git a/plugins/channelrx/demodlora/lorademoddecoder.h b/plugins/channelrx/demodlora/lorademoddecoder.h index 4a86c6c07..4fac56de6 100644 --- a/plugins/channelrx/demodlora/lorademoddecoder.h +++ b/plugins/channelrx/demodlora/lorademoddecoder.h @@ -28,27 +28,37 @@ public: ~LoRaDemodDecoder(); void setCodingScheme(LoRaDemodSettings::CodingScheme codingScheme) { m_codingScheme = codingScheme; } - void setNbSymbolBits(unsigned int symbolBits) { m_nbSymbolBits = symbolBits; } - void decodeSymbols(const std::vector& symbols, QString& str); //!< For ASCII and TTY - void decodeSymbols(const std::vector& symbols, QByteArray& bytes); //!< For raw bytes (original LoRa) + void setNbSymbolBits(unsigned int spreadFactor, unsigned int deBits); + void setLoRaParityBits(unsigned int parityBits) { m_nbParityBits = parityBits; } + void setLoRaHasHeader(bool hasHeader) { m_hasHeader = hasHeader; } + void setLoRaHasCRC(bool hasCRC) { m_hasCRC = hasCRC; } + void setLoRaPacketLength(unsigned int packetLength) { m_packetLength = packetLength; } + void setErrorCheck(bool errorCheck) { m_errorCheck = errorCheck; } + void decodeSymbols(const std::vector& symbols, QString& str); //!< For ASCII and TTY + void decodeSymbols(const std::vector& symbols, QByteArray& bytes); //!< For raw bytes (original LoRa) + unsigned int getNbParityBits() const { return m_nbParityBits; } + unsigned int getPacketLength() const { return m_packetLength; } + bool getHasCRC() const { return m_hasCRC; } + bool getHeaderParityStatus() const { return m_headerParityStatus; } + bool getHeaderCRCStatus() const { return m_headerCRCStatus; } + bool getPayloadParityStatus() const { return m_payloadParityStatus; } + bool getPayloadCRCStatus() const { return m_payloadCRCStatus; } private: - enum TTYState - { - TTYLetters, - TTYFigures - }; - - void decodeSymbolsASCII(const std::vector& symbols, QString& str); - void decodeSymbolsTTY(const std::vector& symbols, QString& str); - LoRaDemodSettings::CodingScheme m_codingScheme; + unsigned int m_spreadFactor; + unsigned int m_deBits; unsigned int m_nbSymbolBits; - - static const char ttyLetters[32]; - static const char ttyFigures[32]; - static const char lettersTag = 0x1f; - static const char figuresTag = 0x1b; + // LoRa attributes + unsigned int m_nbParityBits; //!< 1 to 4 Hamming FEC bits for 4 payload bits + bool m_hasCRC; + bool m_hasHeader; + unsigned int m_packetLength; + bool m_errorCheck; + bool m_headerParityStatus; + bool m_headerCRCStatus; + bool m_payloadParityStatus; + bool m_payloadCRCStatus; }; #endif // INCLUDE_LORADEMODDECODER_H diff --git a/plugins/channelrx/demodlora/lorademoddecoderascii.cpp b/plugins/channelrx/demodlora/lorademoddecoderascii.cpp new file mode 100644 index 000000000..f70043fbe --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecoderascii.cpp @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "lorademoddecoderascii.h" + +void LoRaDemodDecoderASCII::decodeSymbols(const std::vector& symbols, QString& str) +{ + std::vector::const_iterator it = symbols.begin(); + QByteArray bytes; + + for (; it != symbols.end(); ++it) { + bytes.push_back(*it & 0x7F); + } + + str = QString(bytes.toStdString().c_str()); +} diff --git a/plugins/channelrx/demodlora/lorademoddecoderascii.h b/plugins/channelrx/demodlora/lorademoddecoderascii.h new file mode 100644 index 000000000..a014dd94f --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecoderascii.h @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_LORADEMODDECODERASCII_H +#define INCLUDE_LORADEMODDECODERASCII_H + +#include +#include + +class LoRaDemodDecoderASCII +{ +public: + static void decodeSymbols(const std::vector& symbols, QString& str); +}; + +#endif // INCLUDE_LORADEMODDECODERASCII_H diff --git a/plugins/channelrx/demodlora/lorademoddecoderlora.cpp b/plugins/channelrx/demodlora/lorademoddecoderlora.cpp new file mode 100644 index 000000000..3659d2626 --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecoderlora.cpp @@ -0,0 +1,330 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Inspired by: https://github.com/myriadrf/LoRa-SDR // +// // +// 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 "lorademoddecoderlora.h" + +void LoRaDemodDecoderLoRa::decodeBytes( + QByteArray& inBytes, + const std::vector& inSymbols, + unsigned int nbSymbolBits, + bool hasHeader, + bool& hasCRC, + unsigned int& nbParityBits, + unsigned int& packetLength, + bool errorCheck, + bool& headerParityStatus, + bool& headerCRCStatus, + bool& payloadParityStatus, + bool& payloadCRCStatus +) +{ + if (inSymbols.size() < 8) { // need at least a header + return; + } + + const unsigned int numSymbols = roundUp(inSymbols.size(), 4 + nbParityBits); + const unsigned int numCodewords = (numSymbols / (4 + nbParityBits))*nbSymbolBits; + std::vector symbols(numSymbols); + std::copy(inSymbols.begin(), inSymbols.end(), symbols.begin()); + + //gray encode, when SF > PPM, depad the LSBs with rounding + for (auto &sym : symbols) { + sym = binaryToGray16(sym); + } + + std::vector codewords(numCodewords); + + // deinterleave / dewhiten the symbols into codewords + unsigned int sOfs = 0; + unsigned int cOfs = 0; + + if (nbParityBits != 4) + { + diagonalDeinterleaveSx(symbols.data(), 8, codewords.data(), nbSymbolBits, 4); + + if (hasHeader) { + Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, nbSymbolBits - headerCodewords, 0, headerParityBits); + } else { + Sx1272ComputeWhiteningLfsr(codewords.data(), nbSymbolBits, 0, headerParityBits); + } + + cOfs += nbSymbolBits; + sOfs += headerSymbols; + + if (numSymbols - sOfs > 0) + { + diagonalDeinterleaveSx(symbols.data() + sOfs, numSymbols - sOfs, codewords.data() + cOfs, nbSymbolBits, nbParityBits); + + if (hasHeader) { + Sx1272ComputeWhiteningLfsr(codewords.data() + cOfs, numCodewords - cOfs, nbSymbolBits - headerCodewords, nbParityBits); + } else { + Sx1272ComputeWhiteningLfsr(codewords.data() + cOfs, numCodewords - cOfs, nbSymbolBits, nbParityBits); + } + } + } + else + { + diagonalDeinterleaveSx(symbols.data(), numSymbols, codewords.data(), nbSymbolBits, nbParityBits); + + if (hasHeader) { + Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, numCodewords - headerCodewords, 0, nbParityBits); + } else { + Sx1272ComputeWhiteningLfsr(codewords.data(), numCodewords, 0, nbParityBits); + } + } + + bool error = false; + bool bad = false; + std::vector bytes((codewords.size()+1) / 2); + unsigned int dOfs = 0; + cOfs = 0; + + unsigned int dataLength = 0; + bool hasCRCInt; // payload has CRC indicator internal (explicit or implicit) + unsigned int nbParityBitsInt; // number of parity bits internal (explicit or implicit) + unsigned int packetLengthInt; // packet length internal (explicit or implicit) + + if (hasHeader) + { + bytes[0] = decodeHamming84sx(codewords[1], error, bad) & 0xf; + bytes[0] |= decodeHamming84sx(codewords[0], error, bad) << 4; // length + + bytes[1] = decodeHamming84sx(codewords[2], error, bad) & 0xf; // coding rate and crc enable + + bytes[2] = decodeHamming84sx(codewords[4], error, bad) & 0xf; + bytes[2] |= decodeHamming84sx(codewords[3], error, bad) << 4; // checksum + + bytes[2] ^= headerChecksum(bytes.data()); + + if (error) + { + qDebug("LoRaDemodDecoderLoRa::decodeBytes: header Hamming error"); + headerParityStatus = false; + + if (errorCheck) { + return; + } + } + else + { + headerParityStatus = true; + + if (bytes[2] != 0) + { + headerCRCStatus = false; + qDebug("LoRaDemodDecoderLoRa::decodeBytes: header CRC error"); + + if (errorCheck) { + return; + } + } + else + { + headerCRCStatus = true; + } + } + + hasCRCInt = (bytes[1] & 1) != 0; + nbParityBitsInt = (bytes[1] >> 1) & 0x7; + + if (nbParityBitsInt > 4) { + return; + } + + packetLengthInt = bytes[0]; + //dataLength = packetLengthInt + 3 + (hasCRCInt ? 2 : 0); // include header and crc + dataLength = packetLengthInt + 5; + + cOfs = headerCodewords; + dOfs = 6; + } + else + { + hasCRCInt = hasCRC; + nbParityBitsInt = nbParityBits; + packetLengthInt = packetLength; + cOfs = 0; + dOfs = 0; + + if (hasCRCInt) { + dataLength = packetLengthInt + 2; + } else { + dataLength = packetLengthInt; + } + } + + if (dataLength > bytes.size()) { + return; + } + + for (; cOfs < nbSymbolBits; cOfs++, dOfs++) + { + if (dOfs % 2 == 1) { + bytes[dOfs/2] |= decodeHamming84sx(codewords[cOfs], error, bad) << 4; + } else { + bytes[dOfs/2] = decodeHamming84sx(codewords[cOfs], error, bad) & 0xf; + } + } + + if (dOfs % 2 == 1) + { + if (nbParityBitsInt == 1) { + bytes[dOfs/2] |= checkParity54(codewords[cOfs++], error) << 4; + } else if (nbParityBitsInt == 2) { + bytes[dOfs/2] |= checkParity64(codewords[cOfs++], error) << 4; + } else if (nbParityBitsInt == 3){ + bytes[dOfs/2] |= decodeHamming74sx(codewords[cOfs++], error) << 4; + } else if (nbParityBitsInt == 4){ + bytes[dOfs/2] |= decodeHamming84sx(codewords[cOfs++], error, bad) << 4; + } else { + bytes[dOfs/2] |= codewords[cOfs++] << 4; + } + + dOfs++; + } + + dOfs /= 2; + + if (error) + { + qDebug("LoRaDemodDecoderLoRa::decodeBytes: Hamming decode (1) error"); + payloadParityStatus = false; + + if (errorCheck) { + return; + } + } + else + { + payloadParityStatus = true; + } + + //decode each codeword as 2 bytes with correction + + if (nbParityBitsInt == 1) + { + for (unsigned int i = dOfs; i < dataLength; i++) + { + bytes[i] = checkParity54(codewords[cOfs++],error); + bytes[i] |= checkParity54(codewords[cOfs++], error) << 4; + } + } + else if (nbParityBitsInt == 2) + { + for (unsigned int i = dOfs; i < dataLength; i++) + { + bytes[i] = checkParity64(codewords[cOfs++], error); + bytes[i] |= checkParity64(codewords[cOfs++],error) << 4; + } + } + else if (nbParityBitsInt == 3) + { + for (unsigned int i = dOfs; i < dataLength; i++) + { + bytes[i] = decodeHamming74sx(codewords[cOfs++], error) & 0xf; + bytes[i] |= decodeHamming74sx(codewords[cOfs++], error) << 4; + } + } + else if (nbParityBitsInt == 4) + { + for (unsigned int i = dOfs; i < dataLength; i++) + { + bytes[i] = decodeHamming84sx(codewords[cOfs++], error, bad) & 0xf; + bytes[i] |= decodeHamming84sx(codewords[cOfs++], error, bad) << 4; + } + } + else + { + for (unsigned int i = dOfs; i < dataLength; i++) + { + bytes[i] = codewords[cOfs++] & 0xf; + bytes[i] |= codewords[cOfs++] << 4; + } + } + + if (error) + { + qDebug("LoRaDemodDecoderLoRa::decodeBytes: Hamming decode (2) error"); + payloadParityStatus = false; + + if (errorCheck) { + return; + } + } + + if (hasHeader) + { + dOfs = 3; + dataLength -= 5; + + if (hasCRCInt) // always compute crc if present + { + uint16_t crc = sx1272DataChecksum(bytes.data() + dOfs, packetLengthInt - 2); + uint16_t packetCRC = bytes[dOfs + packetLengthInt - 2] | (bytes[dOfs + packetLengthInt - 1] << 8); + + if (crc != packetCRC) + { + qDebug("LoRaDemodDecoderLoRa::decodeBytes: packet CRC error: calc: %x found: %x", crc, packetCRC); + payloadCRCStatus = false; + + if (errorCheck) { + return; + } + } + else + { + payloadCRCStatus = true; + } + + } + + hasCRC = hasCRCInt; + nbParityBits = nbParityBitsInt; + packetLength = packetLengthInt; + } + else + { + dOfs = 0; + + if (hasCRCInt) + { + uint16_t crc = sx1272DataChecksum(bytes.data(), packetLengthInt - 2); + uint16_t packetCRC = bytes[packetLengthInt - 2] | (bytes[packetLengthInt - 1] << 8); + + if (crc != packetCRC) + { + qDebug("LoRaDemodDecoderLoRa::decodeBytes: packet CRC error: calc: %x found: %x", crc, packetCRC); + payloadCRCStatus = false; + + if (errorCheck) { + return; + } + } + else + { + payloadCRCStatus = true; + } + + } + } + + qDebug("LoRaDemodDecoderLoRa::decodeBytes: dataLength: %u packetLengthInt: %u", dataLength, packetLengthInt); + inBytes.resize(dataLength); + std::copy(bytes.data() + dOfs, bytes.data() + dOfs + dataLength, inBytes.data()); +} diff --git a/plugins/channelrx/demodlora/lorademoddecoderlora.h b/plugins/channelrx/demodlora/lorademoddecoderlora.h new file mode 100644 index 000000000..bf7121a0f --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecoderlora.h @@ -0,0 +1,327 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// Inspired by: https://github.com/myriadrf/LoRa-SDR // +// // +// 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_LORADEMODDECODERLORA_H +#define INCLUDE_LORADEMODDECODERLORA_H + +#include +#include + +class LoRaDemodDecoderLoRa +{ +public: + static void decodeBytes( + QByteArray& bytes, + const std::vector& inSymbols, + unsigned int nbSymbolBits, + bool hasHeader, + bool& hasCRC, + unsigned int& nbParityBits, + unsigned int& packetLength, + bool errorCheck, + bool& headerParityStatus, + bool& headerCRCStatus, + bool& payloadParityStatus, + bool& payloadCRCStatus + ); + +private: + static const unsigned int headerParityBits = 4; + static const unsigned int headerSymbols = 8; + static const unsigned int headerCodewords = 5; + + /*********************************************************************** + * Round functions + **********************************************************************/ + static inline unsigned roundUp(unsigned num, unsigned factor) + { + return ((num + factor - 1) / factor) * factor; + } + + /*********************************************************************** + * https://en.wikipedia.org/wiki/Gray_code + **********************************************************************/ + + /* + * This function converts an unsigned binary + * number to reflected binary Gray code. + * + * The operator >> is shift right. The operator ^ is exclusive or. + */ + static inline unsigned short binaryToGray16(unsigned short num) + { + return num ^ (num >> 1); + } + + /*********************************************************************** + * Diagonal deinterleaver + **********************************************************************/ + static inline void diagonalDeinterleaveSx( + const uint16_t *symbols, + const unsigned int numSymbols, + uint8_t *codewords, + const unsigned int nbSymbolBits, + const unsigned int nbParityBits) + { + for (unsigned int x = 0; x < numSymbols / (4 + nbParityBits); x++) + { + const unsigned int cwOff = x*nbSymbolBits; + const unsigned int symOff = x*(4 + nbParityBits); + + for (unsigned int k = 0; k < 4 + nbParityBits; k++) + { + for (unsigned int m = 0; m < nbSymbolBits; m++) + { + const unsigned int i = (m + k) % nbSymbolBits; + const auto bit = (symbols[symOff + k] >> m) & 0x1; + codewords[cwOff + i] |= (bit << k); + } + } + } + } + + /*********************************************************************** + * Whitening generator reverse engineered from Sx1272 data stream. + * Same as above but using the actual interleaved LFSRs. + **********************************************************************/ + static inline void Sx1272ComputeWhiteningLfsr(uint8_t *buffer, uint16_t bufferSize, const int bitOfs, const unsigned int nbParityBits) + { + static const uint64_t seed1[2] = {0x6572D100E85C2EFF,0xE85C2EFFFFFFFFFF}; // lfsr start values + static const uint64_t seed2[2] = {0x05121100F8ECFEEF,0xF8ECFEEFEFEFEFEF}; // lfsr start values for single parity mode (1 == nbParityBits) + const uint8_t m = 0xff >> (4 - nbParityBits); + uint64_t r[2] = {(1 == nbParityBits)?seed2[0]:seed1[0],(1 == nbParityBits)?seed2[1]:seed1[1]}; + int i,j; + + for (i = 0; i < bitOfs;i++) + { + r[i & 1] = (r[i & 1] >> 8) | (((r[i & 1] >> 32) ^ (r[i & 1] >> 24) ^ (r[i & 1] >> 16) ^ r[i & 1]) << 56); // poly: 0x1D + } + + for (j = 0; j < bufferSize; j++,i++) + { + buffer[j] ^= r[i & 1] & m; + r[i & 1] = (r[i & 1] >> 8) | (((r[i & 1] >> 32) ^ (r[i & 1] >> 24) ^ (r[i & 1] >> 16) ^ r[i & 1]) << 56); + } + } + + /*********************************************************************** + * Decode 8 bits into a 4 bit word with single bit correction. + * Non standard version used in sx1272. + * Set error to true when a parity error was detected + * Set bad to true when the result could not be corrected + **********************************************************************/ + static inline unsigned char decodeHamming84sx(const unsigned char b, bool &error, bool &bad) + { + auto b0 = (b >> 0) & 0x1; + auto b1 = (b >> 1) & 0x1; + auto b2 = (b >> 2) & 0x1; + auto b3 = (b >> 3) & 0x1; + auto b4 = (b >> 4) & 0x1; + auto b5 = (b >> 5) & 0x1; + auto b6 = (b >> 6) & 0x1; + auto b7 = (b >> 7) & 0x1; + + auto p0 = (b0 ^ b1 ^ b2 ^ b4); + auto p1 = (b1 ^ b2 ^ b3 ^ b5); + auto p2 = (b0 ^ b1 ^ b3 ^ b6); + auto p3 = (b0 ^ b2 ^ b3 ^ b7); + + auto parity = (p0 << 0) | (p1 << 1) | (p2 << 2) | (p3 << 3); + if (parity != 0) error = true; + switch (parity & 0xf) + { + case 0xD: return (b ^ 1) & 0xf; + case 0x7: return (b ^ 2) & 0xf; + case 0xB: return (b ^ 4) & 0xf; + case 0xE: return (b ^ 8) & 0xf; + case 0x0: + case 0x1: + case 0x2: + case 0x4: + case 0x8: return b & 0xf; + default: bad = true; return b & 0xf; + } + } + + /*********************************************************************** + * Simple 8-bit checksum routine + **********************************************************************/ + static inline uint8_t checksum8(const uint8_t *p, const size_t len) + { + uint8_t acc = 0; + + for (size_t i = 0; i < len; i++) + { + acc = (acc >> 1) + ((acc & 0x1) << 7); //rotate + acc += p[i]; //add + } + + return acc; + } + + static inline uint8_t headerChecksum(const uint8_t *h) + { + auto a0 = (h[0] >> 4) & 0x1; + auto a1 = (h[0] >> 5) & 0x1; + auto a2 = (h[0] >> 6) & 0x1; + auto a3 = (h[0] >> 7) & 0x1; + + auto b0 = (h[0] >> 0) & 0x1; + auto b1 = (h[0] >> 1) & 0x1; + auto b2 = (h[0] >> 2) & 0x1; + auto b3 = (h[0] >> 3) & 0x1; + + auto c0 = (h[1] >> 0) & 0x1; + auto c1 = (h[1] >> 1) & 0x1; + auto c2 = (h[1] >> 2) & 0x1; + auto c3 = (h[1] >> 3) & 0x1; + + uint8_t res; + res = (a0 ^ a1 ^ a2 ^ a3) << 4; + res |= (a3 ^ b1 ^ b2 ^ b3 ^ c0) << 3; + res |= (a2 ^ b0 ^ b3 ^ c1 ^ c3) << 2; + res |= (a1 ^ b0 ^ b2 ^ c0 ^ c1 ^ c2) << 1; + res |= a0 ^ b1 ^ c0 ^ c1 ^ c2 ^ c3; + + return res; + } + + /*********************************************************************** + * Check parity for 5/4 code. + * return true if parity is valid. + **********************************************************************/ + static inline unsigned char checkParity54(const unsigned char b, bool &error) + { + auto x = b ^ (b >> 2); + x = x ^ (x >> 1) ^ (b >> 4); + + if (x & 1) { + error = true; + } + + return b & 0xf; + } + + /*********************************************************************** + * Check parity for 6/4 code. + * return true if parity is valid. + **********************************************************************/ + static inline unsigned char checkParity64(const unsigned char b, bool &error) + { + auto x = b ^ (b >> 1) ^ (b >> 2); + auto y = x ^ b ^ (b >> 3); + x ^= b >> 4; + y ^= b >> 5; + + if ((x | y) & 1) { + error = true; + } + + return b & 0xf; + } + + /*********************************************************************** + * Decode 7 bits into a 4 bit word with single bit correction. + * Non standard version used in sx1272. + * Set error to true when a parity error was detected + **********************************************************************/ + static inline unsigned char decodeHamming74sx(const unsigned char b, bool &error) + { + auto b0 = (b >> 0) & 0x1; + auto b1 = (b >> 1) & 0x1; + auto b2 = (b >> 2) & 0x1; + auto b3 = (b >> 3) & 0x1; + auto b4 = (b >> 4) & 0x1; + auto b5 = (b >> 5) & 0x1; + auto b6 = (b >> 6) & 0x1; + + auto p0 = (b0 ^ b1 ^ b2 ^ b4); + auto p1 = (b1 ^ b2 ^ b3 ^ b5); + auto p2 = (b0 ^ b1 ^ b3 ^ b6); + + auto parity = (p0 << 0) | (p1 << 1) | (p2 << 2); + + if (parity != 0) { + error = true; + } + + switch (parity) + { + case 0x5: return (b ^ 1) & 0xf; + case 0x7: return (b ^ 2) & 0xf; + case 0x3: return (b ^ 4) & 0xf; + case 0x6: return (b ^ 8) & 0xf; + case 0x0: + case 0x1: + case 0x2: + case 0x4: return b & 0xF; + } + + return b & 0xf; + } + + /*********************************************************************** + * CRC reverse engineered from Sx1272 data stream. + * Modified CCITT crc with masking of the output with an 8bit lfsr + **********************************************************************/ + static inline uint16_t crc16sx(uint16_t crc, const uint16_t poly) + { + for (int i = 0; i < 8; i++) + { + if (crc & 0x8000) { + crc = (crc << 1) ^ poly; + } else { + crc <<= 1; + } + } + + return crc; + } + + static inline uint8_t xsum8(uint8_t t) + { + t ^= t >> 4; + t ^= t >> 2; + t ^= t >> 1; + + return (t & 1); + } + + static inline uint16_t sx1272DataChecksum(const uint8_t *data, int length) + { + uint16_t res = 0; + uint8_t v = 0xff; + uint16_t crc = 0; + + for (int i = 0; i < length; i++) + { + crc = crc16sx(res, 0x1021); + v = xsum8(v & 0xB8) | (v << 1); + res = crc ^ data[i]; + } + + res ^= v; + v = xsum8(v & 0xB8) | (v << 1); + res ^= v << 8; + + return res; + } +}; + +#endif // INCLUDE_LORADEMODDECODERLORA_H diff --git a/plugins/channelrx/demodlora/lorademoddecodertty.cpp b/plugins/channelrx/demodlora/lorademoddecodertty.cpp new file mode 100644 index 000000000..277a8ca95 --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecodertty.cpp @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 "lorademoddecodertty.h" + +const char LoRaDemodDecoderTTY::ttyLetters[32] = { + '\0', 'E', '\n', 'A', ' ', 'S', 'I', 'U', + '\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K', + 'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q', + 'O', 'B', 'G', ' ', 'M', 'X', 'V', ' ' +}; + +const char LoRaDemodDecoderTTY::ttyFigures[32] = { // U.S. standard + '\0', '3', '\n', '-', ' ', '\a', '8', '7', + '\r', '$', '4', '\'', ',', '!', ':', '(', + '5', '"', ')', '2', '#', '6', '0', '1', + '9', '?', '&', ' ', '.', '/', ';', ' ' +}; + +void LoRaDemodDecoderTTY::decodeSymbols(const std::vector& symbols, QString& str) +{ + std::vector::const_iterator it = symbols.begin(); + QByteArray bytes; + TTYState ttyState = TTYLetters; + + for (; it != symbols.end(); ++it) + { + char ttyChar = *it & 0x1F; + + if (ttyChar == lettersTag) { + ttyState = TTYLetters; + } else if (ttyChar == figuresTag) { + ttyState = TTYFigures; + } + else + { + char asciiChar = -1; + + if (ttyState == TTYLetters) { + asciiChar = ttyLetters[ttyChar]; + } else if (ttyState == TTYFigures) { + asciiChar = ttyFigures[ttyChar]; + } + + if (asciiChar >= 0) { + bytes.push_back(asciiChar); + } + } + } + + str = QString(bytes.toStdString().c_str()); +} + diff --git a/plugins/channelrx/demodlora/lorademoddecodertty.h b/plugins/channelrx/demodlora/lorademoddecodertty.h new file mode 100644 index 000000000..349491d3c --- /dev/null +++ b/plugins/channelrx/demodlora/lorademoddecodertty.h @@ -0,0 +1,42 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_LORADEMODDECODERTTY_H +#define INCLUDE_LORADEMODDECODERTTY_H + +#include +#include + +class LoRaDemodDecoderTTY +{ +public: + static void decodeSymbols(const std::vector& symbols, QString& str); + +private: + enum TTYState + { + TTYLetters, + TTYFigures + }; + + static const char ttyLetters[32]; + static const char ttyFigures[32]; + static const char lettersTag = 0x1f; + static const char figuresTag = 0x1b; +}; + +#endif // INCLUDE_LORADEMODDECODERTTY_H \ No newline at end of file diff --git a/plugins/channelrx/demodlora/lorademodgui.cpp b/plugins/channelrx/demodlora/lorademodgui.cpp index 23d016538..a39ccc11e 100644 --- a/plugins/channelrx/demodlora/lorademodgui.cpp +++ b/plugins/channelrx/demodlora/lorademodgui.cpp @@ -77,6 +77,8 @@ QByteArray LoRaDemodGUI::serialize() const bool LoRaDemodGUI::deserialize(const QByteArray& data) { + resetLoRaStatus(); + if (m_settings.deserialize(data)) { displaySettings(); @@ -110,10 +112,29 @@ bool LoRaDemodGUI::handleMessage(const Message& 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)); + + if (m_settings.m_hasHeader) + { + ui->fecParity->setValue(msg.getNbParityBits()); + ui->fecParityText->setText(tr("%1").arg(msg.getNbParityBits())); + ui->crc->setChecked(msg.getHasCRC()); + ui->packetLength->setValue(msg.getPacketSize()); + ui->packetLengthText->setText(tr("%1").arg(msg.getPacketSize())); + displayBytes(bytes, msg.getPacketSize(), msg.getHasCRC()); + } + else + { + displayBytes(bytes, m_settings.m_packetLength, m_settings.m_hasCRC); + } + + if (m_settings.m_codingScheme == LoRaDemodSettings::CodingLoRa) { + displayLoRaStatus(msg.getHeaderParityStatus(), msg.getHeaderCRCStatus(), msg.getPayloadParityStatus(), msg.getPayloadCRCStatus()); + } + + return true; } else if (LoRaDemod::MsgReportDecodeString::match(message)) { @@ -122,6 +143,8 @@ bool LoRaDemodGUI::handleMessage(const Message& message) 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)); + + return true; } else { @@ -195,9 +218,21 @@ void LoRaDemodGUI::on_deBits_valueChanged(int value) applySettings(); } +void LoRaDemodGUI::on_preambleChirps_valueChanged(int value) +{ + m_settings.m_preambleChirps = value; + ui->preambleChirpsText->setText(tr("%1").arg(m_settings.m_preambleChirps)); + applySettings(); +} + void LoRaDemodGUI::on_scheme_currentIndexChanged(int index) { m_settings.m_codingScheme = (LoRaDemodSettings::CodingScheme) index; + + if (m_settings.m_codingScheme != LoRaDemodSettings::CodingLoRa) { + resetLoRaStatus(); + } + applySettings(); } @@ -227,6 +262,53 @@ void LoRaDemodGUI::on_messageLength_valueChanged(int value) applySettings(); } +void LoRaDemodGUI::on_header_stateChanged(int state) +{ + m_settings.m_hasHeader = (state == Qt::Checked); + + if (!m_settings.m_hasHeader) // put back values from settings + { + ui->fecParity->blockSignals(true); + ui->crc->blockSignals(true); + ui->fecParity->setValue(m_settings.m_nbParityBits); + ui->fecParityText->setText(tr("%1").arg(m_settings.m_nbParityBits)); + ui->crc->setChecked(m_settings.m_hasCRC); + ui->fecParity->blockSignals(false); + ui->crc->blockSignals(false); + } + + ui->fecParity->setEnabled(state != Qt::Checked); + ui->crc->setEnabled(state != Qt::Checked); + + applySettings(); +} + +void LoRaDemodGUI::on_fecParity_valueChanged(int value) +{ + m_settings.m_nbParityBits = value; + ui->fecParityText->setText(tr("%1").arg(m_settings.m_nbParityBits)); + applySettings(); +} + +void LoRaDemodGUI::on_crc_stateChanged(int state) +{ + m_settings.m_hasCRC = (state == Qt::Checked); + applySettings(); +} + +void LoRaDemodGUI::on_errorCheck_stateChanged(int state) +{ + m_settings.m_errorCheck = (state == Qt::Checked); + applySettings(); +} + +void LoRaDemodGUI::on_packetLength_valueChanged(int value) +{ + m_settings.m_packetLength = value; + ui->packetLengthText->setText(tr("%1").arg(m_settings.m_packetLength)); + applySettings(); +} + void LoRaDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -284,6 +366,7 @@ LoRaDemodGUI::LoRaDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb setBandwidths(); displaySettings(); + resetLoRaStatus(); applySettings(true); } @@ -325,6 +408,10 @@ void LoRaDemodGUI::displaySettings() ui->glSpectrum->setSampleRate(thisBW); ui->glSpectrum->setCenterFrequency(thisBW/2); + ui->fecParity->setEnabled(!m_settings.m_hasHeader); + ui->crc->setEnabled(!m_settings.m_hasHeader); + ui->packetLength->setEnabled(!m_settings.m_hasHeader); + blockApplySettings(true); ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); ui->BWText->setText(QString("%1 Hz").arg(thisBW)); @@ -333,11 +420,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->preambleChirps->setValue(m_settings.m_preambleChirps); + ui->preambleChirpsText->setText(tr("%1").arg(m_settings.m_preambleChirps)); 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); + ui->header->setChecked(m_settings.m_hasHeader); + ui->errorCheck->setChecked(m_settings.m_errorCheck); + + if (!m_settings.m_hasHeader) + { + ui->fecParity->setValue(m_settings.m_nbParityBits); + ui->fecParityText->setText(tr("%1").arg(m_settings.m_nbParityBits)); + ui->crc->setChecked(m_settings.m_hasCRC); + ui->packetLength->setValue(m_settings.m_packetLength); + ui->spectrumGUI->setFFTSize(m_settings.m_spreadFactor); + } + displaySquelch(); + blockApplySettings(false); } @@ -352,6 +453,45 @@ void LoRaDemodGUI::displaySquelch() } } +void LoRaDemodGUI::displayLoRaStatus(bool headerParityStatus, bool headerCRCStatus, bool payloadParityStatus, bool payloadCRCStatus) +{ + if (m_settings.m_hasHeader && headerParityStatus) { + ui->headerHammingStatus->setStyleSheet("QLabel { background-color : green; }"); + } else if (m_settings.m_hasHeader && !headerParityStatus) { + ui->headerHammingStatus->setStyleSheet("QLabel { background-color : red; }"); + } else { + ui->headerHammingStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + } + + if (m_settings.m_hasHeader && headerCRCStatus) { + ui->headerCRCStatus->setStyleSheet("QLabel { background-color : green; }"); + } else if (m_settings.m_hasHeader && !headerCRCStatus) { + ui->headerCRCStatus->setStyleSheet("QLabel { background-color : red; }"); + } else { + ui->headerCRCStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + } + + if (payloadParityStatus) { + ui->payloadFECStatus->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->payloadFECStatus->setStyleSheet("QLabel { background-color : red; }"); + } + + if (payloadCRCStatus) { + ui->payloadCRCStatus->setStyleSheet("QLabel { background-color : green; }"); + } else { + ui->payloadCRCStatus->setStyleSheet("QLabel { background-color : red; }"); + } +} + +void LoRaDemodGUI::resetLoRaStatus() +{ + ui->headerHammingStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->headerCRCStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->payloadFECStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); + ui->payloadCRCStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); +} + void LoRaDemodGUI::setBandwidths() { int maxBandwidth = m_basebandSampleRate/LoRaDemodSettings::oversampling; @@ -382,6 +522,49 @@ void LoRaDemodGUI::addText(const QString& text) ui->messageText->verticalScrollBar()->setValue(ui->messageText->verticalScrollBar()->maximum()); } +void LoRaDemodGUI::displayBytes(const QByteArray& bytes, unsigned int packetLength, bool hasCRC) +{ + QByteArray bytesCopy(bytes); + bytesCopy.truncate(packetLength); + bytesCopy.replace('\0', " "); + QString str = QString(bytesCopy.toStdString().c_str()); + str.chop(hasCRC ? 2 : 0); + addText(str); + + QDateTime dt = QDateTime::currentDateTime(); + QString dateStr = dt.toString("=== HH:mm:ss ==="); + QTextCursor cursor = ui->hexText->textCursor(); + cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + + if (!ui->hexText->document()->isEmpty()) { + cursor.insertText("\n"); + } + + cursor.insertText(tr("%1\n").arg(dateStr)); + QByteArray::const_iterator it = bytes.begin(); + unsigned int i = 0; + + for (;it != bytes.end(); ++it, i++) + { + unsigned char b = *it; + + if (i%16 == 0) { + cursor.insertText(tr("%1|").arg(i, 3, 10, QChar('0'))); + } + + cursor.insertText(tr("%1").arg(b, 2, 16)); + + if (i%16 == 15) { + cursor.insertText("\n"); + } else if (i%4 == 3) { + cursor.insertText("|"); + } else { + cursor.insertText(" "); + } + } + + ui->hexText->verticalScrollBar()->setValue(ui->hexText->verticalScrollBar()->maximum()); +} void LoRaDemodGUI::tick() { @@ -400,3 +583,4 @@ void LoRaDemodGUI::tick() } } } + diff --git a/plugins/channelrx/demodlora/lorademodgui.h b/plugins/channelrx/demodlora/lorademodgui.h index 9f8ddc78f..71efb5ad7 100644 --- a/plugins/channelrx/demodlora/lorademodgui.h +++ b/plugins/channelrx/demodlora/lorademodgui.h @@ -59,11 +59,17 @@ private slots: void on_BW_valueChanged(int value); void on_Spread_valueChanged(int value); void on_deBits_valueChanged(int value); + void on_preambleChirps_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 on_header_stateChanged(int state); + void on_fecParity_valueChanged(int value); + void on_crc_stateChanged(int state); + void on_errorCheck_stateChanged(int state); + void on_packetLength_valueChanged(int value); void onWidgetRolled(QWidget* widget, bool rollDown); void channelMarkerHighlightedByCursor(); void handleInputMessages(); @@ -92,6 +98,9 @@ private: void displaySquelch(); void setBandwidths(); void addText(const QString& text); + void displayBytes(const QByteArray& bytes, unsigned int packetLength, bool hasCRC); + void displayLoRaStatus(bool headerParityStatus, bool headerCRCStatus, bool payloadParityStatus, bool payloadCRCStatus); + void resetLoRaStatus(); }; #endif // INCLUDE_LoRaDEMODGUI_H diff --git a/plugins/channelrx/demodlora/lorademodgui.ui b/plugins/channelrx/demodlora/lorademodgui.ui index af938a025..a9ec43f02 100644 --- a/plugins/channelrx/demodlora/lorademodgui.ui +++ b/plugins/channelrx/demodlora/lorademodgui.ui @@ -6,14 +6,14 @@ 0 0 - 410 - 620 + 500 + 660 - 380 - 620 + 500 + 660 @@ -28,16 +28,16 @@ - 10 + 5 20 - 390 - 102 + 490 + 90 0 - 102 + 90 @@ -74,7 +74,7 @@ 30 50 - 251 + 361 16 @@ -102,7 +102,7 @@ 30 70 - 131 + 80 16 @@ -131,15 +131,15 @@ - 170 + 120 70 - 30 + 20 16 - 30 + 20 0 @@ -153,7 +153,7 @@ - 300 + 400 50 80 16 @@ -175,7 +175,7 @@ - 220 + 170 70 22 16 @@ -188,15 +188,15 @@ - 350 + 290 70 - 30 + 15 16 - 30 + 15 0 @@ -210,9 +210,9 @@ - 250 + 200 70 - 91 + 80 16 @@ -243,7 +243,7 @@ 10 10 - 371 + 471 26 @@ -389,20 +389,86 @@ + + + + 450 + 70 + 20 + 16 + + + + + 20 + 0 + + + + 8 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 340 + 70 + 32 + 16 + + + + Pre + + + + + + 370 + 70 + 81 + 16 + + + + Expected number of preamble chirps + + + 4 + + + 20 + + + 1 + + + 8 + + + 8 + + + Qt::Horizontal + + - 10 - 130 - 390 - 160 + 5 + 120 + 490 + 230 0 - 160 + 230 @@ -412,7 +478,7 @@ 2 - 40 + 60 32 16 @@ -421,21 +487,11 @@ Msg - - - - 30 - 120 - 351 - 20 - - - 2 - 120 + 145 32 16 @@ -448,9 +504,9 @@ 30 - 40 - 351 - 75 + 60 + 455 + 80 @@ -496,7 +552,7 @@ 4 - 60 + 80 20 20 @@ -548,7 +604,7 @@ - 180 + 190 10 35 19 @@ -561,7 +617,7 @@ - 210 + 220 8 22 22 @@ -586,7 +642,7 @@ - 230 + 240 10 28 19 @@ -602,7 +658,7 @@ - 272 + 290 10 20 19 @@ -615,7 +671,7 @@ - 290 + 310 8 22 22 @@ -625,7 +681,7 @@ Message (payload) length in number of symbols - 20 + 8 255 @@ -634,13 +690,13 @@ 1 - 255 + 127 - 310 + 330 10 25 19 @@ -656,8 +712,8 @@ - 350 - 9 + 455 + 10 25 20 @@ -675,14 +731,270 @@ 00 + + + + 2 + 35 + 50 + 16 + + + + LoRa + + + + + + 140 + 30 + 24 + 24 + + + + Number of FEC parity bits (0 to 4) for Hamming code + + + 4 + + + 1 + + + 1 + + + + + + 60 + 35 + 50 + 16 + + + + Expect header (explicit) - disables manual FEC and CRC + + + HDR + + + + + + 160 + 35 + 12 + 16 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 180 + 35 + 50 + 16 + + + + CRC appended to payload + + + CRC + + + + + + 115 + 35 + 25 + 16 + + + + FEC + + + + + + 230 + 35 + 50 + 16 + + + + Check to disable display of payloads with errors + + + ERR + + + + + + 30 + 145 + 455 + 75 + + + + + Liberation Mono + 9 + + + + + + + 290 + 35 + 20 + 16 + + + + Pkt + + + + + + 310 + 30 + 24 + 24 + + + + Payload packet length in number of bytes or characters + + + 225 + + + 1 + + + 30 + + + + + + 330 + 35 + 25 + 16 + + + + 255 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 420 + 10 + 30 + 19 + + + + Sync + + + + + + 370 + 35 + 20 + 16 + + + + Header FEC parity status + + + HF + + + + + + 395 + 35 + 20 + 16 + + + + Header CRC status + + + HC + + + + + + 420 + 35 + 25 + 16 + + + + Payload FEC parity status + + + FEC + + + + + + 455 + 35 + 28 + 16 + + + + Payload CRC status + + + CRC + + - 10 - 300 - 390 - 310 + 5 + 360 + 490 + 260 @@ -715,7 +1027,7 @@ 0 - 300 + 230 diff --git a/plugins/channelrx/demodlora/lorademodmsg.h b/plugins/channelrx/demodlora/lorademodmsg.h index 4cab52ce2..7088225dd 100644 --- a/plugins/channelrx/demodlora/lorademodmsg.h +++ b/plugins/channelrx/demodlora/lorademodmsg.h @@ -27,12 +27,12 @@ namespace LoRaDemodMsg MESSAGE_CLASS_DECLARATION public: - const std::vector& getSymbols() const { return m_symbols; } + 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) { + void pushBackSymbol(unsigned short symbol) { m_symbols.push_back(symbol); } void popSymbol() { @@ -51,12 +51,12 @@ namespace LoRaDemodMsg static MsgDecodeSymbols* create() { return new MsgDecodeSymbols(); } - static MsgDecodeSymbols* create(const std::vector symbols) { + static MsgDecodeSymbols* create(const std::vector symbols) { return new MsgDecodeSymbols(symbols); } private: - std::vector m_symbols; + std::vector m_symbols; unsigned int m_syncWord; float m_signalDb; float m_noiseDb; @@ -67,7 +67,7 @@ namespace LoRaDemodMsg m_signalDb(0.0), m_noiseDb(0.0) {} - MsgDecodeSymbols(const std::vector symbols) : //!< create a message with symbols copy + MsgDecodeSymbols(const std::vector symbols) : //!< create a message with symbols copy Message(), m_syncWord(0), m_signalDb(0.0), diff --git a/plugins/channelrx/demodlora/lorademodsettings.cpp b/plugins/channelrx/demodlora/lorademodsettings.cpp index 2e458ff51..260ee4a1a 100644 --- a/plugins/channelrx/demodlora/lorademodsettings.cpp +++ b/plugins/channelrx/demodlora/lorademodsettings.cpp @@ -45,6 +45,12 @@ void LoRaDemodSettings::resetToDefaults() m_decodeActive = true; m_eomSquelchTenths = 60; m_nbSymbolsMax = 255; + m_preambleChirps = 8; + m_packetLength = 32; + m_nbParityBits = 1; + m_hasCRC = true; + m_hasHeader = true; + m_errorCheck = false; m_rgbColor = QColor(255, 0, 255).rgb(); m_title = "LoRa Demodulator"; } @@ -70,6 +76,12 @@ QByteArray LoRaDemodSettings::serialize() const s.writeBool(9, m_decodeActive); s.writeS32(10, m_eomSquelchTenths); s.writeS32(11, m_nbSymbolsMax); + s.writeS32(12, m_packetLength); + s.writeS32(13, m_nbParityBits); + s.writeBool(14, m_hasCRC); + s.writeBool(15, m_hasHeader); + s.writeBool(16, m_errorCheck); + s.writeS32(17, m_preambleChirps); return s.final(); } @@ -110,6 +122,12 @@ bool LoRaDemodSettings::deserialize(const QByteArray& data) d.readBool(9, &m_decodeActive, true); d.readS32(10, &m_eomSquelchTenths, 60); d.readS32(11, &m_nbSymbolsMax, 255); + d.readS32(12, &m_packetLength, 32); + d.readS32(13, &m_nbParityBits, 1); + d.readBool(14, &m_hasCRC, true); + d.readBool(15, &m_hasHeader, true); + d.readBool(16, &m_errorCheck, false); + d.readS32(17, &m_preambleChirps, 8); return true; } diff --git a/plugins/channelrx/demodlora/lorademodsettings.h b/plugins/channelrx/demodlora/lorademodsettings.h index 8d2c0657d..fd1c9b47a 100644 --- a/plugins/channelrx/demodlora/lorademodsettings.h +++ b/plugins/channelrx/demodlora/lorademodsettings.h @@ -43,6 +43,12 @@ struct LoRaDemodSettings bool m_decodeActive; int m_eomSquelchTenths; //!< Squelch factor to trigger end of message (/10) int m_nbSymbolsMax; //!< Maximum number of symbols in a payload + int m_preambleChirps; //!< Number of expected preamble chirps + int m_nbParityBits; //!< Hamming parity bits (LoRa) + int m_packetLength; //!< Payload packet length in bytes or characters (LoRa) + bool m_hasCRC; //!< Payload has CRC (LoRa) + bool m_hasHeader; //!< Header present before actual payload (LoRa) + bool m_errorCheck; //!< Error check failure cancels decoding (LoRa) uint32_t m_rgbColor; QString m_title; diff --git a/plugins/channelrx/demodlora/lorademodsink.cpp b/plugins/channelrx/demodlora/lorademodsink.cpp index 4eca6cd21..6e150a5ca 100644 --- a/plugins/channelrx/demodlora/lorademodsink.cpp +++ b/plugins/channelrx/demodlora/lorademodsink.cpp @@ -159,7 +159,7 @@ void LoRaDemodSink::processSample(const Complex& ci) m_fftInterpolation ) / m_fftInterpolation; - if (m_magsqQueue.size() > m_requiredPreambleChirps + 1) { + if (m_magsqQueue.size() > m_settings.m_preambleChirps) { m_magsqQueue.pop(); } @@ -260,6 +260,7 @@ void LoRaDemodSink::processSample(const Complex& ci) if (m_chirpCount < 3) // too early { m_state = LoRaStateReset; + qDebug("LoRaDemodSink::processSample: SFD search: signal drop is too early"); } else { @@ -288,8 +289,9 @@ void LoRaDemodSink::processSample(const Complex& ci) m_state = LoRaStateSkipSFD; //LoRaStateSlideSFD; } } - else if (m_chirpCount > m_maxSFDSearchChirps) // SFD missed start over + else if (m_chirpCount > (m_settings.m_preambleChirps - m_requiredPreambleChirps + 2)) // SFD missed start over { + qDebug("LoRaDemodSink::processSample: SFD search: number of possible chirps exceeded"); m_state = LoRaStateReset; } else @@ -339,7 +341,7 @@ void LoRaDemodSink::processSample(const Complex& ci) m_fftCounter = 0; double magsq; - unsigned int symbol = evalSymbol( + unsigned short symbol = evalSymbol( argmax( m_fft->out(), m_fftInterpolation, diff --git a/plugins/channelrx/demodlora/lorademodsink.h b/plugins/channelrx/demodlora/lorademodsink.h index 908de592b..5cd2bbce9 100644 --- a/plugins/channelrx/demodlora/lorademodsink.h +++ b/plugins/channelrx/demodlora/lorademodsink.h @@ -123,87 +123,6 @@ private: void decimateSpectrum(Complex *in, Complex *out, unsigned int size, unsigned int decimation); int toSigned(int u, int intSize); unsigned int evalSymbol(unsigned int rawSymbol); - - /* - Interleaving is "easiest" if the same number of bits is used per symbol as for FEC - Chosen mode "spreading 8, low rate" has 6 bits per symbol, so use 4:6 FEC - - More spreading needs higher frequency resolution and longer time on air, increasing drift errors. - Want higher bandwidth when using more spreading, which needs more CPU and a better FFT. - - Six bit Hamming can only correct long runs of drift errors when not using interleaving. Interleaving defeats the point of using Gray code and puts multiple bit errors into single FEC blocks. Hardware decoding uses RSSI to detect the symbols most likely to be damaged, so that individual bits can be repaired after de-interleaving. - - Using Implicit Mode: explicit starts with a 4:8 block and seems to have a different whitening sequence. - */ - - // Six bits per symbol, six chars per block - inline void interleave6(char* inout, int size) - { - int i, j; - char in[6 * 2]; - short s; - - for (j = 0; j < size; j+=6) { - for (i = 0; i < 6; i++) - in[i] = in[i + 6] = inout[i + j]; - // top bits are swapped - for (i = 0; i < 6; i++) { - s = (32 & in[2 + i]) | (16 & in[1 + i]) | (8 & in[3 + i]) - | (4 & in[4 + i]) | (2 & in[5 + i]) | (1 & in[6 + i]); - // bits are also rotated - s = (s << 3) | (s >> 3); - s &= 63; - s = (s >> i) | (s << (6 - i)); - inout[i + j] = s & 63; - } - } - } - - inline short toGray(short num) - { - return (num >> 1) ^ num; - } - - // Ignore the FEC bits, just extract the data bits - inline void hamming6(char* c, int size) - { - int i; - - for (i = 0; i < size; i++) { - c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<0) | ((c[i] & 4)>>0) | ((c[i] & 8)>>3); - i++; - c[i] = ((c[i] & 1)<<2) | ((c[i] & 2)<<2) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3); - i++; - c[i] = ((c[i] &32)>>2) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3); - i++; - c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3); - i++; - c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] &16)>>4); - i++; - c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>2) | ((c[i] & 8)>>2); - } - c[i] = 0; - } - - // data whitening (6 bit) - inline void prng6(char* inout, int size) - { - const char otp[] = { - //explicit mode - "cOGGg7CM2=b5a?<`i;T2of5jDAB=2DoQ9ko?h_RLQR4@Z\\`9jY\\PX89lHX8h_R]c_^@OB<0`W08ik?Mg>dQZf3kn5Je5R=R4h[dY?d9[n5Lg]b]R8hR<0`T008h9c9QJm[c?a:lQEGa;nU=b_WfUV2?V4@c=8h9B9njlQZDC@9Z(bytes.data()), nbSymbolBits - headerSize); - Sx1272ComputeWhitening(codewords.data() + headerSize, nbSymbolBits - headerSize, 0, 4); + Sx1272ComputeWhitening(codewords.data() + headerSize, nbSymbolBits - headerSize, 0, headerParityBits); // encode and whiten the rest of the payload with 4 + nbParityBits bits codewords if (numCodewords > nbSymbolBits) @@ -75,15 +75,15 @@ void LoRaModEncoderLoRa::encodeBytes( Sx1272ComputeWhitening(codewords.data() + cOfs2, numCodewords - nbSymbolBits, nbSymbolBits - headerSize, nbParityBits); } - const unsigned int numSymbols = 8 + (numCodewords / nbSymbolBits - 1) * (4 + nbParityBits); // header is always coded with 8 bits + const unsigned int numSymbols = headerSymbols + (numCodewords / nbSymbolBits - 1) * (4 + nbParityBits); // header is always coded with 8 bits // interleave the codewords into symbols symbols.clear(); symbols.resize(numSymbols); - diagonalInterleaveSx(codewords.data(), nbSymbolBits, symbols.data(), nbSymbolBits, 4); + diagonalInterleaveSx(codewords.data(), nbSymbolBits, symbols.data(), nbSymbolBits, headerParityBits); if (numCodewords > nbSymbolBits) { - diagonalInterleaveSx(codewords.data() + nbSymbolBits, numCodewords - nbSymbolBits, symbols.data() + 8, nbSymbolBits, nbParityBits); + diagonalInterleaveSx(codewords.data() + nbSymbolBits, numCodewords - nbSymbolBits, symbols.data() + headerSymbols, nbSymbolBits, nbParityBits); } // gray decode diff --git a/plugins/channeltx/modlora/loramodencoderlora.h b/plugins/channeltx/modlora/loramodencoderlora.h index fb422fa03..67e77eeea 100644 --- a/plugins/channeltx/modlora/loramodencoderlora.h +++ b/plugins/channeltx/modlora/loramodencoderlora.h @@ -46,6 +46,10 @@ private: const unsigned int codewordCount ); + static const unsigned int headerParityBits = 4; + static const unsigned int headerSymbols = 8; + static const unsigned int headerCodewords = 5; + /*********************************************************************** * Round functions **********************************************************************/