From d3c9c8d9848e9be95b3e5e3120a799208abf24e5 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 6 Nov 2025 22:32:53 +0100 Subject: [PATCH] control codes support: node_discover_req --- src/meshcore/commands/__init__.py | 7 ++++- src/meshcore/commands/control_data.py | 45 +++++++++++++++++++++++++++ src/meshcore/events.py | 2 ++ src/meshcore/packets.py | 6 ++++ src/meshcore/reader.py | 43 +++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 src/meshcore/commands/control_data.py diff --git a/src/meshcore/commands/__init__.py b/src/meshcore/commands/__init__.py index 0638847..9a37ab4 100644 --- a/src/meshcore/commands/__init__.py +++ b/src/meshcore/commands/__init__.py @@ -7,10 +7,15 @@ from .binary import BinaryCommandHandler from .contact import ContactCommands from .device import DeviceCommands from .messaging import MessagingCommands +from .control_data import ControlDataCommandHandler class CommandHandler( - DeviceCommands, ContactCommands, MessagingCommands, BinaryCommandHandler + DeviceCommands, + ContactCommands, + MessagingCommands, + BinaryCommandHandler, + ControlDataCommandHandler ): pass diff --git a/src/meshcore/commands/control_data.py b/src/meshcore/commands/control_data.py new file mode 100644 index 0000000..b63ed57 --- /dev/null +++ b/src/meshcore/commands/control_data.py @@ -0,0 +1,45 @@ +import logging +import random + +from .base import CommandHandlerBase +from ..events import EventType, Event +from ..packets import ControlType, PacketType + +logger = logging.getLogger("meshcore") + +class ControlDataCommandHandler(CommandHandlerBase): + """Helper functions to handle binary requests through binary commands""" + + async def send_control_data (self, control_type: ControlType, payload: bytes) -> Event: + data = bytearray([PacketType.SEND_CONTROL_DATA.value]) + data.extend(control_type.value.to_bytes(1, "little", signed = False)) + data.extend(payload) + + result = await self.send(data, [EventType.OK, EventType.ERROR]) + return result + + async def send_node_discover_req ( + self, + filter: int, + tag: int=None, + since: int=None + ) -> Event: + + if tag is None: + tag = random.randint(1, 0xFFFFFFFF) + + data = bytearray() + data.extend(filter.to_bytes(1, "little", signed=False)) + data.extend(tag.to_bytes(4, "little")) + if not since is None: + data.extend(since.to_bytes(4, "little", signed=False)) + + logger.debug(f"sending node discover req {data.hex()}") + + res = await self.send_control_data(ControlType.NODE_DISCOVER_REQ, data) + + if res is None: + return None + else: + res.payload["tag"] = tag + return res diff --git a/src/meshcore/events.py b/src/meshcore/events.py index 913430b..fb889c0 100644 --- a/src/meshcore/events.py +++ b/src/meshcore/events.py @@ -44,6 +44,8 @@ class EventType(Enum): PATH_RESPONSE = "path_response" PRIVATE_KEY = "private_key" DISABLED = "disabled" + CONTROL_DATA = "control_data" + DISCOVER_RESPONSE = "discover_response" # Command response types OK = "command_ok" diff --git a/src/meshcore/packets.py b/src/meshcore/packets.py index 9ca0809..62f45ee 100644 --- a/src/meshcore/packets.py +++ b/src/meshcore/packets.py @@ -7,6 +7,10 @@ class BinaryReqType(Enum): MMA = 0x04 ACL = 0x05 +class ControlType(Enum): + NODE_DISCOVER_REQ = 0x80 + NODE_DISCOVER_RESP = 0x90 + # Packet prefixes for the protocol class PacketType(Enum): OK = 0 @@ -35,6 +39,7 @@ class PacketType(Enum): FACTORY_RESET = 51 PATH_DISCOVERY = 52 SET_FLOOD_SCOPE = 54 + SEND_CONTROL_DATA = 55 # Push notifications ADVERTISEMENT = 0x80 @@ -51,3 +56,4 @@ class PacketType(Enum): TELEMETRY_RESPONSE = 0x8B BINARY_RESPONSE = 0x8C PATH_DISCOVERY_RESPONSE = 0x8D + CONTROL_DATA = 0x8E diff --git a/src/meshcore/reader.py b/src/meshcore/reader.py index 75907f9..ed0a8d5 100644 --- a/src/meshcore/reader.py +++ b/src/meshcore/reader.py @@ -4,7 +4,7 @@ import time import io from typing import Any, Dict from .events import Event, EventType, EventDispatcher -from .packets import BinaryReqType, PacketType +from .packets import BinaryReqType, PacketType, ControlType from .parsing import lpp_parse, lpp_parse_mma, parse_acl, parse_status from cayennelpp import LppFrame, LppData from meshcore.lpp_json_encoder import lpp_json_encoder @@ -331,8 +331,8 @@ class MessageReader: elif packet_type_value == PacketType.RAW_DATA.value: res = {} - res["SNR"] = dbuf.read(1)[0] / 4 - res["RSSI"] = dbuf.read(1)[0] + res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4 + res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) res["payload"] = dbuf.read(4).hex() logger.debug("Received raw data") print(res) @@ -593,6 +593,43 @@ class MessageReader: res = {"reason": "private_key_export_disabled"} await self.dispatcher.dispatch(Event(EventType.DISABLED, res)) + elif packet_type_value == PacketType.CONTROL_DATA.value: + logger.debug("Received control data packet") + res={} + res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4 + res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) + res["path_len"] = dbuf.read(1)[0] + payload = dbuf.read() + payload_type = payload[0] + res["payload_type"] = payload_type + res["payload"] = payload + + attributes = {"payload_type": payload_type} + await self.dispatcher.dispatch( + Event(EventType.CONTROL_DATA, res, attributes) + ) + + # decode NODE_DISCOVER_RESP + if payload_type & 0xF0 == ControlType.NODE_DISCOVER_RESP.value: + pbuf = io.BytesIO(payload[1:]) + ndr = dict(res) + del ndr["payload_type"] + del ndr["payload"] + ndr["node_type"] = payload_type & 0x0F + ndr["SNR_in"] = int.from_bytes(pbuf.read(1), byteorder="little", signed=True)/4 + ndr["tag"] = pbuf.read(4).hex() + ndr["pubkey"] = pbuf.read(32).hex() + + attributes = { + "node_type" : ndr["node_type"], + "tag" : ndr["tag"], + "pubkey" : ndr["pubkey"], + } + + await self.dispatcher.dispatch( + Event(EventType.DISCOVER_RESPONSE, ndr, attributes) + ) + else: logger.debug(f"Unhandled data received {data}") logger.debug(f"Unhandled packet type: {packet_type_value}")