|
|
@@ -94,7 +94,7 @@ struct StatsRadio {
|
|
|
|
|
|
## 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 |
|
|
|
|--------|------|------|------------|-------------|-------------|
|
|
|
@@ -106,12 +106,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 |
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
### Example Structure (C/C++)
|
|
|
|
|
|
@@ -125,6 +127,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
|
|
|
} __attribute__((packed));
|
|
|
```
|
|
|
|
|
|
@@ -183,11 +186,12 @@ def parse_stats_radio(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 = \
|
|
|
- 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"
|
|
|
- return {
|
|
|
+ result = {
|
|
|
'recv': recv,
|
|
|
'sent': sent,
|
|
|
'flood_tx': flood_tx,
|
|
|
@@ -195,6 +199,10 @@ def parse_stats_packets(frame):
|
|
|
'flood_rx': flood_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;
|
|
|
flood_rx: number;
|
|
|
direct_rx: number;
|
|
|
+ recv_errors?: number; // present when frame is 30 bytes
|
|
|
}
|
|
|
|
|
|
function parseStatsCore(buffer: ArrayBuffer): StatsCore {
|
|
|
@@ -286,12 +295,15 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
|
|
|
|
|
|
function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
|
|
|
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 stats_type = view.getUint8(1);
|
|
|
if (response_code !== 24 || stats_type !== 2) {
|
|
|
throw new Error('Invalid response type');
|
|
|
}
|
|
|
- return {
|
|
|
+ const result: StatsPackets = {
|
|
|
recv: view.getUint32(2, true),
|
|
|
sent: view.getUint32(6, true),
|
|
|
flood_tx: view.getUint32(10, true),
|
|
|
@@ -299,6 +311,10 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
|
|
|
flood_rx: view.getUint32(18, true),
|
|
|
direct_rx: view.getUint32(22, true)
|
|
|
};
|
|
|
+ if (buffer.byteLength >= 30) {
|
|
|
+ result.recv_errors = view.getUint32(26, true);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
}
|
|
|
```
|
|
|
|