diff --git a/engine/MUNGA_L4/L4RIO.cpp b/engine/MUNGA_L4/L4RIO.cpp index 1ebad97..89d5157 100644 --- a/engine/MUNGA_L4/L4RIO.cpp +++ b/engine/MUNGA_L4/L4RIO.cpp @@ -1240,6 +1240,23 @@ RIO::~RIO() Check_Fpu(); } +// +// [rio] telemetry state (see the block in GetNextEvent). File-scope statics +// on purpose: one hardware RIO per process, and no header/layout churn. +// +static int sRioLog = -1; +static unsigned gRioReqCount = 0; +static unsigned gRioRepCount = 0; +static unsigned gRioBtnCount = 0; +static int gRioReqPending = 0; +static DWORD gRioLastReqTick = 0; +static DWORD gRioLastReplyTick = 0; +static DWORD gRioLastReportTick = 0; +static int gRioStallActive = 0; +static DWORD gRioStallStart = 0; +static unsigned gRioStallBtnBase = 0; +static unsigned gRioStallErrBase = 0; + Logical RIO::TestInstance() const { @@ -1264,6 +1281,57 @@ Logical Logical reply, looping; Word errors; + // + // [rio] link-health telemetry (encoder-dropout hunt, 2026-08-10). The + // protocol is polled (host requests analog, board replies) over an + // ACK-gated packet layer, so ONE lost byte can freeze every axis until + // unrelated board traffic resyncs the exchange -- which is exactly what + // a dropout-revived-by-a-button feels like from the seat. These lines + // discriminate wire noise (lineErr/overrun climbing) from protocol jams + // (abandon/rRetry/rFull climbing) from quiet stalls (age grows, counters + // still). STALL = a specific analog request unanswered for >500 ms; + // RECOVER records whether a BUTTON arrived during the stall (the field + // theory). Default ON on real-RIO rigs only; BT_RIO_LOG=0 opts out. + // + { + if (sRioLog < 0) + { + const char *e = getenv("BT_RIO_LOG"); + sRioLog = (e != 0 && *e == '0') ? 0 : 1; + } + if (sRioLog && operational) + { + DWORD now = GetTickCount(); + if (!gRioStallActive && gRioReqPending + && now - gRioLastReqTick > 500) + { + gRioStallActive = 1; + gRioStallStart = now; + gRioStallBtnBase = gRioBtnCount; + gRioStallErrBase = (unsigned)(lineErrorCount + abandonCount); + DEBUG_STREAM << "[rio] STALL analog request unanswered " + << (now - gRioLastReqTick) << "ms req=" << gRioReqCount + << " rep=" << gRioRepCount << "\n" << std::flush; + } + if (now - gRioLastReportTick >= 5000) + { + gRioLastReportTick = now; + DEBUG_STREAM << "[rio] req=" << gRioReqCount + << " rep=" << gRioRepCount + << " btn=" << gRioBtnCount + << " age=" << (gRioLastReplyTick != 0 + ? (now - gRioLastReplyTick) : 0) + << "ms lineErr=" << lineErrorCount + << " overrun=" << overrunCount + << " abandon=" << abandonCount + << " rRetry=" << remoteRetryCount + << " rAbandon=" << remoteAbandonCount + << " rFull=" << remoteFullBufferCount + << "\n" << std::flush; + } + } + } + //PCSPAKState(this); // //cout << flush; @@ -1377,6 +1445,24 @@ Logical break; case AnalogReply: + ++gRioRepCount; + gRioReqPending = 0; + gRioLastReplyTick = GetTickCount(); + if (gRioStallActive) + { + // The reply that ended the drought. btnDuring is the + // field theory's verdict: >0 on every RECOVER = button + // traffic is what resyncs the link; 0 = it self-heals. + DEBUG_STREAM << "[rio] RECOVER after " + << (gRioLastReplyTick - gRioStallStart) + << "ms btnDuring=" + << (gRioBtnCount - gRioStallBtnBase) + << " errsDuring=" + << ((unsigned)(lineErrorCount + abandonCount) + - gRioStallErrBase) + << "\n" << std::flush; + gRioStallActive = 0; + } destination->Type = RIO::AnalogEvent; // // NOTE: no data is sent in this packet. @@ -1423,11 +1509,13 @@ Logical break; case ButtonPressed: + ++gRioBtnCount; destination->Type = RIO::ButtonPressedEvent; destination->Data.Unit = receive_buffer[1]; break; case ButtonReleased: + ++gRioBtnCount; destination->Type = RIO::ButtonReleasedEvent; destination->Data.Unit = receive_buffer[1]; break; @@ -1568,6 +1656,9 @@ void if (operational && !TestModeActive) { SendPacket((Byte *) request_analog_string); + ++gRioReqCount; + gRioReqPending = 1; + gRioLastReqTick = GetTickCount(); } Check_Fpu(); }