Fix Meshtastic soft FEC payload status

This commit is contained in:
jvn314
2026-08-27 22:37:03 -05:00
parent 262ad92a4b
commit e63a3b792b
2 changed files with 88 additions and 6 deletions
@@ -432,6 +432,8 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
{
payloadCRCStatus = false;
payloadParityStatus = (int) MeshtasticDemodSettings::ParityUndefined;
bool error = false; // set if any payload codeword requires soft FEC correction
bool bad = false; // set if any payload codeword has a structural decode failure
if (inSymbols.size() < headerSymbols)
{
@@ -542,7 +544,18 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
}
}
auto decodeSoftBlock = [&llrs, spreadFactor](unsigned int symOfs, unsigned int cwLen, unsigned int sfApp, unsigned int crApp, std::vector<uint8_t>& nibbles) {
auto decodeSoftBlock = [
&llrs,
spreadFactor,
&error, &bad // accumulated payload FEC status
](
unsigned int symOfs,
unsigned int cwLen,
unsigned int sfApp,
unsigned int crApp,
unsigned int payloadRowStart, // first row that contributes to payload FEC status
std::vector<uint8_t>& nibbles) {
if (sfApp == 0U) {
return false;
}
@@ -569,8 +582,22 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
}
}
for (unsigned int row = 0; row < sfApp; row++) {
nibbles.push_back(decodeCodewordSoft(deinterBin[row], crApp));
for (unsigned int row = 0; row < sfApp; row++)
{
if (row < payloadRowStart)
{
// Still in header, don't accumulate payload FEC
bool ignoredError = false;
bool ignoredBad = false;
nibbles.push_back(
decodeCodewordSoft(deinterBin[row], crApp, ignoredError, ignoredBad));
}
else
{
// Payload row contributes to accumulated payload FEC status
nibbles.push_back(
decodeCodewordSoft(deinterBin[row], crApp, error, bad));
}
}
return true;
@@ -587,7 +614,15 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
return;
}
if (!decodeSoftBlock(symOfs, 8U, headerNbSymbolBits, 4U, nibbles)) {
const unsigned int payloadRowStart = headerCodewords; // payload starts after the 5 header codewords
if (!decodeSoftBlock(
symOfs,
headerSymbols,
headerNbSymbolBits,
headerParityBits,
payloadRowStart,
nibbles)) {
earlyEOM = true;
return;
}
@@ -595,9 +630,17 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
symOfs += headerSymbols;
}
const unsigned int payloadOnlyRowStart = 0U; // all rows in these blocks are payload
while (symOfs + payloadBlockSymbols <= numSymbols)
{
if (!decodeSoftBlock(symOfs, payloadBlockSymbols, payloadNbSymbolBits, nbParityBits, nibbles)) {
if (!decodeSoftBlock(
symOfs,
payloadBlockSymbols,
payloadNbSymbolBits,
nbParityBits,
payloadOnlyRowStart,
nibbles)) {
earlyEOM = true;
return;
}
@@ -628,6 +671,15 @@ void MeshtasticDemodDecoderLoRa::decodeBytesSoft(
dewhitenPayloadBytes(bytes.data(), packetLength);
}
// Report accumulated payload FEC result independently of CRC
if (bad) {
payloadParityStatus = (int) MeshtasticDemodSettings::ParityError;
} else if (error) {
payloadParityStatus = (int) MeshtasticDemodSettings::ParityCorrected;
} else {
payloadParityStatus = (int) MeshtasticDemodSettings::ParityOK;
}
if (hasCRC)
{
if ((packetLength >= 2U) && (dataByteLen >= packetLength + 2U))
@@ -274,7 +274,11 @@ private:
return static_cast<unsigned char>((nibbleBits[0] << 3) | (nibbleBits[1] << 2) | (nibbleBits[2] << 1) | nibbleBits[3]);
}
static inline unsigned char decodeCodewordSoft(const std::vector<float>& codewordLLR, unsigned int crApp)
static inline unsigned char decodeCodewordSoft(
const std::vector<float>& codewordLLR,
unsigned int crApp,
bool& error, // set if soft FEC changes a hard decision; caller may accumulate across codewords
bool& bad) // set on structural decode failure; caller may accumulate across codewords
{
static const unsigned char cwLUT[16] = {
0, 23, 45, 58, 78, 89, 99, 116,
@@ -286,10 +290,17 @@ private:
};
if ((crApp < 1U) || (crApp > 4U)) {
bad = true; // invalid coding rate: structural decode failure
return 0;
}
const unsigned int cwLen = 4U + crApp;
if (codewordLLR.size() < cwLen) {
bad = true; // insufficient LLR data: structural decode failure (later mapped to ParityError)
return 0;
}
const unsigned char *lut = (crApp == 1U) ? cwLUTCr5 : cwLUT;
float bestScore = std::numeric_limits<float>::lowest();
unsigned int bestIdx = 0U;
@@ -312,6 +323,25 @@ private:
}
}
const unsigned char selectedCW =
static_cast<unsigned char>(lut[bestIdx] >> (8U - cwLen));
for (unsigned int j = 0; j < cwLen; j++)
{
if (codewordLLR[j] == 0.0f) {
continue; // no hard-decision preference for an exactly zero LLR
}
const bool hardBit = codewordLLR[j] > 0.0f;
const bool selectedBit =
((selectedCW >> (cwLen - 1U - j)) & 0x1U) != 0U;
if (hardBit != selectedBit) {
error = true; // soft FEC changed one or more raw hard-decision bits
break;
}
}
const unsigned char dataNibbleSoft = static_cast<unsigned char>(cwLUT[bestIdx] >> 4);
return static_cast<unsigned char>(
(((dataNibbleSoft & 0x1U) != 0U) << 3) |