From 4a97dd0968a8c94bfe5ba046b06854f60c4729ee Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 17 Nov 2025 09:59:50 -0800 Subject: [PATCH] Modify statistics handling in MessageReader to support binary parsing for core, radio, and packet stats. Update DeviceCommands to send appropriate commands for each stats type. --- src/meshcore/commands/device.py | 9 ++- src/meshcore/reader.py | 113 +++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/meshcore/commands/device.py b/src/meshcore/commands/device.py index f04a6c1..152ec1d 100644 --- a/src/meshcore/commands/device.py +++ b/src/meshcore/commands/device.py @@ -208,12 +208,15 @@ class DeviceCommands(CommandHandlerBase): async def get_stats_core(self) -> Event: logger.debug("Getting core statistics") - return await self.send(b"\x38", [EventType.STATS_CORE, EventType.ERROR]) + # CMD_GET_STATS (56) + STATS_TYPE_CORE (0) + return await self.send(b"\x38\x00", [EventType.STATS_CORE, EventType.ERROR]) async def get_stats_radio(self) -> Event: logger.debug("Getting radio statistics") - return await self.send(b"\x39", [EventType.STATS_RADIO, EventType.ERROR]) + # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1) + return await self.send(b"\x38\x01", [EventType.STATS_RADIO, EventType.ERROR]) async def get_stats_packets(self) -> Event: logger.debug("Getting packet statistics") - return await self.send(b"\x3a", [EventType.STATS_PACKETS, EventType.ERROR]) + # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2) + return await self.send(b"\x38\x02", [EventType.STATS_PACKETS, EventType.ERROR]) diff --git a/src/meshcore/reader.py b/src/meshcore/reader.py index cfabcdb..2a64fb2 100644 --- a/src/meshcore/reader.py +++ b/src/meshcore/reader.py @@ -1,5 +1,6 @@ import logging import json +import struct import time import io from typing import Any, Dict @@ -285,38 +286,86 @@ class MessageReader: logger.debug(f"got custom vars : {res}") await self.dispatcher.dispatch(Event(EventType.CUSTOM_VARS, res)) - elif packet_type_value == PacketType.STATS_CORE.value: - logger.debug(f"received stats core response: {data.hex()}") - rawdata = dbuf.read().decode("utf-8", "ignore") - try: - res = json.loads(rawdata) - logger.debug(f"parsed stats core: {res}") - await self.dispatcher.dispatch(Event(EventType.STATS_CORE, res)) - except json.JSONDecodeError as e: - logger.error(f"Error parsing stats core JSON: {e}, raw data: {rawdata}") - await self.dispatcher.dispatch(Event(EventType.ERROR, {"reason": f"json_parse_error: {e}"})) - - elif packet_type_value == PacketType.STATS_RADIO.value: - logger.debug(f"received stats radio response: {data.hex()}") - rawdata = dbuf.read().decode("utf-8", "ignore") - try: - res = json.loads(rawdata) - logger.debug(f"parsed stats radio: {res}") - await self.dispatcher.dispatch(Event(EventType.STATS_RADIO, res)) - except json.JSONDecodeError as e: - logger.error(f"Error parsing stats radio JSON: {e}, raw data: {rawdata}") - await self.dispatcher.dispatch(Event(EventType.ERROR, {"reason": f"json_parse_error: {e}"})) - - elif packet_type_value == PacketType.STATS_PACKETS.value: - logger.debug(f"received stats packets response: {data.hex()}") - rawdata = dbuf.read().decode("utf-8", "ignore") - try: - res = json.loads(rawdata) - logger.debug(f"parsed stats packets: {res}") - await self.dispatcher.dispatch(Event(EventType.STATS_PACKETS, res)) - except json.JSONDecodeError as e: - logger.error(f"Error parsing stats packets JSON: {e}, raw data: {rawdata}") - await self.dispatcher.dispatch(Event(EventType.ERROR, {"reason": f"json_parse_error: {e}"})) + elif packet_type_value == PacketType.STATS_CORE.value: # RESP_CODE_STATS (24) + logger.debug(f"received stats response: {data.hex()}") + # RESP_CODE_STATS: All stats responses use code 24 with sub-type byte + # Byte 0: response_code (24), Byte 1: stats_type (0=core, 1=radio, 2=packets) + if len(data) < 2: + logger.error(f"Stats response too short: {len(data)} bytes, need at least 2 for header") + await self.dispatcher.dispatch(Event(EventType.ERROR, {"reason": "invalid_frame_length"})) + return + + stats_type = data[1] + + if stats_type == 0: # STATS_TYPE_CORE + # RESP_CODE_STATS + STATS_TYPE_CORE: 11 bytes total + # Format: