vnode 0.1.6__tar.gz → 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vnode
3
- Version: 0.1.6
3
+ Version: 0.1.9
4
4
  Summary: Virtual Meshtastic node using mudp transport and meshdb storage.
5
5
  License-Expression: GPL-3.0-only
6
6
  License-File: LICENSE
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.14
17
17
  Requires-Dist: cryptography (>=43,<45)
18
18
  Requires-Dist: meshdb (>=0.1.9)
19
19
  Requires-Dist: meshtastic (>=2.7,<3.0)
20
- Requires-Dist: mudp (>=1.1.0)
20
+ Requires-Dist: mudp (>=1.1.5)
21
21
  Project-URL: Homepage, https://github.com/pdxlocations/vnode
22
22
  Project-URL: Issues, https://github.com/pdxlocations/vnode/issues
23
23
  Description-Content-Type: text/markdown
@@ -30,6 +30,20 @@ Virtual Meshtastic node runtime built around:
30
30
  - `meshdb` for packet and node persistence
31
31
  - Meshtastic-style PKI DM encryption using X25519 + SHA-256 + AES-CCM
32
32
 
33
+ ## Install
34
+
35
+ Install from PyPI:
36
+
37
+ ```bash
38
+ pip install vnode
39
+ ```
40
+
41
+ If you are working from this repository instead:
42
+
43
+ ```bash
44
+ .venv/bin/pip install -e .
45
+ ```
46
+
33
47
  ## Config
34
48
 
35
49
  Edit [node.json](node.json).
@@ -58,7 +72,6 @@ broadcasts.
58
72
  ## Run
59
73
 
60
74
  ```bash
61
- .venv/bin/pip install -e .
62
75
  .venv/bin/python -m vnode --vnode-file node.json run
63
76
  ```
64
77
 
@@ -84,6 +97,19 @@ node = VirtualNode("node.json")
84
97
  packet_id = node.send_text("!1234abcd", "hello")
85
98
  ```
86
99
 
100
+ Supported `VirtualNode` calls:
101
+
102
+ - Lifecycle: `start()`, `stop()`, `close()`, `run_forever()`
103
+ - Subscription: `receive(callback)`, `unreceive(callback)`
104
+ - Node identity: `getLongName()`, `getShortName()`, `getPublicKey()`, `getMyUser()`, `getMyNodeInfo()`
105
+ - Native vnode sends: `send_text()`, `send_reply()`, `send_nodeinfo()`, `send_position()`
106
+ - MeshInterface-compatible sends: `sendText()`, `sendAlert()`, `sendData()`, `sendPosition()`
107
+ - Packet helpers: `is_direct_message_for_me()`, `is_text_message()`, `get_text_message()`, `reply_to_packet()`
108
+
109
+ `receive(callback)` is deduplicated. Repeated multicast copies stay on `mudp`'s wire-level
110
+ topics, while `vnode` publishes `meshtastic.receive` from `mesh.rx.unique_packet` so
111
+ application callbacks run once per logical packet.
112
+
87
113
  ## Examples
88
114
 
89
115
  Use these as small runnable references for common tasks:
@@ -91,15 +117,17 @@ Use these as small runnable references for common tasks:
91
117
  - `examples/autoresponder.py`: DM-only reply bot for inbound direct text messages
92
118
  - `examples/basic_subscriptions.py`: minimal `node.receive()` subscription example
93
119
  - `examples/listen_packets.py`: packet logger for multicast traffic and decoded text
120
+ - `examples/mesh_interface_compat.py`: examples of MeshInterface-style compatibility calls on `VirtualNode`
94
121
  - `examples/serial_or_vnode.py`: try a serial Meshtastic node first, then fall back to `VirtualNode`
95
122
  - `examples/send_dm.py`: minimal one-shot direct-message sender
96
123
  - `examples/library_embed.py`: minimal application-style embedding example using `VirtualNode` directly
97
- - `examples/watch_reliability.py`: watcher for ACK, NAK, retry, and retransmit-failure events
124
+ - `examples/watch_reliability.py`: watcher for unique ACK, NAK, retry, and retransmit-failure events
98
125
 
99
126
  ```bash
100
127
  .venv/bin/python examples/autoresponder.py
101
128
  .venv/bin/python examples/basic_subscriptions.py
102
129
  .venv/bin/python examples/listen_packets.py
130
+ .venv/bin/python examples/mesh_interface_compat.py
103
131
  .venv/bin/python examples/serial_or_vnode.py
104
132
  .venv/bin/python examples/send_dm.py --to '!1234abcd' --message 'hello'
105
133
  .venv/bin/python examples/library_embed.py
@@ -6,6 +6,20 @@ Virtual Meshtastic node runtime built around:
6
6
  - `meshdb` for packet and node persistence
7
7
  - Meshtastic-style PKI DM encryption using X25519 + SHA-256 + AES-CCM
8
8
 
9
+ ## Install
10
+
11
+ Install from PyPI:
12
+
13
+ ```bash
14
+ pip install vnode
15
+ ```
16
+
17
+ If you are working from this repository instead:
18
+
19
+ ```bash
20
+ .venv/bin/pip install -e .
21
+ ```
22
+
9
23
  ## Config
10
24
 
11
25
  Edit [node.json](node.json).
@@ -34,7 +48,6 @@ broadcasts.
34
48
  ## Run
35
49
 
36
50
  ```bash
37
- .venv/bin/pip install -e .
38
51
  .venv/bin/python -m vnode --vnode-file node.json run
39
52
  ```
40
53
 
@@ -60,6 +73,19 @@ node = VirtualNode("node.json")
60
73
  packet_id = node.send_text("!1234abcd", "hello")
61
74
  ```
62
75
 
76
+ Supported `VirtualNode` calls:
77
+
78
+ - Lifecycle: `start()`, `stop()`, `close()`, `run_forever()`
79
+ - Subscription: `receive(callback)`, `unreceive(callback)`
80
+ - Node identity: `getLongName()`, `getShortName()`, `getPublicKey()`, `getMyUser()`, `getMyNodeInfo()`
81
+ - Native vnode sends: `send_text()`, `send_reply()`, `send_nodeinfo()`, `send_position()`
82
+ - MeshInterface-compatible sends: `sendText()`, `sendAlert()`, `sendData()`, `sendPosition()`
83
+ - Packet helpers: `is_direct_message_for_me()`, `is_text_message()`, `get_text_message()`, `reply_to_packet()`
84
+
85
+ `receive(callback)` is deduplicated. Repeated multicast copies stay on `mudp`'s wire-level
86
+ topics, while `vnode` publishes `meshtastic.receive` from `mesh.rx.unique_packet` so
87
+ application callbacks run once per logical packet.
88
+
63
89
  ## Examples
64
90
 
65
91
  Use these as small runnable references for common tasks:
@@ -67,15 +93,17 @@ Use these as small runnable references for common tasks:
67
93
  - `examples/autoresponder.py`: DM-only reply bot for inbound direct text messages
68
94
  - `examples/basic_subscriptions.py`: minimal `node.receive()` subscription example
69
95
  - `examples/listen_packets.py`: packet logger for multicast traffic and decoded text
96
+ - `examples/mesh_interface_compat.py`: examples of MeshInterface-style compatibility calls on `VirtualNode`
70
97
  - `examples/serial_or_vnode.py`: try a serial Meshtastic node first, then fall back to `VirtualNode`
71
98
  - `examples/send_dm.py`: minimal one-shot direct-message sender
72
99
  - `examples/library_embed.py`: minimal application-style embedding example using `VirtualNode` directly
73
- - `examples/watch_reliability.py`: watcher for ACK, NAK, retry, and retransmit-failure events
100
+ - `examples/watch_reliability.py`: watcher for unique ACK, NAK, retry, and retransmit-failure events
74
101
 
75
102
  ```bash
76
103
  .venv/bin/python examples/autoresponder.py
77
104
  .venv/bin/python examples/basic_subscriptions.py
78
105
  .venv/bin/python examples/listen_packets.py
106
+ .venv/bin/python examples/mesh_interface_compat.py
79
107
  .venv/bin/python examples/serial_or_vnode.py
80
108
  .venv/bin/python examples/send_dm.py --to '!1234abcd' --message 'hello'
81
109
  .venv/bin/python examples/library_embed.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vnode"
3
- version = "0.1.6"
3
+ version = "0.1.9"
4
4
  description = "Virtual Meshtastic node using mudp transport and meshdb storage."
5
5
  authors = [
6
6
  {name = "Ben Lipsey",email = "ben@pdxlocations.com"}
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "cryptography>=43,<45",
13
13
  "meshdb>=0.1.9",
14
14
  "meshtastic>=2.7,<3.0",
15
- "mudp>=1.1.0",
15
+ "mudp>=1.1.5",
16
16
  ]
17
17
 
18
18
  [project.urls]
@@ -7,9 +7,10 @@ import time
7
7
  from pathlib import Path
8
8
  from typing import Any, Dict, Optional, Union
9
9
 
10
+ from google.protobuf.json_format import MessageToDict
10
11
  import meshdb
11
12
  from google.protobuf.message import DecodeError
12
- from meshtastic import BROADCAST_NUM, config_pb2, mesh_pb2, portnums_pb2
13
+ from meshtastic import BROADCAST_ADDR, BROADCAST_NUM, LOCAL_ADDR, config_pb2, mesh_pb2, portnums_pb2, protocols
13
14
  from mudp import UDPPacketStream
14
15
  from mudp.encryption import encrypt_packet as mudp_encrypt_packet
15
16
  from mudp.reliability import is_ack, is_nak, publish_ack, register_pending_ack, send_ack
@@ -62,7 +63,10 @@ def resolve_role(value: Union[str, int]) -> int:
62
63
 
63
64
 
64
65
  class VirtualNode:
66
+ RAW_PACKET_TOPIC = "mesh.rx.packet"
65
67
  PACKET_TOPIC = "mesh.rx.unique_packet"
68
+ ACK_TOPIC = "mesh.rx.ack"
69
+ NAK_TOPIC = "mesh.rx.nak"
66
70
  DUPLICATE_TOPIC = "mesh.rx.duplicate"
67
71
  RECEIVE_TOPIC = "meshtastic.receive"
68
72
 
@@ -82,6 +86,7 @@ class VirtualNode:
82
86
  self._last_nodeinfo_sent_monotonic = 0.0
83
87
  self._last_position_reply_monotonic = 0.0
84
88
  self._last_nodeinfo_seen: Dict[int, int] = {}
89
+ self._response_handlers: Dict[int, Dict[str, Any]] = {}
85
90
 
86
91
  self._ensure_security_keys()
87
92
  self._write_public_key_file()
@@ -140,8 +145,11 @@ class VirtualNode:
140
145
  def start(self) -> None:
141
146
  if self.stream is not None:
142
147
  return
143
- pub.subscribe(self._handle_raw_packet, "mesh.rx.packet")
148
+ pub.subscribe(self._handle_raw_packet, self.RAW_PACKET_TOPIC)
144
149
  pub.subscribe(self._handle_unique_packet, self.PACKET_TOPIC)
150
+ pub.subscribe(self._handle_compat_response_packet, self.RECEIVE_TOPIC)
151
+ pub.subscribe(self._handle_compat_ack, self.ACK_TOPIC)
152
+ pub.subscribe(self._handle_compat_nak, self.NAK_TOPIC)
145
153
  self.stream = UDPPacketStream(
146
154
  self.config.udp.mcast_group,
147
155
  int(self.config.udp.mcast_port),
@@ -164,13 +172,22 @@ class VirtualNode:
164
172
  self.stream.stop()
165
173
  self.stream = None
166
174
  try:
167
- pub.unsubscribe(self._handle_raw_packet, "mesh.rx.packet")
175
+ pub.unsubscribe(self._handle_raw_packet, self.RAW_PACKET_TOPIC)
168
176
  except KeyError:
169
177
  pass
170
178
  try:
171
179
  pub.unsubscribe(self._handle_unique_packet, self.PACKET_TOPIC)
172
180
  except KeyError:
173
181
  pass
182
+ for topic, listener in (
183
+ (self.RECEIVE_TOPIC, self._handle_compat_response_packet),
184
+ (self.ACK_TOPIC, self._handle_compat_ack),
185
+ (self.NAK_TOPIC, self._handle_compat_nak),
186
+ ):
187
+ try:
188
+ pub.unsubscribe(listener, topic)
189
+ except KeyError:
190
+ pass
174
191
  if self._broadcast_thread and self._broadcast_thread.is_alive():
175
192
  self._broadcast_thread.join(timeout=2.0)
176
193
 
@@ -191,6 +208,161 @@ class VirtualNode:
191
208
  except KeyError:
192
209
  pass
193
210
 
211
+ def close(self) -> None:
212
+ self.stop()
213
+
214
+ def getLongName(self):
215
+ return self.config.long_name
216
+
217
+ def getShortName(self):
218
+ return self.config.short_name
219
+
220
+ def getPublicKey(self):
221
+ return b64_decode(self._public_key_b64) if self._public_key_b64 else None
222
+
223
+ def getMyUser(self):
224
+ user = mesh_pb2.User(
225
+ id=self.config.node_id,
226
+ long_name=self.config.long_name,
227
+ short_name=self.config.short_name,
228
+ hw_model=resolve_hw_model(self.config.hw_model),
229
+ )
230
+ user.role = resolve_role(self.config.role)
231
+ if self._public_key_b64:
232
+ user.public_key = b64_decode(self._public_key_b64)
233
+ return MessageToDict(user, preserving_proto_field_name=False, use_integers_for_enums=False)
234
+
235
+ def getMyNodeInfo(self):
236
+ return {
237
+ "num": self.node_num,
238
+ "user": self.getMyUser(),
239
+ }
240
+
241
+ def sendText(
242
+ self,
243
+ text: str,
244
+ destinationId: Union[int, str] = BROADCAST_ADDR,
245
+ wantAck: bool = False,
246
+ wantResponse: bool = False,
247
+ onResponse=None,
248
+ channelIndex: int = 0,
249
+ portNum: int = portnums_pb2.PortNum.TEXT_MESSAGE_APP,
250
+ replyId: Optional[int] = None,
251
+ hopLimit: Optional[int] = None,
252
+ ):
253
+ return self.sendData(
254
+ text.encode("utf-8"),
255
+ destinationId=destinationId,
256
+ portNum=portNum,
257
+ wantAck=wantAck,
258
+ wantResponse=wantResponse,
259
+ onResponse=onResponse,
260
+ channelIndex=channelIndex,
261
+ hopLimit=hopLimit,
262
+ replyId=replyId,
263
+ )
264
+
265
+ def sendAlert(
266
+ self,
267
+ text: str,
268
+ destinationId: Union[int, str] = BROADCAST_ADDR,
269
+ onResponse=None,
270
+ channelIndex: int = 0,
271
+ hopLimit: Optional[int] = None,
272
+ ):
273
+ return self.sendData(
274
+ text.encode("utf-8"),
275
+ destinationId=destinationId,
276
+ portNum=portnums_pb2.PortNum.ALERT_APP,
277
+ wantAck=False,
278
+ wantResponse=False,
279
+ onResponse=onResponse,
280
+ channelIndex=channelIndex,
281
+ hopLimit=hopLimit,
282
+ priority=mesh_pb2.MeshPacket.Priority.ALERT,
283
+ )
284
+
285
+ def sendData(
286
+ self,
287
+ data,
288
+ destinationId: Union[int, str] = BROADCAST_ADDR,
289
+ portNum: int = portnums_pb2.PortNum.PRIVATE_APP,
290
+ wantAck: bool = False,
291
+ wantResponse: bool = False,
292
+ onResponse=None,
293
+ onResponseAckPermitted: bool = False,
294
+ channelIndex: int = 0,
295
+ hopLimit: Optional[int] = None,
296
+ pkiEncrypted: Optional[bool] = False,
297
+ publicKey: Optional[bytes] = None,
298
+ priority: int = mesh_pb2.MeshPacket.Priority.RELIABLE,
299
+ replyId: Optional[int] = None,
300
+ ):
301
+ if getattr(data, "SerializeToString", None):
302
+ data = data.SerializeToString()
303
+ if not isinstance(data, (bytes, bytearray)):
304
+ raise TypeError("data must be bytes or a protobuf with SerializeToString()")
305
+ if int(channelIndex) != 0:
306
+ raise NotImplementedError("Only channelIndex=0 is currently supported")
307
+ if publicKey is not None:
308
+ raise NotImplementedError("publicKey override is not currently supported")
309
+ if int(portNum) == portnums_pb2.PortNum.UNKNOWN_APP:
310
+ raise ValueError("A non-zero port number must be specified")
311
+
312
+ destination = self._resolve_destination_compat(destinationId)
313
+ force_pki = bool(pkiEncrypted)
314
+ data_message = mesh_pb2.Data()
315
+ data_message.portnum = int(portNum)
316
+ data_message.payload = bytes(data)
317
+ data_message.want_response = bool(wantResponse)
318
+ data_message.source = self.node_num
319
+ data_message.dest = int(destination)
320
+ if replyId is not None:
321
+ data_message.reply_id = int(replyId)
322
+
323
+ packet = self._send_data_packet(
324
+ data_message,
325
+ destination=int(destination),
326
+ force_pki=force_pki,
327
+ hop_limit=hopLimit,
328
+ want_ack=wantAck,
329
+ priority=int(priority),
330
+ )
331
+ if onResponse is not None:
332
+ self._response_handlers[int(packet.id)] = {
333
+ "callback": onResponse,
334
+ "ack_permitted": bool(onResponseAckPermitted) or getattr(onResponse, "__name__", "") == "onAckNak",
335
+ }
336
+ return packet
337
+
338
+ def sendPosition(
339
+ self,
340
+ latitude: float = 0.0,
341
+ longitude: float = 0.0,
342
+ altitude: int = 0,
343
+ destinationId: Union[int, str] = BROADCAST_ADDR,
344
+ wantAck: bool = False,
345
+ wantResponse: bool = False,
346
+ channelIndex: int = 0,
347
+ hopLimit: Optional[int] = None,
348
+ ):
349
+ position = mesh_pb2.Position()
350
+ if latitude != 0.0:
351
+ position.latitude_i = int(latitude * 1e7)
352
+ if longitude != 0.0:
353
+ position.longitude_i = int(longitude * 1e7)
354
+ if altitude != 0:
355
+ position.altitude = int(altitude)
356
+ return self.sendData(
357
+ position,
358
+ destinationId=destinationId,
359
+ portNum=portnums_pb2.PortNum.POSITION_APP,
360
+ wantAck=wantAck,
361
+ wantResponse=wantResponse,
362
+ channelIndex=channelIndex,
363
+ hopLimit=hopLimit,
364
+ )
365
+
194
366
  def connect_send_socket(self) -> None:
195
367
  if getattr(conn, "socket", None) is None:
196
368
  conn.setup_multicast(self.config.udp.mcast_group, int(self.config.udp.mcast_port))
@@ -390,13 +562,38 @@ class VirtualNode:
390
562
  hop_limit: Optional[int] = None,
391
563
  hop_start: Optional[int] = None,
392
564
  want_ack: bool = False,
565
+ priority: Optional[int] = None,
393
566
  ) -> int:
567
+ packet = self._send_data_packet(
568
+ data,
569
+ destination=destination,
570
+ force_pki=force_pki,
571
+ hop_limit=hop_limit,
572
+ hop_start=hop_start,
573
+ want_ack=want_ack,
574
+ priority=priority,
575
+ )
576
+ return packet.id
577
+
578
+ def _send_data_packet(
579
+ self,
580
+ data: mesh_pb2.Data,
581
+ *,
582
+ destination: int,
583
+ force_pki: bool,
584
+ hop_limit: Optional[int] = None,
585
+ hop_start: Optional[int] = None,
586
+ want_ack: bool = False,
587
+ priority: Optional[int] = None,
588
+ ) -> mesh_pb2.MeshPacket:
394
589
  self.connect_send_socket()
395
590
  packet = mesh_pb2.MeshPacket()
396
591
  packet.id = self._next_packet_id()
397
592
  setattr(packet, "from", self.node_num)
398
593
  packet.to = int(destination)
399
594
  packet.want_ack = bool(want_ack)
595
+ if priority is not None:
596
+ packet.priority = int(priority)
400
597
  resolved_hop_limit = int(self.config.hop_limit if hop_limit is None else hop_limit)
401
598
  resolved_hop_start = int(resolved_hop_limit if hop_start is None else hop_start)
402
599
  if resolved_hop_start < resolved_hop_limit:
@@ -429,7 +626,7 @@ class VirtualNode:
429
626
  register_pending_ack(packet, raw_packet)
430
627
  conn.sendto(raw_packet, (conn.host, conn.port))
431
628
  self._persist_outbound_packet(packet, data)
432
- return packet.id
629
+ return packet
433
630
 
434
631
  def _handle_raw_packet(self, packet: mesh_pb2.MeshPacket, addr: Any = None) -> None:
435
632
  del addr
@@ -594,10 +791,7 @@ class VirtualNode:
594
791
  )
595
792
 
596
793
  def _publish_receive(self, packet: mesh_pb2.MeshPacket) -> None:
597
- from meshtastic import protocols
598
-
599
- packet_dict = meshdb.normalize_packet(packet, "udp")
600
- packet_dict["raw"] = packet
794
+ packet_dict = self._mesh_interface_packet_dict(packet)
601
795
 
602
796
  topic = self.RECEIVE_TOPIC
603
797
  decoded = packet_dict.get("decoded")
@@ -605,23 +799,94 @@ class VirtualNode:
605
799
  if isinstance(decoded, dict):
606
800
  raw_portnum = decoded.get("portnum")
607
801
  if raw_portnum is not None:
608
- try:
609
- portnum = int(raw_portnum)
610
- except (TypeError, ValueError):
611
- portnum = None
802
+ portnum = raw_portnum
612
803
 
613
804
  if portnum is not None:
614
- handler = protocols.get(portnum)
805
+ try:
806
+ portnum_value = portnums_pb2.PortNum.Value(str(portnum))
807
+ except ValueError:
808
+ portnum_value = None
809
+ handler = protocols.get(portnum_value) if portnum_value is not None else None
615
810
  if handler is not None:
616
811
  topic = f"{self.RECEIVE_TOPIC}.{handler.name}"
617
812
  else:
618
- try:
619
- topic = f"{self.RECEIVE_TOPIC}.data.{portnums_pb2.PortNum.Name(portnum)}"
620
- except ValueError:
813
+ if isinstance(portnum, str):
814
+ topic = f"{self.RECEIVE_TOPIC}.data.{portnum}"
815
+ else:
621
816
  topic = f"{self.RECEIVE_TOPIC}.data.{portnum}"
622
817
 
623
818
  pub.sendMessage(topic, packet=packet_dict, interface=self)
624
819
 
820
+ def _mesh_interface_packet_dict(self, packet: mesh_pb2.MeshPacket) -> Dict[str, Any]:
821
+ packet_dict = meshdb.normalize_packet(packet, "udp")
822
+ packet_dict["raw"] = packet
823
+
824
+ if packet.HasField("decoded"):
825
+ decoded = packet_dict.setdefault("decoded", {})
826
+ decoded["payload"] = bytes(packet.decoded.payload)
827
+ try:
828
+ decoded["portnum"] = portnums_pb2.PortNum.Name(int(packet.decoded.portnum))
829
+ except ValueError:
830
+ decoded["portnum"] = int(packet.decoded.portnum)
831
+ if getattr(packet.decoded, "want_response", False):
832
+ decoded["wantResponse"] = True
833
+ if getattr(packet.decoded, "request_id", 0):
834
+ decoded["requestId"] = int(packet.decoded.request_id)
835
+ if getattr(packet.decoded, "reply_id", 0):
836
+ decoded["replyId"] = int(packet.decoded.reply_id)
837
+ if getattr(packet.decoded, "emoji", 0):
838
+ decoded["emoji"] = int(packet.decoded.emoji)
839
+
840
+ handler = protocols.get(int(packet.decoded.portnum))
841
+ if handler is not None and getattr(handler, "protobufFactory", None) is not None:
842
+ try:
843
+ pb = handler.protobufFactory()
844
+ pb.ParseFromString(bytes(packet.decoded.payload))
845
+ decoded[handler.name] = MessageToDict(
846
+ pb,
847
+ preserving_proto_field_name=False,
848
+ use_integers_for_enums=False,
849
+ )
850
+ except Exception:
851
+ pass
852
+ return packet_dict
853
+
854
+ def _resolve_destination_compat(self, destination: Union[int, str]) -> int:
855
+ if destination == BROADCAST_ADDR:
856
+ return BROADCAST_NUM
857
+ if destination == LOCAL_ADDR:
858
+ return self.node_num
859
+ return self._resolve_destination(destination)
860
+
861
+ def _handle_compat_response_packet(self, packet, interface=None) -> None:
862
+ if interface is not self:
863
+ return
864
+ raw_packet = packet.get("raw") if isinstance(packet, dict) else None
865
+ if not isinstance(raw_packet, mesh_pb2.MeshPacket) or not raw_packet.HasField("decoded"):
866
+ return
867
+ request_id = int(getattr(raw_packet.decoded, "request_id", 0))
868
+ if request_id <= 0 or is_ack(raw_packet) or is_nak(raw_packet):
869
+ return
870
+ handler = self._response_handlers.pop(request_id, None)
871
+ if handler is not None:
872
+ handler["callback"](packet)
873
+
874
+ def _handle_compat_ack(self, packet, routing=None, addr=None, pending=None) -> None:
875
+ del routing, addr, pending
876
+ request_id = int(getattr(packet.decoded, "request_id", 0)) if packet.HasField("decoded") else 0
877
+ handler = self._response_handlers.get(request_id)
878
+ if handler is None or not handler["ack_permitted"]:
879
+ return
880
+ self._response_handlers.pop(request_id, None)
881
+ handler["callback"](self._mesh_interface_packet_dict(packet))
882
+
883
+ def _handle_compat_nak(self, packet, routing=None, addr=None, pending=None) -> None:
884
+ del routing, addr, pending
885
+ request_id = int(getattr(packet.decoded, "request_id", 0)) if packet.HasField("decoded") else 0
886
+ handler = self._response_handlers.pop(request_id, None)
887
+ if handler is not None:
888
+ handler["callback"](self._mesh_interface_packet_dict(packet))
889
+
625
890
  def _broadcast_loop(self) -> None:
626
891
  nodeinfo_interval = int(self.config.broadcasts.nodeinfo_interval_seconds)
627
892
  position_interval = int(self.config.position.position_interval_seconds)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes