feat(rx): always-on счётчики причин RX-событий (rxReasonCnt) + RXSTAT-сводка

rxBriefLog теперь безусловен: ВСЕГДА инкрементирует rxReasonCnt[reason]
(наблюдаемость по контракту живучести — работает в проде без печати),
печать события — только при IR_RX_BRIEF_LOG. 14 точек вызова развёрнуты
из-под #if (Glitch/Timing/Preamble/Sync/BufOverflow/Timeout/Crc/Ok +
pulse-filter пути); ISR-агрегатные MuteBegin/End/RawOverflow остаются
только при логе (их флаш живёт в brief-механике). Публичное API:
rxReasonCounters() / rxReasonCountersClear() / printRxReasonStats(Print&)
-> 'RXSTAT,GLITCH=..,TIME=..,...,OK=..'.
Компил-чек: LaserTestCheck (G4, флаг выкл) и Plan_B (F4, флаг вкл) — чисто.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 23:05:50 +03:00
parent 5bcaf4092a
commit 08ea71eb5e
2 changed files with 41 additions and 29 deletions

View File

@ -145,9 +145,19 @@ const __FlashStringHelper *IR_DecoderRaw::rxBriefReasonTag(RxBriefReason reason)
default: return F("UNK");
}
}
#endif // IR_RX_BRIEF_LOG (rxBriefReasonTag)
// Безусловна: always-on счёт причин RX-событий (наблюдаемость по контракту
// живучести — счётчики живут и в проде без печати); печать — только под флагом.
void IR_DecoderRaw::rxBriefLog(RxBriefReason reason, uint16_t a, uint16_t b, uint32_t tUs)
{
const uint8_t ri = (uint8_t)reason;
if (ri < 14U)
rxReasonCnt[ri]++;
#if !IR_RX_BRIEF_LOG
(void)a; (void)b; (void)tUs;
return;
#else
#if IR_RX_BRIEF_LOG_REJECT_ONLY
if (reason == RxBriefReason::Ok || reason == RxBriefReason::Preamble)
return;
@ -220,8 +230,26 @@ void IR_DecoderRaw::rxBriefLog(RxBriefReason reason, uint16_t a, uint16_t b, uin
break;
}
Serial.println();
#endif // IR_RX_BRIEF_LOG (печать)
}
void IR_DecoderRaw::printRxReasonStats(Print &out) const
{
static const char *const kTags[14] = {"?", "MUTEB", "MUTEE", "QRAW", "QFLT", "HOLD",
"GLITCH", "TIME", "PREAMB", "SYNC", "BUF",
"TIMEOUT", "CRC", "OK"};
out.print(F("RXSTAT"));
for (uint8_t i = 1; i < 14U; i++)
{
out.print(',');
out.print(kTags[i]);
out.print('=');
out.print(rxReasonCnt[i]);
}
out.println();
}
#if IR_RX_BRIEF_LOG
void IR_DecoderRaw::rxBriefNoteMuteBlockedIsr(uint32_t tUs)
{
(void)tUs;
@ -373,10 +401,8 @@ inline void IR_DecoderRaw::checkTimeout()
#if defined(IRDEBUG_SERIAL_PACK)
packTraceOnTimeoutOrAbort(false);
#endif
#if IR_RX_BRIEF_LOG
const uint16_t expected = (i_dataBuffer >= 8U) ? uint16_t(dataBuffer[0] & IR_MASK_MSG_INFO) : 0U;
rxBriefLog(RxBriefReason::Timeout, i_dataBuffer, expected, micros());
#endif
isRecive = false; // приём завершён
msgTypeReceive = 0;
// Как после listenStart(): без сброса isReciveRaw + firstRX() декодер остаётся
@ -487,9 +513,7 @@ void IR_DecoderRaw::processDecodedFront(const FrontStorage &currentFront)
if (short_low_glitch)
{
errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time);
#endif
#if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif
@ -504,9 +528,7 @@ void IR_DecoderRaw::processDecodedFront(const FrontStorage &currentFront)
if (micro_gap_rise)
{
errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, 1, 0, currentFront.time);
#endif
#if IR_GLITCH_REJECT_PHASE_NUDGE
irGlitchPhaseNudge(currentFront.time, riseSyncTime, prevRise);
#endif
@ -516,9 +538,7 @@ void IR_DecoderRaw::processDecodedFront(const FrontStorage &currentFront)
if (candRp <= riseTimeMax / 4U && !highCount && !lowCount)
{
errors.other++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Timing, irClampU16(candRp), 0, currentFront.time);
#endif
return;
}
@ -582,11 +602,9 @@ void IR_DecoderRaw::processDecodedFront(const FrontStorage &currentFront)
if (risePeriod > IR_timeout || isBufferOverflow || risePeriod < riseTimeMin || isWrongPack)
// ~Мы в пределах таймаута и буффер не переполнен и fix дроблёных единиц
{
#if IR_RX_BRIEF_LOG
if (!isBufferOverflow && !isWrongPack)
rxBriefLog(RxBriefReason::Timing, irClampU16((uint32_t)risePeriod),
irClampU16((uint32_t)highTime), currentFront.time);
#endif
return;
}
@ -752,9 +770,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
if (i_dataBuffer >= dataByteSizeMax * 8)
{ // проверка переполнения (>=: иначе при i_dataBuffer==dataByteSizeMax*8 запись dataBuffer[38] за границей массива — B3)
isBufferOverflow = true;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::BufferOverflow, i_dataBuffer, 0, micros());
#endif
#if defined(IRDEBUG_SERIAL_PACK)
if (packTraceOpen)
packTraceEmitErrorFlash(F("ERROR: buffer overflow"));
@ -831,9 +847,7 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
#endif
{
isWrongPack = true;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Sync, err_syncBit, 0, micros());
#endif
#if defined(IRDEBUG_SERIAL_PACK)
packTraceEmitErrorFlash(F("ERROR: Wrong sync bit"));
#endif
@ -935,12 +949,10 @@ void IR_DecoderRaw::writeToBuffer(bool bit, bool packTraceInvertFix)
#endif
const uint16_t errSum =
uint16_t(errors.lowSignal) + uint16_t(errors.highSignal) + uint16_t(errors.other);
#if IR_RX_BRIEF_LOG
if (isAvailable)
rxBriefLog(RxBriefReason::Ok, packSize, errSum, micros());
else
rxBriefLog(RxBriefReason::Crc, packSize, errSum, micros());
#endif
if (!isAvailable && packSize > 0 && packSize <= dataByteSizeMax) {
memcpy(rejectBuffer, dataBuffer, packSize);
rejectPackSize = static_cast<uint8_t>(packSize);
@ -1486,9 +1498,7 @@ void IR_DecoderRaw::pulseFilterPushRaw(const FrontStorage &e)
if (pulseFilterHoldCount >= kPulseFilterHoldCap)
{
pulseFilterDropHoldOverflow++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::HoldOverflow, irClampU16(pulseFilterDropHoldOverflow), 0, e.time);
#endif
pulseFilterShiftLeft(1);
}
@ -1513,9 +1523,7 @@ bool IR_DecoderRaw::pulseFilterTryTakeConfirmed(FrontStorage &out, uint32_t logT
if (dt < minUs)
{
pulseFilterDropGlitchPairs++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, logTime);
#endif
pulseFilterShiftLeft(2);
continue;
}
@ -1545,9 +1553,7 @@ bool IR_DecoderRaw::pulseFilterTryFlushOne(uint32_t nowUs, FrontStorage &out)
if (dt < IR_INPUT_MIN_PULSE_US)
{
pulseFilterDropGlitchPairs++;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Glitch, irClampU16(pulseFilterDropGlitchPairs), 0, nowUs);
#endif
pulseFilterShiftLeft(2);
continue;
}
@ -1623,9 +1629,7 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{
if ((uint32_t)(front.time - preambleCandidateLastEdgeTime) > candTimeout)
{
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, 0, front.time);
#endif
preambleStartCandidate(front);
}
@ -1646,9 +1650,7 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
{
preambleGoodPeriods = 0;
preambleMeanPeriod = 0;
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, 0, irClampU16(period), front.time);
#endif
return true;
}
@ -1669,9 +1671,7 @@ bool IR_DecoderRaw::preambleProcessEdge(const FrontStorage &front)
}
else
{
#if IR_RX_BRIEF_LOG
rxBriefLog(RxBriefReason::Preamble, preambleGoodPeriods, irClampU16(period), front.time);
#endif
preambleGoodPeriods = 1;
preambleMeanPeriod = (uint16_t)period;
}