Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions docs/stats_binary_frames.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The `CMD_GET_STATS` command uses a 2-byte frame structure:
- `STATS_TYPE_RADIO` (1) - Get radio statistics
- `STATS_TYPE_PACKETS` (2) - Get packet statistics

For unsupported sub-types, firmware returns:
- `RESP_CODE_ERR` (`1`)
- error code `ERR_CODE_ILLEGAL_ARG` (`6`)

## Response Codes

| Response | Code | Description |
Expand Down Expand Up @@ -94,7 +98,7 @@ struct StatsRadio {

## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)

**Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`)
**Total Frame Size:** 30 bytes

| Offset | Size | Type | Field Name | Description | Range/Notes |
|--------|------|------|------------|-------------|-------------|
Expand All @@ -106,14 +110,14 @@ struct StatsRadio {
| 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 |
| 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 |
| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib) | 0 - 4,294,967,295 |

### Notes

- Counters are cumulative from boot and may wrap.
- `recv = flood_rx + direct_rx`
- `sent = flood_tx + direct_tx`
- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26.
- Current companion firmware always writes `recv_errors` in this frame.

### Example Structure (C/C++)

Expand All @@ -127,7 +131,7 @@ struct StatsPackets {
uint32_t direct_tx;
uint32_t flood_rx;
uint32_t direct_rx;
uint32_t recv_errors; // present when frame size is 30
uint32_t recv_errors;
} __attribute__((packed));
```

Expand Down Expand Up @@ -186,23 +190,20 @@ def parse_stats_radio(frame):
}

def parse_stats_packets(frame):
"""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 = \
struct.unpack('<B B I I I I I I', frame[:26])
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (30 bytes)"""
assert len(frame) >= 30, "STATS_TYPE_PACKETS frame too short"
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx, recv_errors = \
struct.unpack('<B B I I I I I I I', frame[:30])
assert response_code == 24 and stats_type == 2, "Invalid response type"
result = {
return {
'recv': recv,
'sent': sent,
'flood_tx': flood_tx,
'direct_tx': direct_tx,
'flood_rx': flood_rx,
'direct_rx': direct_rx
'direct_rx': direct_rx,
'recv_errors': recv_errors
}
if len(frame) >= 30:
(recv_errors,) = struct.unpack('<I', frame[26:30])
result['recv_errors'] = recv_errors
return result
```

---
Expand Down Expand Up @@ -259,7 +260,7 @@ interface StatsPackets {
direct_tx: number;
flood_rx: number;
direct_rx: number;
recv_errors?: number; // present when frame is 30 bytes
recv_errors: number;
}

function parseStatsCore(buffer: ArrayBuffer): StatsCore {
Expand Down Expand Up @@ -295,7 +296,7 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {

function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
const view = new DataView(buffer);
if (buffer.byteLength < 26) {
if (buffer.byteLength < 30) {
throw new Error('STATS_TYPE_PACKETS frame too short');
}
const response_code = view.getUint8(0);
Expand All @@ -309,11 +310,9 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
flood_tx: view.getUint32(10, true),
direct_tx: view.getUint32(14, true),
flood_rx: view.getUint32(18, true),
direct_rx: view.getUint32(22, true)
direct_rx: view.getUint32(22, true),
recv_errors: view.getUint32(26, true)
};
if (buffer.byteLength >= 30) {
result.recv_errors = view.getUint32(26, true);
}
return result;
}
```
Expand All @@ -325,4 +324,3 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
- Packet counters (uint32_t): May wrap after extended high-traffic operation.
- Time fields (uint32_t): Max ~136 years.
- SNR (int8_t, scaled by 4): Range -32 to +31.75 dB, 0.25 dB precision.