///////////////////////////////////////////////////////////////////////////////////
// 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 "chirpchatdemoddecoderlora.h"
void ChirpChatDemodDecoderLoRa::decodeHeader(
const std::vector& inSymbols,
unsigned int nbSymbolBits,
bool& hasCRC,
unsigned int& nbParityBits,
unsigned int& packetLength,
int& headerParityStatus,
bool& headerCRCStatus
)
{
// with header (H: header 8-bit codeword P: payload-8 bit codeword):
// nbSymbolBits = 5 |H|H|H|H|H| codewords => 8 symbols (always) : static headerSymbols = 8
// nbSymbolBits = 7 |H|H|H|H|H|P|P|
// without header (P: payload 8-bit codeword):
// nbSymbolBits = 5 |P|P|P|P|P| codewords => 8 symbols (always)
// nbSymbolBits = 7 |P|P|P|P|P|P|P|
// Actual header is always represented with 5 8-bit codewords : static headerCodewords = 5
// These 8-bit codewords are encoded with Hamming(4,8) FEC : static headerParityBits = 4
std::vector symbols(headerSymbols);
std::copy(inSymbols.begin(), inSymbols.begin() + headerSymbols, symbols.begin());
//gray encode
for (auto &sym : symbols) {
sym = binaryToGray16(sym);
}
std::vector codewords(nbSymbolBits);
// Header symbols de-interleave thus headerSymbols (8) symbols into nbSymbolBits (5..12) codewords using header FEC (4/8)
diagonalDeinterleaveSx(symbols.data(), headerSymbols, codewords.data(), nbSymbolBits, headerParityBits);
// whitening does not apply to the header codewords
Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, nbSymbolBits - headerCodewords, 0, headerParityBits);
bool error = false;
bool bad = false;
uint8_t bytes[3];
// decode actual header inside 8-bit codewords header with 4/8 FEC (5 first codewords)
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);
if (bad)
{
headerParityStatus = (int) ParityError;
}
else
{
if (error) {
headerParityStatus = (int) ParityCorrected;
} else {
headerParityStatus = (int) ParityOK;
}
if (bytes[2] != 0) {
headerCRCStatus = false;
} else {
headerCRCStatus = true;
}
}
hasCRC = (bytes[1] & 1) != 0;
nbParityBits = (bytes[1] >> 1) & 0x7;
packetLength = bytes[0];
}
void ChirpChatDemodDecoderLoRa::decodeBytes(
QByteArray& inBytes,
const std::vector& inSymbols,
unsigned int nbSymbolBits,
bool hasHeader,
bool& hasCRC,
unsigned int& nbParityBits,
unsigned int& packetLength,
bool& earlyEOM,
int& headerParityStatus,
bool& headerCRCStatus,
int& payloadParityStatus,
bool& payloadCRCStatus
)
{
// need at least a header (8 symbols of 8 bit codewords) whether an actual header is sent or not
if (inSymbols.size() < headerSymbols)
{
qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: need at least %u symbols for header", headerSymbols);
earlyEOM = true;
return;
}
else
{
earlyEOM = false;
}
if (hasHeader)
{
decodeHeader(
inSymbols,
nbSymbolBits,
hasCRC,
nbParityBits,
packetLength,
headerParityStatus,
headerCRCStatus
);
}
qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: crc: %s nbParityBits: %u packetLength: %u",
hasCRC ? "on": "off", nbParityBits, packetLength);
if (nbParityBits > 4)
{
qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: invalid parity bits in header: %u", nbParityBits);
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;
// the first headerSymbols (8 symbols) are coded with 4/8 FEC (thus 8 bit codewords) whether an actual header is present or not
// this corresponds to nbSymbolBits codewords (therefore LoRa imposes nbSymbolBits >= headerCodewords (5 codewords) this is controlled externally)
if (nbParityBits != 4) // different FEC between header symbols and the rest of the packet
{
// Header symbols de-interleave thus headerSymbols (8) symbols into nbSymbolBits (5..12) codewords using header FEC (4/8)
diagonalDeinterleaveSx(symbols.data(), headerSymbols, codewords.data(), nbSymbolBits, headerParityBits);
if (hasHeader) { // whitening does not apply to the header codewords
Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, nbSymbolBits - headerCodewords, 0, headerParityBits);
} else {
Sx1272ComputeWhiteningLfsr(codewords.data(), nbSymbolBits, 0, headerParityBits);
}
cOfs += nbSymbolBits; // nbSymbolBits codewords in header
sOfs += headerSymbols; // headerSymbols symbols in header
if (numSymbols - sOfs > 0) // remaining payload symbols after header symbols using their own FEC (4/5..4/7)
{
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 // uniform 4/8 FEC for all the packet
{
// De-interleave the whole packet thus numSymbols into nbSymbolBits (5..12) codewords using packet FEC (4/8)
diagonalDeinterleaveSx(symbols.data(), numSymbols, codewords.data(), nbSymbolBits, nbParityBits);
if (hasHeader) { // whitening does not apply to the header codewords
Sx1272ComputeWhiteningLfsr(codewords.data() + headerCodewords, numCodewords - headerCodewords, 0, nbParityBits);
} else {
Sx1272ComputeWhiteningLfsr(codewords.data(), numCodewords, 0, nbParityBits);
}
}
// Now we have nbSymbolBits 8-bit codewords (4/8 FEC) possibly containing the actual header followed by the rest of payload codewords with their own FEC (4/5..4/8)
std::vector bytes((codewords.size()+1) / 2);
unsigned int dOfs = 0;
cOfs = 0;
unsigned int dataLength = packetLength + 3 + (hasCRC ? 2 : 0); // include header and CRC
if (hasHeader)
{
cOfs = headerCodewords;
dOfs = 6;
}
else
{
cOfs = 0;
dOfs = 0;
}
if (dataLength > bytes.size())
{
qDebug("ChirpChatDemodDecoderLoRa::decodeBytes: not enough data %lu vs %u", bytes.size(), dataLength);
earlyEOM = true;
return;
}
// decode the rest of the payload inside 8-bit codewords header with 4/8 FEC
bool error = false;
bool bad = false;
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) // decode the start of the payload codewords with their own FEC when not on an even boundary
{
if (nbParityBits == 1) {
bytes[dOfs/2] |= checkParity54(codewords[cOfs++], error) << 4;
} else if (nbParityBits == 2) {
bytes[dOfs/2] |= checkParity64(codewords[cOfs++], error) << 4;
} else if (nbParityBits == 3){
bytes[dOfs/2] |= decodeHamming74sx(codewords[cOfs++], error) << 4;
} else if (nbParityBits == 4){
bytes[dOfs/2] |= decodeHamming84sx(codewords[cOfs++], error, bad) << 4;
} else {
bytes[dOfs/2] |= codewords[cOfs++] << 4;
}
dOfs++;
}
dOfs /= 2;
// decode the rest of the payload codewords with their own FEC
if (nbParityBits == 1)
{
for (unsigned int i = dOfs; i < dataLength; i++)
{
bytes[i] = checkParity54(codewords[cOfs++],error);
bytes[i] |= checkParity54(codewords[cOfs++], error) << 4;
}
}
else if (nbParityBits == 2)
{
for (unsigned int i = dOfs; i < dataLength; i++)
{
bytes[i] = checkParity64(codewords[cOfs++], error);
bytes[i] |= checkParity64(codewords[cOfs++],error) << 4;
}
}
else if (nbParityBits == 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 (nbParityBits == 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 (bad) {
payloadParityStatus = (int) ParityError;
} else if (error) {
payloadParityStatus = (int) ParityCorrected;
} else {
payloadParityStatus = (int) ParityOK;
}
// finalization:
// adjust offsets dpending on header and CRC presence
// compute and verify payload CRC if present
if (hasHeader)
{
dOfs = 3; // skip header
dataLength -= 3; // remove header
if (hasCRC) // always compute crc if present skipping the header
{
uint16_t crc = sx1272DataChecksum(bytes.data() + dOfs, packetLength);
uint16_t packetCRC = bytes[dOfs + packetLength] | (bytes[dOfs + packetLength + 1] << 8);
if (crc != packetCRC) {
payloadCRCStatus = false;
} else {
payloadCRCStatus = true;
}
}
}
else
{
dOfs = 0; // no header to skip
if (hasCRC)
{
uint16_t crc = sx1272DataChecksum(bytes.data(), packetLength);
uint16_t packetCRC = bytes[packetLength] | (bytes[packetLength + 1] << 8);
if (crc != packetCRC) {
payloadCRCStatus = false;
} else {
payloadCRCStatus = true;
}
}
}
inBytes.resize(dataLength);
std::copy(bytes.data() + dOfs, bytes.data() + dOfs + dataLength, inBytes.data());
}
void ChirpChatDemodDecoderLoRa::getCodingMetrics(
unsigned int nbSymbolBits,
unsigned int nbParityBits,
unsigned int packetLength,
bool hasHeader,
bool hasCRC,
unsigned int& numSymbols,
unsigned int& numCodewords
)
{
numCodewords = roundUp((packetLength + (hasCRC ? 2 : 0))*2 + (hasHeader ? headerCodewords : 0), nbSymbolBits); // uses payload + CRC for encoding size
numSymbols = headerSymbols + (numCodewords / nbSymbolBits - 1) * (4 + nbParityBits); // header is always coded with 8 bits and yields exactly 8 symbols (headerSymbols)
}