Add recv_errors to CMD_GET_STATS STATS_TYPE_PACKETS response

Append uint32_t recv_errors (RadioLib receive/CRC errors) to packet stats
binary frame. Frame size 26 -> 30 bytes. Update stats_binary_frames.md and
Python/TypeScript parsing examples for backward compatibility (accept >=26).
This commit is contained in:
agessaman
2026-01-29 20:44:11 -08:00
parent acca73f57e
commit 019bbf74d3
2 changed files with 23 additions and 5 deletions

View File

@@ -94,7 +94,7 @@ struct StatsRadio {
## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) ## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)
**Total Frame Size:** 26 bytes **Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`)
| Offset | Size | Type | Field Name | Description | Range/Notes | | Offset | Size | Type | Field Name | Description | Range/Notes |
|--------|------|------|------------|-------------|-------------| |--------|------|------|------------|-------------|-------------|
@@ -106,12 +106,14 @@ struct StatsRadio {
| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | | 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | | 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | | 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib); present only in 30-byte frame | 0 - 4,294,967,295 |
### Notes ### Notes
- Counters are cumulative from boot and may wrap. - Counters are cumulative from boot and may wrap.
- `recv = flood_rx + direct_rx` - `recv = flood_rx + direct_rx`
- `sent = flood_tx + direct_tx` - `sent = flood_tx + direct_tx`
- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26.
### Example Structure (C/C++) ### Example Structure (C/C++)
@@ -125,6 +127,7 @@ struct StatsPackets {
uint32_t direct_tx; uint32_t direct_tx;
uint32_t flood_rx; uint32_t flood_rx;
uint32_t direct_rx; uint32_t direct_rx;
uint32_t recv_errors; // present when frame size is 30
} __attribute__((packed)); } __attribute__((packed));
``` ```
@@ -183,11 +186,12 @@ def parse_stats_radio(frame):
} }
def parse_stats_packets(frame): def parse_stats_packets(frame):
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)""" """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 or 30 bytes)"""
assert len(frame) >= 26, "STATS_TYPE_PACKETS frame too short"
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
struct.unpack('<B B I I I I I I', frame) struct.unpack('<B B I I I I I I', frame[:26])
assert response_code == 24 and stats_type == 2, "Invalid response type" assert response_code == 24 and stats_type == 2, "Invalid response type"
return { result = {
'recv': recv, 'recv': recv,
'sent': sent, 'sent': sent,
'flood_tx': flood_tx, 'flood_tx': flood_tx,
@@ -195,6 +199,10 @@ def parse_stats_packets(frame):
'flood_rx': flood_rx, 'flood_rx': flood_rx,
'direct_rx': direct_rx 'direct_rx': direct_rx
} }
if len(frame) >= 30:
(recv_errors,) = struct.unpack('<I', frame[26:30])
result['recv_errors'] = recv_errors
return result
``` ```
--- ---
@@ -251,6 +259,7 @@ interface StatsPackets {
direct_tx: number; direct_tx: number;
flood_rx: number; flood_rx: number;
direct_rx: number; direct_rx: number;
recv_errors?: number; // present when frame is 30 bytes
} }
function parseStatsCore(buffer: ArrayBuffer): StatsCore { function parseStatsCore(buffer: ArrayBuffer): StatsCore {
@@ -286,12 +295,15 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
function parseStatsPackets(buffer: ArrayBuffer): StatsPackets { function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
const view = new DataView(buffer); const view = new DataView(buffer);
if (buffer.byteLength < 26) {
throw new Error('STATS_TYPE_PACKETS frame too short');
}
const response_code = view.getUint8(0); const response_code = view.getUint8(0);
const stats_type = view.getUint8(1); const stats_type = view.getUint8(1);
if (response_code !== 24 || stats_type !== 2) { if (response_code !== 24 || stats_type !== 2) {
throw new Error('Invalid response type'); throw new Error('Invalid response type');
} }
return { const result: StatsPackets = {
recv: view.getUint32(2, true), recv: view.getUint32(2, true),
sent: view.getUint32(6, true), sent: view.getUint32(6, true),
flood_tx: view.getUint32(10, true), flood_tx: view.getUint32(10, true),
@@ -299,6 +311,10 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
flood_rx: view.getUint32(18, true), flood_rx: view.getUint32(18, true),
direct_rx: view.getUint32(22, true) direct_rx: view.getUint32(22, true)
}; };
if (buffer.byteLength >= 30) {
result.recv_errors = view.getUint32(26, true);
}
return result;
} }
``` ```

View File

@@ -1688,12 +1688,14 @@ void MyMesh::handleCmdFrame(size_t len) {
uint32_t n_sent_direct = getNumSentDirect(); uint32_t n_sent_direct = getNumSentDirect();
uint32_t n_recv_flood = getNumRecvFlood(); uint32_t n_recv_flood = getNumRecvFlood();
uint32_t n_recv_direct = getNumRecvDirect(); uint32_t n_recv_direct = getNumRecvDirect();
uint32_t n_recv_errors = radio_driver.getPacketsRecvErrors();
memcpy(&out_frame[i], &recv, 4); i += 4; memcpy(&out_frame[i], &recv, 4); i += 4;
memcpy(&out_frame[i], &sent, 4); i += 4; memcpy(&out_frame[i], &sent, 4); i += 4;
memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; memcpy(&out_frame[i], &n_sent_flood, 4); i += 4;
memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; memcpy(&out_frame[i], &n_sent_direct, 4); i += 4;
memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; memcpy(&out_frame[i], &n_recv_flood, 4); i += 4;
memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; memcpy(&out_frame[i], &n_recv_direct, 4); i += 4;
memcpy(&out_frame[i], &n_recv_errors, 4); i += 4;
_serial->writeFrame(out_frame, i); _serial->writeFrame(out_frame, i);
} else { } else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type