mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-03 21:20:31 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
///////////////////////////////////////////////////////////////////////////////////
 | 
						|
// 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 <http://www.gnu.org/licenses/>.          //
 | 
						|
///////////////////////////////////////////////////////////////////////////////////
 | 
						|
 | 
						|
#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)
 | 
						|
    {
 | 
						|
        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<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) {
 | 
						|
        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)
 | 
						|
}
 |