[rio] link-health telemetry: req/rep/btn counters, reply age, the existing (never-printed) line-error/abandon/board counters, and STALL/RECOVER event lines -- default-on for hardware RIO, BT_RIO_LOG=0 opts out (encoder-dropout hunt on ALPHA-MR)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-10 12:34:10 -05:00
co-authored by Claude Fable 5
parent a7722d9ca9
commit a28878beaf
+91
View File
@@ -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();
}