# MeshCore Stats Binary Frames

## Stats Binary Frame Structures

Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order.

### Command Codes

<table id="bkmrk-commandcodedescripti"><thead><tr><th>Command</th><th>Code</th><th>Description</th></tr></thead><tbody><tr><td>`CMD_GET_STATS`</td><td>56</td><td>Get statistics (2-byte command: code + sub-type)</td></tr></tbody></table>

#### Stats Sub-Types

The `CMD_GET_STATS` command uses a 2-byte frame structure:

- **Byte 0:** `CMD_GET_STATS` (56)
- **Byte 1:** Stats sub-type:
- `STATS_TYPE_CORE` (0) - Get core device statistics
- `STATS_TYPE_RADIO` (1) - Get radio statistics
- `STATS_TYPE_PACKETS` (2) - Get packet statistics

### Response Codes

<table id="bkmrk-responsecodedescript"><thead><tr><th>Response</th><th>Code</th><th>Description</th></tr></thead><tbody><tr><td>`RESP_CODE_STATS`</td><td>24</td><td>Statistics response (2-byte response: code + sub-type)</td></tr></tbody></table>

#### Stats Response Sub-Types

The `RESP_CODE_STATS` response uses a 2-byte header structure:

- **Byte 0:** `RESP_CODE_STATS` (24)
- **Byte 1:** Stats sub-type (matches command sub-type):
- `STATS_TYPE_CORE` (0) - Core device statistics response
- `STATS_TYPE_RADIO` (1) - Radio statistics response
- `STATS_TYPE_PACKETS` (2) - Packet statistics response

\---

### RESP\_CODE\_STATS + STATS\_TYPE\_CORE (24, 0)

**Total Frame Size:** 11 bytes

<table id="bkmrk-offsetsizetypefield-"><thead><tr><th>Offset</th><th>Size</th><th>Type</th><th>Field Name</th><th>Description</th><th>Range/Notes</th></tr></thead><tbody><tr><td>0</td><td>1</td><td>uint8\_t</td><td>response\_code</td><td>Always `0x18` (24)</td><td>-</td></tr><tr><td>1</td><td>1</td><td>uint8\_t</td><td>stats\_type</td><td>Always `0x00` (STATS\_TYPE\_CORE)</td><td>-</td></tr><tr><td>2</td><td>2</td><td>uint16\_t</td><td>battery\_mv</td><td>Battery voltage in millivolts</td><td>0 - 65,535</td></tr><tr><td>4</td><td>4</td><td>uint32\_t</td><td>uptime\_secs</td><td>Device uptime in seconds</td><td>0 - 4,294,967,295</td></tr><tr><td>8</td><td>2</td><td>uint16\_t</td><td>errors</td><td>Error flags bitmask</td><td>-</td></tr><tr><td>10</td><td>1</td><td>uint8\_t</td><td>queue\_len</td><td>Outbound packet queue length</td><td>0 - 255</td></tr></tbody></table>

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

```
struct StatsCore {
 uint8_t response_code; // 0x18
 uint8_t stats_type; // 0x00 (STATS_TYPE_CORE)
 uint16_t battery_mv;
 uint32_t uptime_secs;
 uint16_t errors;
 uint8_t queue_len;
} __attribute__((packed));
```

\---

### RESP\_CODE\_STATS + STATS\_TYPE\_RADIO (24, 1)

**Total Frame Size:** 14 bytes

<table id="bkmrk-offsetsizetypefield--1"><thead><tr><th>Offset</th><th>Size</th><th>Type</th><th>Field Name</th><th>Description</th><th>Range/Notes</th></tr></thead><tbody><tr><td>0</td><td>1</td><td>uint8\_t</td><td>response\_code</td><td>Always `0x18` (24)</td><td>-</td></tr><tr><td>1</td><td>1</td><td>uint8\_t</td><td>stats\_type</td><td>Always `0x01` (STATS\_TYPE\_RADIO)</td><td>-</td></tr><tr><td>2</td><td>2</td><td>int16\_t</td><td>noise\_floor</td><td>Radio noise floor in dBm</td><td>-140 to +10</td></tr><tr><td>4</td><td>1</td><td>int8\_t</td><td>last\_rssi</td><td>Last received signal strength in dBm</td><td>-128 to +127</td></tr><tr><td>5</td><td>1</td><td>int8\_t</td><td>last\_snr</td><td>SNR scaled by 4</td><td>Divide by 4.0 for dB</td></tr><tr><td>6</td><td>4</td><td>uint32\_t</td><td>tx\_air\_secs</td><td>Cumulative transmit airtime in seconds</td><td>0 - 4,294,967,295</td></tr><tr><td>10</td><td>4</td><td>uint32\_t</td><td>rx\_air\_secs</td><td>Cumulative receive airtime in seconds</td><td>0 - 4,294,967,295</td></tr></tbody></table>

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

```
struct StatsRadio {
 uint8_t response_code; // 0x18
 uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO)
 int16_t noise_floor;
 int8_t last_rssi;
 int8_t last_snr; // Divide by 4.0 to get actual SNR in dB
 uint32_t tx_air_secs;
 uint32_t rx_air_secs;
} __attribute__((packed));
```

\---

### RESP\_CODE\_STATS + STATS\_TYPE\_PACKETS (24, 2)

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

<table id="bkmrk-offsetsizetypefield--2"><thead><tr><th>Offset</th><th>Size</th><th>Type</th><th>Field Name</th><th>Description</th><th>Range/Notes</th></tr></thead><tbody><tr><td>0</td><td>1</td><td>uint8\_t</td><td>response\_code</td><td>Always `0x18` (24)</td><td>-</td></tr><tr><td>1</td><td>1</td><td>uint8\_t</td><td>stats\_type</td><td>Always `0x02` (STATS\_TYPE\_PACKETS)</td><td>-</td></tr><tr><td>2</td><td>4</td><td>uint32\_t</td><td>recv</td><td>Total packets received</td><td>0 - 4,294,967,295</td></tr><tr><td>6</td><td>4</td><td>uint32\_t</td><td>sent</td><td>Total packets sent</td><td>0 - 4,294,967,295</td></tr><tr><td>10</td><td>4</td><td>uint32\_t</td><td>flood\_tx</td><td>Packets sent via flood routing</td><td>0 - 4,294,967,295</td></tr><tr><td>14</td><td>4</td><td>uint32\_t</td><td>direct\_tx</td><td>Packets sent via direct routing</td><td>0 - 4,294,967,295</td></tr><tr><td>18</td><td>4</td><td>uint32\_t</td><td>flood\_rx</td><td>Packets received via flood routing</td><td>0 - 4,294,967,295</td></tr><tr><td>22</td><td>4</td><td>uint32\_t</td><td>direct\_rx</td><td>Packets received via direct routing</td><td>0 - 4,294,967,295</td></tr><tr><td>26</td><td>4</td><td>uint32\_t</td><td>recv\_errors</td><td>Receive/CRC errors (RadioLib); present only in 30-byte frame</td><td>0 - 4,294,967,295</td></tr></tbody></table>

#### 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++)

```
struct StatsPackets {
 uint8_t response_code; // 0x18
 uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS)
 uint32_t recv;
 uint32_t sent;
 uint32_t flood_tx;
 uint32_t direct_tx;
 uint32_t flood_rx;
 uint32_t direct_rx;
 uint32_t recv_errors; // present when frame size is 30
} __attribute__((packed));
```

\---

### Command Usage Example (Python)

```
# Send CMD_GET_STATS command
def send_get_stats_core(serial_interface):
 """Send command to get core stats"""
 cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0)
 serial_interface.write(cmd)

def send_get_stats_radio(serial_interface):
 """Send command to get radio stats"""
 cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1)
 serial_interface.write(cmd)

def send_get_stats_packets(serial_interface):
 """Send command to get packet stats"""
 cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2)
 serial_interface.write(cmd)
```

\---

### Response Parsing Example (Python)

```
import struct

def parse_stats_core(frame):
 """Parse RESP_CODE_STATS + STATS_TYPE_CORE frame (11 bytes)"""
 response_code, stats_type, battery_mv, uptime_secs, errors, queue_len = \
 struct.unpack('<B B H I H B', frame)
 assert response_code == 24 and stats_type == 0, "Invalid response type"
 return {
 'battery_mv': battery_mv,
 'uptime_secs': uptime_secs,
 'errors': errors,
 'queue_len': queue_len
 }

def parse_stats_radio(frame):
 """Parse RESP_CODE_STATS + STATS_TYPE_RADIO frame (14 bytes)"""
 response_code, stats_type, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \
 struct.unpack('<B B h b b I I', frame)
 assert response_code == 24 and stats_type == 1, "Invalid response type"
 return {
 'noise_floor': noise_floor,
 'last_rssi': last_rssi,
 'last_snr': last_snr / 4.0, # Unscale SNR
 'tx_air_secs': tx_air_secs,
 'rx_air_secs': rx_air_secs
 }

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])
 assert response_code == 24 and stats_type == 2, "Invalid response type"
 result = {
 'recv': recv,
 'sent': sent,
 'flood_tx': flood_tx,
 'direct_tx': direct_tx,
 '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
```

\---

### Command Usage Example (JavaScript/TypeScript)

```
// Send CMD_GET_STATS command
const CMD_GET_STATS = 56;
const STATS_TYPE_CORE = 0;
const STATS_TYPE_RADIO = 1;
const STATS_TYPE_PACKETS = 2;

function sendGetStatsCore(serialInterface: SerialPort): void {
 const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_CORE]);
 serialInterface.write(cmd);
}

function sendGetStatsRadio(serialInterface: SerialPort): void {
 const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_RADIO]);
 serialInterface.write(cmd);
}

function sendGetStatsPackets(serialInterface: SerialPort): void {
 const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_PACKETS]);
 serialInterface.write(cmd);
}
```

\---

### Response Parsing Example (JavaScript/TypeScript)

```
interface StatsCore {
 battery_mv: number;
 uptime_secs: number;
 errors: number;
 queue_len: number;
}

interface StatsRadio {
 noise_floor: number;
 last_rssi: number;
 last_snr: number;
 tx_air_secs: number;
 rx_air_secs: number;
}

interface StatsPackets {
 recv: number;
 sent: number;
 flood_tx: number;
 direct_tx: number;
 flood_rx: number;
 direct_rx: number;
 recv_errors?: number; // present when frame is 30 bytes
}

function parseStatsCore(buffer: ArrayBuffer): StatsCore {
 const view = new DataView(buffer);
 const response_code = view.getUint8(0);
 const stats_type = view.getUint8(1);
 if (response_code !== 24 || stats_type !== 0) {
 throw new Error('Invalid response type');
 }
 return {
 battery_mv: view.getUint16(2, true),
 uptime_secs: view.getUint32(4, true),
 errors: view.getUint16(8, true),
 queue_len: view.getUint8(10)
 };
}

function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
 const view = new DataView(buffer);
 const response_code = view.getUint8(0);
 const stats_type = view.getUint8(1);
 if (response_code !== 24 || stats_type !== 1) {
 throw new Error('Invalid response type');
 }
 return {
 noise_floor: view.getInt16(2, true),
 last_rssi: view.getInt8(4),
 last_snr: view.getInt8(5) / 4.0, // Unscale SNR
 tx_air_secs: view.getUint32(6, true),
 rx_air_secs: view.getUint32(10, true)
 };
}

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');
 }
 const result: StatsPackets = {
 recv: view.getUint32(2, true),
 sent: view.getUint32(6, true),
 flood_tx: view.getUint32(10, true),
 direct_tx: view.getUint32(14, true),
 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;
}
```

\---

### Field Size Considerations

- 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.