From 8400995600ad4858c964a7585d31750d7585ae2c Mon Sep 17 00:00:00 2001 From: Matthew Wolter Date: Sun, 12 Apr 2026 04:14:11 -0700 Subject: [PATCH] =?UTF-8?q?G6:=20N01+N02+N03=20=E2=80=94=20add=20reader=20?= =?UTF-8?q?branches=20for=20CONTACT=5FDELETED,=20CONTACTS=5FFULL,=20TUNING?= =?UTF-8?q?=5FPARAMS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: Three firmware push/response codes had no SDK handler — frames fell through to the "Unhandled" debug log. CONTACT_DELETED (0x8F) carries a 32-byte pubkey from onContactOverwrite(); CONTACTS_FULL (0x90) is a 1-byte push from onContactsFull(); TUNING_PARAMS (23) is the 9-byte response to CMD_GET_TUNING_PARAMS carrying rx_delay and airtime_factor. All three now dispatch typed events. Short frames are guarded. Refs: Forensics report findings N01, N02, N03 --- src/meshcore/events.py | 3 +++ src/meshcore/reader.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/meshcore/events.py b/src/meshcore/events.py index f8a7521..85821ef 100644 --- a/src/meshcore/events.py +++ b/src/meshcore/events.py @@ -49,6 +49,9 @@ class EventType(Enum): PATH_RESPONSE = "path_response" PRIVATE_KEY = "private_key" DISABLED = "disabled" + CONTACT_DELETED = "contact_deleted" + CONTACTS_FULL = "contacts_full" + TUNING_PARAMS = "tuning_params" CONTROL_DATA = "control_data" DISCOVER_RESPONSE = "discover_response" NEIGHBOURS_RESPONSE = "neighbours_response" diff --git a/src/meshcore/reader.py b/src/meshcore/reader.py index 802004a..bfadb68 100644 --- a/src/meshcore/reader.py +++ b/src/meshcore/reader.py @@ -916,6 +916,37 @@ class MessageReader: Event(EventType.DISCOVER_RESPONSE, ndr, attributes) ) + elif packet_type_value == PacketType.CONTACT_DELETED.value: + # N01: PUSH_CODE_CONTACT_DELETED (0x8F) — 1-byte code + 32-byte pubkey + # Emitted by MyMesh::onContactOverwrite() (MyMesh.cpp:325-334) + if len(data) < 33: + logger.debug("CONTACT_DELETED frame too short (%d bytes, need 33)", len(data)) + return + pubkey = data[1:33].hex() + await self.dispatcher.dispatch( + Event(EventType.CONTACT_DELETED, {"pubkey": pubkey}, {"pubkey": pubkey}) + ) + + elif packet_type_value == PacketType.CONTACTS_FULL.value: + # N02: PUSH_CODE_CONTACTS_FULL (0x90) — 1-byte push, no payload + # Emitted by MyMesh::onContactsFull() (MyMesh.cpp:336) + await self.dispatcher.dispatch(Event(EventType.CONTACTS_FULL, {})) + + elif packet_type_value == PacketType.TUNING_PARAMS.value: + # N03: RESP_CODE_TUNING_PARAMS (23) — response to CMD_GET_TUNING_PARAMS (43) + # Format: 1-byte code + 4-byte rx_delay (LE) + 4-byte airtime_factor (LE) = 9 bytes + # Emitted by MyMesh.cpp:1307-1313 + if len(data) < 9: + logger.debug("TUNING_PARAMS frame too short (%d bytes, need 9)", len(data)) + await self.dispatcher.dispatch( + Event(EventType.ERROR, {"reason": "invalid_frame_length"}) + ) + return + rx_delay = int.from_bytes(data[1:5], byteorder="little") + airtime_factor = int.from_bytes(data[5:9], byteorder="little") + res = {"rx_delay": rx_delay, "airtime_factor": airtime_factor} + await self.dispatcher.dispatch(Event(EventType.TUNING_PARAMS, res)) + else: logger.debug(f"Unhandled data received {data}") logger.debug(f"Unhandled packet type: {packet_type_value}")