2020-11-09 05:35:18 -05:00
///////////////////////////////////////////////////////////////////////////////////
2023-11-18 06:02:48 -05:00
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
2020-11-09 05:35:18 -05:00
// //
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
2024-04-04 19:03:24 -04:00
# include "chirpchatdemodsettings.h"
2020-11-09 05:35:18 -05:00
# include "chirpchatdemoddecoderlora.h"
void ChirpChatDemodDecoderLoRa : : decodeHeader (
const std : : vector < unsigned short > & 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 < uint16_t > symbols ( headerSymbols ) ;
std : : copy ( inSymbols . begin ( ) , inSymbols . begin ( ) + headerSymbols , symbols . begin ( ) ) ;
//gray encode
for ( auto & sym : symbols ) {
sym = binaryToGray16 ( sym ) ;
}
std : : vector < uint8_t > 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 )
{
2024-04-04 19:03:24 -04:00
headerParityStatus = ( int ) ChirpChatDemodSettings : : ParityError ;
2020-11-09 05:35:18 -05:00
}
else
{
if ( error ) {
2024-04-04 19:03:24 -04:00
headerParityStatus = ( int ) ChirpChatDemodSettings : : ParityCorrected ;
2020-11-09 05:35:18 -05:00
} else {
2024-04-04 19:03:24 -04:00
headerParityStatus = ( int ) ChirpChatDemodSettings : : ParityOK ;
2020-11-09 05:35:18 -05:00
}
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 < unsigned short > & 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 < uint16_t > 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 < uint8_t > 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 < uint8_t > 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 ) {
2024-04-04 19:03:24 -04:00
payloadParityStatus = ( int ) ChirpChatDemodSettings : : ParityError ;
2020-11-09 05:35:18 -05:00
} else if ( error ) {
2024-04-04 19:03:24 -04:00
payloadParityStatus = ( int ) ChirpChatDemodSettings : : ParityCorrected ;
2020-11-09 05:35:18 -05:00
} else {
2024-04-04 19:03:24 -04:00
payloadParityStatus = ( int ) ChirpChatDemodSettings : : ParityOK ;
2020-11-09 05:35:18 -05:00
}
// 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)
2020-11-14 12:45:05 -05:00
}