소스 검색

Modify CMD_GET_STATS with sub-types for core, radio, and packet statistics. Consolidated to a single RESP_CODE_STATS with a second byte to identify response structure. Updated documentation and examples to reflect the new command structure and response parsing.

agessaman 8 달 전
부모
커밋
a3c9a07377
2개의 변경된 파일200개의 추가작업 그리고 104개의 파일을 삭제
  1. 144 57
      docs/stats_binary_frames.md
  2. 56 47
      examples/companion_radio/MyMesh.cpp

+ 144 - 57
docs/stats_binary_frames.md

@@ -6,37 +6,53 @@ Binary frame structures for companion radio stats commands. All multi-byte integ
 
 | Command | Code | Description |
 |---------|------|-------------|
-| `CMD_GET_STATS_CORE` | 56 | Get core device statistics |
-| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics |
-| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics |
+| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) |
+
+### 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
 
 | Response | Code | Description |
 |----------|------|-------------|
-| `RESP_CODE_STATS_CORE` | 24 | Core stats response |
-| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response |
-| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response |
+| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) |
+
+### 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_CORE (24)
+## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0)
 
-**Total Frame Size:** 10 bytes
+**Total Frame Size:** 11 bytes
 
 | Offset | Size | Type | Field Name | Description | Range/Notes |
 |--------|------|------|------------|-------------|-------------|
 | 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
-| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 |
-| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 |
-| 7 | 2 | uint16_t | errors | Error flags bitmask | - |
-| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 |
+| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - |
+| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 |
+| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 |
+| 8 | 2 | uint16_t | errors | Error flags bitmask | - |
+| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 |
 
 ### Example Structure (C/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;
@@ -46,24 +62,26 @@ struct StatsCore {
 
 ---
 
-## RESP_CODE_STATS_RADIO (25)
+## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1)
 
-**Total Frame Size:** 13 bytes
+**Total Frame Size:** 14 bytes
 
 | Offset | Size | Type | Field Name | Description | Range/Notes |
 |--------|------|------|------------|-------------|-------------|
-| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - |
-| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 |
-| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 |
-| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB |
-| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 |
-| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 |
+| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
+| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - |
+| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 |
+| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 |
+| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB |
+| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 |
+| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 |
 
 ### Example Structure (C/C++)
 
 ```c
 struct StatsRadio {
-    uint8_t  response_code;  // 0x19
+    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
@@ -74,19 +92,20 @@ struct StatsRadio {
 
 ---
 
-## RESP_CODE_STATS_PACKETS (26)
+## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)
 
-**Total Frame Size:** 25 bytes
+**Total Frame Size:** 26 bytes
 
 | Offset | Size | Type | Field Name | Description | Range/Notes |
 |--------|------|------|------------|-------------|-------------|
-| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - |
-| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 |
-| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 |
-| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 |
-| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
-| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
-| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
+| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
+| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - |
+| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 |
+| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 |
+| 10 | 4 | uint32_t | flood_tx | Packets sent via flood 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 |
+| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
 
 ### Notes
 
@@ -98,7 +117,8 @@ struct StatsRadio {
 
 ```c
 struct StatsPackets {
-    uint8_t  response_code;  // 0x1A
+    uint8_t  response_code;  // 0x18
+    uint8_t  stats_type;     // 0x02 (STATS_TYPE_PACKETS)
     uint32_t recv;
     uint32_t sent;
     uint32_t flood_tx;
@@ -110,15 +130,38 @@ struct StatsPackets {
 
 ---
 
-## Usage Example (Python)
+## Command Usage Example (Python)
+
+```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)
 
 ```python
 import struct
 
 def parse_stats_core(frame):
-    """Parse RESP_CODE_STATS_CORE frame (10 bytes)"""
-    response_code, battery_mv, uptime_secs, errors, queue_len = \
-        struct.unpack('<B H I H B', 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,
@@ -127,9 +170,10 @@ def parse_stats_core(frame):
     }
 
 def parse_stats_radio(frame):
-    """Parse RESP_CODE_STATS_RADIO frame (13 bytes)"""
-    response_code, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \
-        struct.unpack('<B h b b I I', 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,
@@ -139,9 +183,10 @@ def parse_stats_radio(frame):
     }
 
 def parse_stats_packets(frame):
-    """Parse RESP_CODE_STATS_PACKETS frame (25 bytes)"""
-    response_code, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
-        struct.unpack('<B I I I I I I', frame)
+    """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)"""
+    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)
+    assert response_code == 24 and stats_type == 2, "Invalid response type"
     return {
         'recv': recv,
         'sent': sent,
@@ -154,7 +199,34 @@ def parse_stats_packets(frame):
 
 ---
 
-## Usage Example (JavaScript/TypeScript)
+## Command Usage Example (JavaScript/TypeScript)
+
+```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)
 
 ```typescript
 interface StatsCore {
@@ -183,34 +255,49 @@ interface StatsPackets {
 
 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(1, true),
-        uptime_secs: view.getUint32(3, true),
-        errors: view.getUint16(7, true),
-        queue_len: view.getUint8(9)
+        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(1, true),
-        last_rssi: view.getInt8(3),
-        last_snr: view.getInt8(4) / 4.0,  // Unscale SNR
-        tx_air_secs: view.getUint32(5, true),
-        rx_air_secs: view.getUint32(9, true)
+        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);
+    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 {
-        recv: view.getUint32(1, true),
-        sent: view.getUint32(5, true),
-        flood_tx: view.getUint32(9, true),
-        direct_tx: view.getUint32(13, true),
-        flood_rx: view.getUint32(17, true),
-        direct_rx: view.getUint32(21, true)
+        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)
     };
 }
 ```

+ 56 - 47
examples/companion_radio/MyMesh.cpp

@@ -52,9 +52,12 @@
 #define CMD_SEND_PATH_DISCOVERY_REQ   52
 #define CMD_SET_FLOOD_SCOPE           54   // v8+
 #define CMD_SEND_CONTROL_DATA         55   // v8+
-#define CMD_GET_STATS_CORE            56
-#define CMD_GET_STATS_RADIO           57
-#define CMD_GET_STATS_PACKETS         58
+#define CMD_GET_STATS                 56   // v8+, second byte is stats type
+
+// Stats sub-types for CMD_GET_STATS
+#define STATS_TYPE_CORE               0
+#define STATS_TYPE_RADIO              1
+#define STATS_TYPE_PACKETS             2
 
 #define RESP_CODE_OK                  0
 #define RESP_CODE_ERR                 1
@@ -80,9 +83,7 @@
 #define RESP_CODE_CUSTOM_VARS         21
 #define RESP_CODE_ADVERT_PATH         22
 #define RESP_CODE_TUNING_PARAMS       23
-#define RESP_CODE_STATS_CORE          24
-#define RESP_CODE_STATS_RADIO         25
-#define RESP_CODE_STATS_PACKETS       26
+#define RESP_CODE_STATS               24   // v8+, second byte is stats type
 
 #define SEND_TIMEOUT_BASE_MILLIS        500
 #define FLOOD_SEND_TIMEOUT_FACTOR       16.0f
@@ -1535,47 +1536,55 @@ void MyMesh::handleCmdFrame(size_t len) {
     } else {
       writeErrFrame(ERR_CODE_NOT_FOUND);
     }
-  } else if (cmd_frame[0] == CMD_GET_STATS_CORE) {
-    int i = 0;
-    out_frame[i++] = RESP_CODE_STATS_CORE;
-    uint16_t battery_mv = board.getBattMilliVolts();
-    uint32_t uptime_secs = _ms->getMillis() / 1000;
-    uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF);
-    memcpy(&out_frame[i], &battery_mv, 2); i += 2;
-    memcpy(&out_frame[i], &uptime_secs, 4); i += 4;
-    memcpy(&out_frame[i], &_err_flags, 2); i += 2;
-    out_frame[i++] = queue_len;
-    _serial->writeFrame(out_frame, i);
-  } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) {
-    int i = 0;
-    out_frame[i++] = RESP_CODE_STATS_RADIO;
-    int16_t noise_floor = (int16_t)_radio->getNoiseFloor();
-    int8_t last_rssi = (int8_t)radio_driver.getLastRSSI();
-    int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision
-    uint32_t tx_air_secs = getTotalAirTime() / 1000;
-    uint32_t rx_air_secs = getReceiveAirTime() / 1000;
-    memcpy(&out_frame[i], &noise_floor, 2); i += 2;
-    out_frame[i++] = last_rssi;
-    out_frame[i++] = last_snr;
-    memcpy(&out_frame[i], &tx_air_secs, 4); i += 4;
-    memcpy(&out_frame[i], &rx_air_secs, 4); i += 4;
-    _serial->writeFrame(out_frame, i);
-  } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) {
-    int i = 0;
-    out_frame[i++] = RESP_CODE_STATS_PACKETS;
-    uint32_t recv = radio_driver.getPacketsRecv();
-    uint32_t sent = radio_driver.getPacketsSent();
-    uint32_t n_sent_flood = getNumSentFlood();
-    uint32_t n_sent_direct = getNumSentDirect();
-    uint32_t n_recv_flood = getNumRecvFlood();
-    uint32_t n_recv_direct = getNumRecvDirect();
-    memcpy(&out_frame[i], &recv, 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_direct, 4); i += 4;
-    memcpy(&out_frame[i], &n_recv_flood, 4); i += 4;
-    memcpy(&out_frame[i], &n_recv_direct, 4); i += 4;
-    _serial->writeFrame(out_frame, i);
+  } else if (cmd_frame[0] == CMD_GET_STATS && len >= 2) {
+    uint8_t stats_type = cmd_frame[1];
+    if (stats_type == STATS_TYPE_CORE) {
+      int i = 0;
+      out_frame[i++] = RESP_CODE_STATS;
+      out_frame[i++] = STATS_TYPE_CORE;
+      uint16_t battery_mv = board.getBattMilliVolts();
+      uint32_t uptime_secs = _ms->getMillis() / 1000;
+      uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF);
+      memcpy(&out_frame[i], &battery_mv, 2); i += 2;
+      memcpy(&out_frame[i], &uptime_secs, 4); i += 4;
+      memcpy(&out_frame[i], &_err_flags, 2); i += 2;
+      out_frame[i++] = queue_len;
+      _serial->writeFrame(out_frame, i);
+    } else if (stats_type == STATS_TYPE_RADIO) {
+      int i = 0;
+      out_frame[i++] = RESP_CODE_STATS;
+      out_frame[i++] = STATS_TYPE_RADIO;
+      int16_t noise_floor = (int16_t)_radio->getNoiseFloor();
+      int8_t last_rssi = (int8_t)radio_driver.getLastRSSI();
+      int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision
+      uint32_t tx_air_secs = getTotalAirTime() / 1000;
+      uint32_t rx_air_secs = getReceiveAirTime() / 1000;
+      memcpy(&out_frame[i], &noise_floor, 2); i += 2;
+      out_frame[i++] = last_rssi;
+      out_frame[i++] = last_snr;
+      memcpy(&out_frame[i], &tx_air_secs, 4); i += 4;
+      memcpy(&out_frame[i], &rx_air_secs, 4); i += 4;
+      _serial->writeFrame(out_frame, i);
+    } else if (stats_type == STATS_TYPE_PACKETS) {
+      int i = 0;
+      out_frame[i++] = RESP_CODE_STATS;
+      out_frame[i++] = STATS_TYPE_PACKETS;
+      uint32_t recv = radio_driver.getPacketsRecv();
+      uint32_t sent = radio_driver.getPacketsSent();
+      uint32_t n_sent_flood = getNumSentFlood();
+      uint32_t n_sent_direct = getNumSentDirect();
+      uint32_t n_recv_flood = getNumRecvFlood();
+      uint32_t n_recv_direct = getNumRecvDirect();
+      memcpy(&out_frame[i], &recv, 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_direct, 4); i += 4;
+      memcpy(&out_frame[i], &n_recv_flood, 4); i += 4;
+      memcpy(&out_frame[i], &n_recv_direct, 4); i += 4;
+      _serial->writeFrame(out_frame, i);
+    } else {
+      writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type
+    }
   } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) {
     bool success = _store->formatFileSystem();
     if (success) {