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. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
21e01499d8
commit
71dde3364d
@@ -5,6 +5,71 @@ Engineering to-do list. The reviewer-facing open items live in
|
||||
|
||||
## Waiting on hardware
|
||||
|
||||
### Confirm the RX queue drop counter explains the bench-session frame drops / map flicker (added 2026-09-22)
|
||||
|
||||
Investigated the user's report of "OBU mode keeps dropping a few frames" and "v2x screen comes
|
||||
and goes" while bench-testing against `obu-cam-transmistter`. Found a real, previously invisible
|
||||
drop path: `obu-firmware/main/main.c`'s `wifi_promisc_rx_cb()` calls `xQueueSend(s_rx_queue, ...,
|
||||
0)` (queue depth 8) without checking the return value, so a burst of promiscuously-captured
|
||||
frames arriving faster than `rx_forward_task` can drain them (each drain can legitimately block up
|
||||
to ~400ms under USB/UART contention) silently vanishes. None of the existing `EspLinkStatus`
|
||||
counters (`oversizeDrops`/`txFailures`/`rxCrcErrors`) caught this class of drop.
|
||||
|
||||
This plausibly also explains the map symptom: `UseCaseDetectionEngine.pruneStale()` drops a remote
|
||||
station's marker after `staleRemoteMs` (3 s) with no CAM update. Measured 2026-09-22 via
|
||||
`tools/cit_one_rx_watch.py --host 192.168.40.201` against `obu-cam-transmistter`'s bench beacon
|
||||
(stationID 195936478 / 0x0BADC0DE): **75 CAMs in 25 s, ~3 Hz**, not the 1 Hz this note assumed
|
||||
earlier — faster than assumed means more promiscuous captures per second and a shorter fuse on
|
||||
`staleRemoteMs`, both of which make the queue-overflow theory more likely, not less.
|
||||
|
||||
Fixed to be **visible**, not yet fixed to **not drop**: added a `rxQueueDrops` counter, checked
|
||||
`xQueueSend`'s return value (`main.c`), wired it through the STATUS heartbeat as a new trailing
|
||||
`uint16` field (`serial_link.c/.h`, `SerialFrame.kt`'s `EspLinkStatus`), and surfaced it on the
|
||||
CAM Pinger card (`MqttTopicViewerScreen.kt`, string `mqtt_cam_pinger_fw_counters`). Host build
|
||||
untouched (serial_link.c/main.c aren't in the host test's standard-headers-only set); IDF build
|
||||
verification is the remaining pre-flash check. Deliberately did NOT bump `s_rx_queue`'s depth from
|
||||
8 — no real burst-size data yet, and guessing a bigger number against an unmeasured memory budget
|
||||
is exactly the kind of assumption [[microbu-hw-review]] flags as needing verification first, not
|
||||
capacity that's cheap to reason your way into.
|
||||
|
||||
Needs: a phone attached to the production OBU's native USB port, watching the CAM Pinger card,
|
||||
while `obu-cam-transmistter` (or real traffic) beacons.
|
||||
|
||||
- [x] `idf.py build` succeeds (obu-firmware, IDF 6.1) — clean, both changed files compiled with no
|
||||
warnings, 17% flash free.
|
||||
- [x] Reflashed the production OBU on **COM3** 2026-09-22 (hash verified). Boot log confirms the
|
||||
new build (`21e0149-dirty`, compiled Sep 22 2026 14:14:09), clean boot, OCB @ 5900 MHz
|
||||
TX/RX armed, `serial_link up ... 1 Hz heartbeat`, no panic. Incidentally answers part of the
|
||||
"measure the OBU's actual transmit power" item below: this boot logged
|
||||
`tx power: 72 quarter-dBm = 18.00 dBm (20.00 requested)` — the driver **is** clamping below
|
||||
the requested 20 dBm at 5900 MHz, as that item suspected but had not measured.
|
||||
- [x] 25 s of steady-state console (no phone attached, `obu-cam-transmistter` beaconing nearby):
|
||||
silent — no crash, no `oversize`/`rx queue full`/`crc` warnings. Inconclusive on its own
|
||||
(successful forwards aren't logged, and nothing was attached to trigger the ~400 ms UART
|
||||
stalls the theory needs), but at least rules out a crash-on-boot regression.
|
||||
- [x] Confirmed the wider bench RF path independently via the CiT One OBU broker
|
||||
(`py -3.11 tools/cit_one_rx_watch.py --host 192.168.40.201`): heard `obu-cam-transmistter`'s
|
||||
beacon cleanly, 75/25 s, GN source `14:00:02:00:00:00:00:01`, position in the expected
|
||||
St. Georg route area. This is a *different* receiver from the production OBU though — it
|
||||
shows the beacon is genuinely on air, not that COM3 forwards every one of it without drops.
|
||||
- [x] **Confirmed on real hardware, 2026-09-22.** Installed the updated debug APK (previous build
|
||||
on the phone was from 2026-09-15, predating this fix entirely) on the Pixel 9 Pro (adb over
|
||||
Wi-Fi), relaunched against the freshly-reflashed COM3, and read `rx queue drop` via `adb
|
||||
logcat -s UsbSerialTransport`. The counter mechanism works end-to-end and **the bug is
|
||||
real**: `rxQueueDrops` was 0 at the last flash (14:22), read as 89 at first reconnect
|
||||
(14:48, ~26 min later), and 90 at a second reconnect (14:52). No `oversizeDrops`,
|
||||
`txFailures`, or `rxCrcErrors` moved at all, and zero `decode FAILED` lines — this queue is
|
||||
the only place frames are going missing.
|
||||
Nuance: over a clean ~4.5 min window in between (14:48→14:52) with `obu-cam-transmistter`
|
||||
actively beaconing at a measured **~3.33 Hz** (matches the CiT One's 75/25 s independently)
|
||||
and 490+ CAMs decoding cleanly with steady cadence and no gaps, the counter did **not**
|
||||
move — it only ticked at connect/reconnect moments. So this is a low-rate, bursty drop (matches
|
||||
the user's own "a few frames" framing), not a continuous overflow under steady single-station
|
||||
traffic; it may be specific to WiFi/PHY activity around association or reconnect rather than
|
||||
raw beacon rate. Worth a longer, quieter-boot capture before sizing a `s_rx_queue` bump.
|
||||
Did **not** independently confirm the map-flicker connection this session — that needs eyes
|
||||
on the app's V2X screen while watching this same counter live, not just logcat.
|
||||
|
||||
### On-device check of the full-screen V2X live map (added 2026-09-15)
|
||||
|
||||
The live map moved out of the V2X Monitor's view-mode row into its own full-screen destination
|
||||
@@ -51,6 +116,27 @@ Partial check possible with one board and no phone: flash it, `idf.py -p COMx mo
|
||||
for `OCB @ 5900 MHz - TX/RX armed`. That proves the new build boots and brings the radio up, not
|
||||
that it transmits correctly.
|
||||
|
||||
### Measure the OBU's actual transmit power (added 2026-09-14)
|
||||
|
||||
Nothing in this project has ever measured it. `main.c` asks for 20 dBm
|
||||
(`esp_wifi_set_max_tx_power(80)`, 0.25 dBm units) and the build's ceiling is the same
|
||||
(`CONFIG_ESP_PHY_MAX_TX_POWER=20`), but a request is a ceiling, not a guarantee: the driver clamps
|
||||
it to its own calibrated table, and 5900 MHz is above the range this chip is rated for, so the
|
||||
table actually in use is channel 177's. The firmware now reads the value back and logs it at boot,
|
||||
which records what the driver admits to, not what leaves the antenna.
|
||||
|
||||
- [ ] Flash and `idf.py -p COM3 monitor`, then note the `tx power:` line. A value below 80 means
|
||||
the driver clamped the request, which the code alone cannot tell you.
|
||||
- [ ] Relative check with the second ESP32-C5 on `its-g5-receiver-firmware`: capture at a measured
|
||||
distance in a straight line, read the RSSI the receive path already reports, and record
|
||||
distance and RSSI together. This gives a comparable number between builds and antennas,
|
||||
which is what matters for range work, without any lab equipment.
|
||||
- [ ] Only a spectrum analyser or a calibrated reference receiver gives real radiated power. Worth
|
||||
it only if the range result looks wrong, or if the thesis needs an absolute figure.
|
||||
|
||||
For context: ETSI allows up to 33 dBm EIRP on the ITS band, and production OBUs sit around
|
||||
20 to 23 dBm, so the requested figure is in the right region if the PA really keys it there.
|
||||
|
||||
### obu-cam-transmistter yawRateConfidence fix (added 2026-09-11)
|
||||
|
||||
Its `cam.c` (compiled into that firmware) wrote `yawRateConfidence` as 3 bits / 7 instead of
|
||||
@@ -80,6 +166,58 @@ optional, but shows what was on air at the time.
|
||||
shorter).
|
||||
- [ ] The heartbeat's oversize counter still counts over-long messages (e.g. road SPATEMs).
|
||||
|
||||
### CiT One custom CAM injection over `v2x/tx/v2/cam` (added 2026-09-14)
|
||||
|
||||
The haw-002 unit now runs the special firmware: Cohda's own CAM transmission disabled, and a
|
||||
V2X-Gateway build that accepts a `SendV2XMessage` (schemas.consider-innovation.de/its-s/
|
||||
v2x_interface.proto) carrying a UPER CAM on `v2x/tx/v2/cam`. `tools/cit_one_cam_tx.py` builds
|
||||
and publishes those from a PC; its `--self-test` passes offline, proving only that the bytes
|
||||
match `CamEncodeGoldenTest.kt` and that the protobuf wrapper round-trips. Nothing about what
|
||||
the OBU does with them is established.
|
||||
|
||||
Reach the broker over Wi-Fi or Ethernet for now - the USB-peripheral-mode link needs the phone
|
||||
to be USB host on a `172.25.1.0/24` interface with no DHCP server, which Android cannot
|
||||
configure from inside an app.
|
||||
|
||||
Needs: the CiT One haw-002 on the same network as a PC, and a second ESP32-C5 running
|
||||
`its-g5-receiver-firmware` sniffing G5CC (`-c 5900`) to capture with.
|
||||
|
||||
Bench run 2026-09-14, PC -> haw-002 (192.168.3.201), captured on the RSU (192.168.3.202,
|
||||
**not** .2.202 - that address does not route). `tools/cit_one_rx_watch.py` decodes what a unit
|
||||
hears. Result: the injection path works end to end, with one blocker found.
|
||||
|
||||
- [x] Publishes without the broker refusing the topic. 1.00 Hz, confirmed by subscribing to
|
||||
`v2x/tx/v2/cam` on the OBU itself.
|
||||
- [x] The RSU hears our CAMs on air, 1.00 Hz, matching what we publish.
|
||||
- [x] `ItsPduHeader` **is** expected in the payload - we send it included and it decodes.
|
||||
- [x] BTP destination port 2001. GN source address `08:00:26:93:92:01:91:dc`, the OBU's.
|
||||
- [x] **Our CAM content goes out intact**: position, speed (417), heading (639), width (7) and
|
||||
length (18) arrive byte-exact. The gateway does not touch the content.
|
||||
- [x] **The gateway overwrites `stationID`** with the OBU's own (999999 -> 4033890855, which
|
||||
matches `own_info.stationID` on `v2x/rx/obu_gnss`). This is what the "OBU owns identity"
|
||||
decision wants, so `--follow-obu-identity` is not needed on this unit.
|
||||
- [x] ~~BLOCKER: Cohda's own CAM is still transmitting.~~ Fixed 2026-09-14 by disabling CAM in
|
||||
a second conf file: the RSU now hears only our stream, 0 CAMs with the stack's
|
||||
unavailable dimensions over 30 s. Note the stack restart gave the unit a new identity
|
||||
(stationID 4033890855 -> 2553426533, GN source `08:00:26:...` -> `08:00:a2:...`), which is
|
||||
expected under `ItsGnLocalAddrConfMethod = 2` (anonymous, random at boot).
|
||||
- [x] Re-checked: 41 published / 41 heard over 40 s, 1.02 Hz both ends, inter-arrival a steady
|
||||
1.0 s. 100% delivery, no gateway rate limiting. An earlier 0.40 Hz sample was the stack
|
||||
still settling after the restart and did not persist.
|
||||
Two topics the v6 API does not document, found by subscribing to `#` on haw-002:
|
||||
|
||||
- `v2x/loopback/cam` - a `RecvV2XMessage` (btpHeader.type=2) carrying each CAM the unit
|
||||
transmits, 1:1 with what we publish and **after** the gateway's stationID rewrite. This is the
|
||||
TX confirmation we were going to ask consider it for: it makes "did my CAM go out, and under
|
||||
which identity" answerable on the transmitting unit alone, without an RSU or a second ESP32.
|
||||
- `v2x/rx/obuinfo` at 10 Hz - the protobuf `OwnStationInfo` (binary twin of `obu_gnss`;
|
||||
field 2 decodes to the same stationID, field 10 to the same heading). Output only, so it is
|
||||
not the content-feed input we speculated about.
|
||||
|
||||
- [ ] Wire `v2x/loopback/cam` into `cit_one_rx_watch.py` as a local TX check.
|
||||
- [ ] Sanity-check the rate: `--rate 4` should produce 4 CAMs/s on air, since `ItsDCCEnabled = 0`
|
||||
on this unit.
|
||||
|
||||
## Set up host testing
|
||||
|
||||
- [x] Install MSYS2 UCRT64 gcc (done 2026-09-11: gcc 16.2.0, GNU Make 4.4.1; chosen over WSL,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -259,8 +259,13 @@ static void wifi_promisc_rx_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type)
|
||||
s_cb_item.rssi = packet->rx_ctrl.rssi;
|
||||
|
||||
// 0 timeout: never block the WiFi driver's own task waiting for queue space. xQueueSend copies
|
||||
// the struct out before returning, so reusing s_cb_item on the next callback is fine.
|
||||
xQueueSend(s_rx_queue, &s_cb_item, 0);
|
||||
// the struct out before returning, so reusing s_cb_item on the next callback is fine. The
|
||||
// return value used to go unchecked, so a full queue (rx_forward_task still draining a
|
||||
// previous burst) silently ate frames with no counter anywhere - see
|
||||
// serial_link_note_rx_queue_drop()'s KDoc.
|
||||
if (xQueueSend(s_rx_queue, &s_cb_item, 0) != pdTRUE) {
|
||||
serial_link_note_rx_queue_drop();
|
||||
}
|
||||
}
|
||||
|
||||
static void rx_forward_task(void *arg)
|
||||
|
||||
@@ -21,6 +21,7 @@ static serial_link_cam_tx_pv_cb_t s_on_cam_tx_pv;
|
||||
static uint16_t s_oversize_drops;
|
||||
static uint16_t s_tx_failures;
|
||||
static uint16_t s_rx_crc_errors;
|
||||
static uint16_t s_rx_queue_drops;
|
||||
|
||||
// Serializes send_frame(): it writes a frame as four separate usb_serial_jtag_write_bytes() calls
|
||||
// and shares one static CRC scratch buffer, and it's now called from three tasks (rx_forward for
|
||||
@@ -45,6 +46,12 @@ void serial_link_note_oversize_drop(uint16_t btp_dest_port)
|
||||
btp_dest_port, s_oversize_drops);
|
||||
}
|
||||
|
||||
void serial_link_note_rx_queue_drop(void)
|
||||
{
|
||||
bump(&s_rx_queue_drops);
|
||||
ESP_LOGW(TAG, "rx queue full, dropped a captured frame, total rx queue drops %u", s_rx_queue_drops);
|
||||
}
|
||||
|
||||
// ---- CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect, no xorout) ----
|
||||
// Bytewise (no table) - frames here are at most SERIAL_LINK_MAX_PAYLOAD + 3 bytes, so table
|
||||
// lookup isn't worth the flash/RAM tradeoff. MUST match the Kotlin-side implementation exactly
|
||||
@@ -165,9 +172,10 @@ bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
|
||||
bool serial_link_send_status(uint8_t status)
|
||||
{
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1] -
|
||||
// keep in lockstep with EspLinkStatus.parse() in the app's SerialFrame.kt.
|
||||
uint8_t payload[8];
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1]
|
||||
// [rx_queue_drops:2 LE] - keep in lockstep with EspLinkStatus.parse() in the app's
|
||||
// SerialFrame.kt.
|
||||
uint8_t payload[10];
|
||||
payload[0] = status;
|
||||
payload[1] = (uint8_t)(s_oversize_drops & 0xFF);
|
||||
payload[2] = (uint8_t)((s_oversize_drops >> 8) & 0xFF);
|
||||
@@ -178,6 +186,8 @@ bool serial_link_send_status(uint8_t status)
|
||||
// What this firmware accepts. The app reads it to decide whether it may send CAM_TX_PV, which
|
||||
// is what lets a new app keep working against firmware that predates that message.
|
||||
payload[7] = SERIAL_CAP_CAM_TX_PV;
|
||||
payload[8] = (uint8_t)(s_rx_queue_drops & 0xFF);
|
||||
payload[9] = (uint8_t)((s_rx_queue_drops >> 8) & 0xFF);
|
||||
return send_frame(SERIAL_MSG_STATUS, payload, sizeof(payload));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,11 +52,14 @@
|
||||
// message's own ItsPduHeader.stationID is the meaningful identifier.
|
||||
// SERIAL_MSG_STATUS (0x03), ESP32 -> phone: heartbeat + counters, sent at 1 Hz so the phone can
|
||||
// distinguish "link idle" from "link dead" independent of CAM traffic (the app's watchdog in
|
||||
// UsbSerialTransport.kt declares the link dead after 3 missed beats). Payload is 8 bytes:
|
||||
// UsbSerialTransport.kt declares the link dead after 3 missed beats). Payload is 10 bytes:
|
||||
// [status:1][oversize_drops:2 LE][tx_failures:2 LE][rx_crc_errors:2 LE][capabilities:1]
|
||||
// [rx_queue_drops:2 LE]
|
||||
// status 0 = ok. The counters are free-running totals since boot, saturating at 0xFFFF.
|
||||
// capabilities is a bitmask of the SERIAL_CAP_* flags below. It was appended as byte 7 rather
|
||||
// than inserted, so an app that predates it, and reads only the first 7 bytes, is unaffected.
|
||||
// than inserted, so an app that predates it, and reads only the first 7 bytes, is unaffected;
|
||||
// rx_queue_drops (bytes 8-9) follows the same rule for an app that predates it. Either side
|
||||
// reading a payload shorter than the field it wants should treat that field as 0, not error.
|
||||
// They exist because the alternative - ESP_LOGW on the flashing port - is invisible to the
|
||||
// phone, which is the only thing watching during a bench session. Mirrored by EspLinkStatus
|
||||
// in the app's SerialFrame.kt.
|
||||
@@ -165,4 +168,13 @@ void serial_link_note_tx_failure(void);
|
||||
// whose capture buffer is smaller than the largest frames on air.
|
||||
void serial_link_note_oversize_drop(uint16_t btp_dest_port);
|
||||
|
||||
// Counts a promiscuously-captured frame that main.c's wifi_promisc_rx_cb() could not hand to
|
||||
// rx_forward_task because s_rx_queue was full - i.e. frames arrived faster than the forward task
|
||||
// (gn_unwrap + a blocking USB write, up to SERIAL_LINK_WRITE_TIMEOUT_MS x 4 per frame under
|
||||
// contention) could drain them. Unlike oversize_drop this is not about one frame's size; it is
|
||||
// about a burst of otherwise-forwardable frames. Previously silent - xQueueSend's return value
|
||||
// was not even checked - so a run of these had no visible symptom beyond "that station's CAM
|
||||
// count looked a little low."
|
||||
void serial_link_note_rx_queue_drop(void);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user