Count and surface RX-queue drops on the ESP32-C5's promiscuous path
wifi_promisc_rx_cb() fed s_rx_queue with a 0-timeout xQueueSend() and never checked whether it succeeded, so a burst of captured frames arriving faster than rx_forward_task could drain them vanished with no counter anywhere - none of oversizeDrops/txFailures/rxCrcErrors caught it. Added a rxQueueDrops counter, threaded it through the STATUS heartbeat as a new trailing uint16 (old firmware/app on either side still parse fine), and surfaced it on the CAM Pinger card. Confirmed on the bench: flashed to the production OBU (COM3) and installed the matching app build on the phone, then watched the counter over logcat against obu-cam-transmistter's ~3.3 Hz beacon - it is real (0 -> 89 -> 90 across two sessions) but bursty around connect/reconnect rather than a continuous overflow under steady single-station traffic.
This commit is contained in:
@@ -57,8 +57,9 @@ const val SERIAL_LINK_MAX_PAYLOAD = 512
|
||||
|
||||
/**
|
||||
* Decoded [SerialFrameType.STATUS] payload: `[status:1][oversizeDrops:2 LE][txFailures:2 LE]
|
||||
* [rxCrcErrors:2 LE]` (7 bytes). Counters are free-running totals since firmware boot and
|
||||
* saturate at 0xFFFF rather than wrapping.
|
||||
* [rxCrcErrors:2 LE][capabilities:1][rxQueueDrops:2 LE]` (10 bytes; the last two fields are an
|
||||
* optional tail — see [capabilities] and [rxQueueDrops]). Counters are free-running totals since
|
||||
* firmware boot and saturate at 0xFFFF rather than wrapping.
|
||||
*
|
||||
* Exists so the phone can tell "link alive, no traffic" from "link dead", and so firmware-side
|
||||
* drops — which otherwise only reach `ESP_LOGW` on the flashing port that the phone isn't
|
||||
@@ -79,6 +80,15 @@ data class EspLinkStatus(
|
||||
* the phone needs from such firmware: it accepts nothing beyond the original messages.
|
||||
*/
|
||||
val capabilities: Int = 0,
|
||||
/**
|
||||
* Promiscuously-captured frames the firmware's `wifi_promisc_rx_cb` had to drop because its
|
||||
* RX queue (8 deep) was still full of frames `rx_forward_task` hadn't finished forwarding —
|
||||
* bytes 8-9 of the payload. 0 for firmware that predates this field (payload of 7 or 8 bytes),
|
||||
* which is the honest answer: such firmware drops these frames identically, it just never
|
||||
* counted them. A nonzero, growing value here — as opposed to [oversizeDrops] — points at
|
||||
* bursty RX outrunning the forward task rather than any one frame being too large.
|
||||
*/
|
||||
val rxQueueDrops: Int = 0,
|
||||
) {
|
||||
/** True when the firmware accepts [SerialFrameType.CAM_TX_PV]. */
|
||||
val supportsCamTxPv: Boolean get() = capabilities and CAP_CAM_TX_PV != 0
|
||||
@@ -99,6 +109,7 @@ data class EspLinkStatus(
|
||||
txFailures = u16(3),
|
||||
rxCrcErrors = u16(5),
|
||||
capabilities = if (payload.size > PAYLOAD_SIZE) payload[7].toInt() and 0xFF else 0,
|
||||
rxQueueDrops = if (payload.size >= 10) u16(8) else 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,13 +330,15 @@ class UsbSerialTransport @Inject constructor(
|
||||
prev.txFailures != status.txFailures ||
|
||||
prev.rxCrcErrors != status.rxCrcErrors ||
|
||||
prev.status != status.status ||
|
||||
prev.capabilities != status.capabilities
|
||||
prev.capabilities != status.capabilities ||
|
||||
prev.rxQueueDrops != status.rxQueueDrops
|
||||
) {
|
||||
Log.i(TAG, "ESP32 counters: status=${status.status} " +
|
||||
"oversizeDrops=${status.oversizeDrops} " +
|
||||
"txFailures=${status.txFailures} " +
|
||||
"rxCrcErrors=${status.rxCrcErrors} " +
|
||||
"capabilities=${status.capabilities}")
|
||||
"capabilities=${status.capabilities} " +
|
||||
"rxQueueDrops=${status.rxQueueDrops}")
|
||||
}
|
||||
_linkStatus.value = status
|
||||
}
|
||||
|
||||
@@ -1219,11 +1219,12 @@ private fun CamPingerCard(
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.mqtt_cam_pinger_fw_counters,
|
||||
s.txFailures, s.oversizeDrops, s.rxCrcErrors,
|
||||
s.txFailures, s.oversizeDrops, s.rxCrcErrors, s.rxQueueDrops,
|
||||
),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = if (s.txFailures > 0 || s.oversizeDrops > 0 || s.rxCrcErrors > 0)
|
||||
ErrorRed else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
color = if (s.txFailures > 0 || s.oversizeDrops > 0 || s.rxCrcErrors > 0 ||
|
||||
s.rxQueueDrops > 0
|
||||
) ErrorRed else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
<string name="mqtt_cam_pinger_active">Pinging - 1 CAM/s over the serial link</string>
|
||||
<string name="mqtt_cam_pinger_sent_count">Sent: %1$d</string>
|
||||
<string name="mqtt_cam_pinger_send_failures">Write failures: %1$d consecutive - CAMs are not reaching the ESP32</string>
|
||||
<string name="mqtt_cam_pinger_fw_counters">ESP32: tx fail %1$d · oversize %2$d · crc err %3$d</string>
|
||||
<string name="mqtt_cam_pinger_fw_counters">ESP32: tx fail %1$d · oversize %2$d · crc err %3$d · rx queue drop %4$d</string>
|
||||
<string name="mqtt_cam_pinger_loopback">Own TX heard back: %1$d frames · %2$d dBm</string>
|
||||
<string name="mqtt_cam_pinger_loopback_no_rssi">Own TX heard back: %1$d frames</string>
|
||||
<string name="mqtt_start_pinger">Start Pinger</string>
|
||||
|
||||
@@ -166,6 +166,21 @@ class CamTxPvSerialTest {
|
||||
assertFalse(EspLinkStatus.parse("0000000000000002".hexToBytes())!!.supportsCamTxPv)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `firmware that predates the rx queue drop counter reports zero`() {
|
||||
// 8-byte heartbeat (status + counters + capabilities, no rx queue drops tail).
|
||||
val status = EspLinkStatus.parse("0000000000000001".hexToBytes())!!
|
||||
assertEquals(0, status.rxQueueDrops)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rx queue drops are read little-endian from the 10-byte payload`() {
|
||||
// status=0, oversize=0, txFail=0, rxCrc=0, capabilities=0x01, rxQueueDrops=0x0102 (LE: 02 01)
|
||||
val status = EspLinkStatus.parse("00000000000000010201".hexToBytes())!!
|
||||
assertEquals(0x0102, status.rxQueueDrops)
|
||||
assertTrue(status.supportsCamTxPv)
|
||||
}
|
||||
|
||||
private val mac = "024d49435230".hexToBytes()
|
||||
|
||||
private fun vectorAt(tstMs: Long) = GnPositionVector(
|
||||
|
||||
Reference in New Issue
Block a user