meshcore 2.1.16__tar.gz → 2.1.21__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.
Files changed (61) hide show
  1. {meshcore-2.1.16 → meshcore-2.1.21}/PKG-INFO +1 -1
  2. {meshcore-2.1.16 → meshcore-2.1.21}/pyproject.toml +1 -1
  3. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/ble_cx.py +8 -2
  4. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/__init__.py +6 -1
  5. meshcore-2.1.21/src/meshcore/commands/control_data.py +45 -0
  6. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/device.py +41 -42
  7. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/messaging.py +19 -0
  8. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/events.py +2 -0
  9. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/meshcore.py +1 -0
  10. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/packets.py +8 -0
  11. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/reader.py +167 -121
  12. {meshcore-2.1.16 → meshcore-2.1.21}/.github/python-test.yml +0 -0
  13. {meshcore-2.1.16 → meshcore-2.1.21}/.gitignore +0 -0
  14. {meshcore-2.1.16 → meshcore-2.1.21}/LICENSE +0 -0
  15. {meshcore-2.1.16 → meshcore-2.1.21}/README.md +0 -0
  16. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_chat.py +0 -0
  17. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_pin_pairing_example.py +0 -0
  18. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_private_key_export.py +0 -0
  19. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_chan_msg.py +0 -0
  20. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_custom_vars.py +0 -0
  21. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_infos.py +0 -0
  22. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_msg.py +0 -0
  23. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_msg_retries.py +0 -0
  24. {meshcore-2.1.16 → meshcore-2.1.21}/examples/ble_t1000_set_cv.py +0 -0
  25. {meshcore-2.1.16 → meshcore-2.1.21}/examples/connection_events_example.py +0 -0
  26. {meshcore-2.1.16 → meshcore-2.1.21}/examples/mepo_mc_gps.py +0 -0
  27. {meshcore-2.1.16 → meshcore-2.1.21}/examples/pubsub_example.py +0 -0
  28. {meshcore-2.1.16 → meshcore-2.1.21}/examples/rf_packet_monitor.py +0 -0
  29. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_battery_monitor.py +0 -0
  30. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_channel_manager.py +0 -0
  31. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_chat.py +0 -0
  32. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_contacts.py +0 -0
  33. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_infos.py +0 -0
  34. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_msg.py +0 -0
  35. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_repeater_status.py +0 -0
  36. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_repeater_telemetry.py +0 -0
  37. {meshcore-2.1.16 → meshcore-2.1.21}/examples/serial_trace.py +0 -0
  38. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_chat.py +0 -0
  39. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_login_status.py +0 -0
  40. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_mchome_contacts.py +0 -0
  41. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_mchome_infos.py +0 -0
  42. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_mchome_msg.py +0 -0
  43. {meshcore-2.1.16 → meshcore-2.1.21}/examples/tcp_mchome_readmsgs.py +0 -0
  44. {meshcore-2.1.16 → meshcore-2.1.21}/pytest.ini +0 -0
  45. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/__init__.py +0 -0
  46. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/base.py +0 -0
  47. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/binary.py +0 -0
  48. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/commands/contact.py +0 -0
  49. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/connection_manager.py +0 -0
  50. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/lpp_json_encoder.py +0 -0
  51. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/parsing.py +0 -0
  52. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/serial_cx.py +0 -0
  53. {meshcore-2.1.16 → meshcore-2.1.21}/src/meshcore/tcp_cx.py +0 -0
  54. {meshcore-2.1.16 → meshcore-2.1.21}/tests/README.md +0 -0
  55. {meshcore-2.1.16 → meshcore-2.1.21}/tests/test_ble_connection.py +0 -0
  56. {meshcore-2.1.16 → meshcore-2.1.21}/tests/test_ble_pin_pairing.py +0 -0
  57. {meshcore-2.1.16 → meshcore-2.1.21}/tests/test_meshcore_ble_pin.py +0 -0
  58. {meshcore-2.1.16 → meshcore-2.1.21}/tests/unit/test_commands.py +0 -0
  59. {meshcore-2.1.16 → meshcore-2.1.21}/tests/unit/test_events.py +0 -0
  60. {meshcore-2.1.16 → meshcore-2.1.21}/tests/unit/test_private_key_export.py +0 -0
  61. {meshcore-2.1.16 → meshcore-2.1.21}/tests/unit/test_reader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcore
3
- Version: 2.1.16
3
+ Version: 2.1.21
4
4
  Summary: Base classes for communicating with meshcore companion radios
5
5
  Project-URL: Homepage, https://github.com/fdlamotte/meshcore_py
6
6
  Project-URL: Issues, https://github.com/fdlamotte/meshcore_py/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshcore"
7
- version = "2.1.16"
7
+ version = "2.1.21"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  { name="Alex Wolden", email="awolden@gmail.com" },
@@ -18,7 +18,6 @@ UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
18
18
  UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
19
19
  UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
20
20
 
21
-
22
21
  class BLEConnection:
23
22
  def __init__(self, address=None, device=None, client=None, pin=None):
24
23
  """
@@ -113,7 +112,14 @@ class BLEConnection:
113
112
  except TimeoutError:
114
113
  return None
115
114
 
116
- await self.client.start_notify(UART_TX_CHAR_UUID, self.handle_rx)
115
+ try:
116
+ await self.client.start_notify(UART_TX_CHAR_UUID, self.handle_rx)
117
+ except AttributeError :
118
+ if self.client :
119
+ await self.client.disconnect()
120
+ logger.info("Connection is not established, need to restart it")
121
+ logger.debug("in ble_cx.connect()")
122
+ return None
117
123
 
118
124
  nus = self.client.services.get_service(UART_SERVICE_UUID)
119
125
  if nus is None:
@@ -7,10 +7,15 @@ from .binary import BinaryCommandHandler
7
7
  from .contact import ContactCommands
8
8
  from .device import DeviceCommands
9
9
  from .messaging import MessagingCommands
10
+ from .control_data import ControlDataCommandHandler
10
11
 
11
12
 
12
13
  class CommandHandler(
13
- DeviceCommands, ContactCommands, MessagingCommands, BinaryCommandHandler
14
+ DeviceCommands,
15
+ ContactCommands,
16
+ MessagingCommands,
17
+ BinaryCommandHandler,
18
+ ControlDataCommandHandler
14
19
  ):
15
20
  pass
16
21
 
@@ -0,0 +1,45 @@
1
+ import logging
2
+ import random
3
+
4
+ from .base import CommandHandlerBase
5
+ from ..events import EventType, Event
6
+ from ..packets import ControlType, PacketType
7
+
8
+ logger = logging.getLogger("meshcore")
9
+
10
+ class ControlDataCommandHandler(CommandHandlerBase):
11
+ """Helper functions to handle binary requests through binary commands"""
12
+
13
+ async def send_control_data (self, control_type: ControlType, payload: bytes) -> Event:
14
+ data = bytearray([PacketType.SEND_CONTROL_DATA.value])
15
+ data.extend(control_type.value.to_bytes(1, "little", signed = False))
16
+ data.extend(payload)
17
+
18
+ result = await self.send(data, [EventType.OK, EventType.ERROR])
19
+ return result
20
+
21
+ async def send_node_discover_req (
22
+ self,
23
+ filter: int,
24
+ tag: int=None,
25
+ since: int=None
26
+ ) -> Event:
27
+
28
+ if tag is None:
29
+ tag = random.randint(1, 0xFFFFFFFF)
30
+
31
+ data = bytearray()
32
+ data.extend(filter.to_bytes(1, "little", signed=False))
33
+ data.extend(tag.to_bytes(4, "little"))
34
+ if not since is None:
35
+ data.extend(since.to_bytes(4, "little", signed=False))
36
+
37
+ logger.debug(f"sending node discover req {data.hex()}")
38
+
39
+ res = await self.send_control_data(ControlType.NODE_DISCOVER_REQ, data)
40
+
41
+ if res is None:
42
+ return None
43
+ else:
44
+ res.payload["tag"] = tag
45
+ return res
@@ -87,14 +87,18 @@ class DeviceCommands(CommandHandlerBase):
87
87
  [EventType.OK, EventType.ERROR],
88
88
  )
89
89
 
90
+ # the old set_other_params function has been replaced in
91
+ # favour of set_other_params_from_infos to be more generic
92
+ # stays here for backward compatibility but does not support
93
+ # multi_acks for instance
90
94
  async def set_other_params(
91
- self,
92
- manual_add_contacts: bool,
93
- telemetry_mode_base: int,
94
- telemetry_mode_loc: int,
95
- telemetry_mode_env: int,
96
- advert_loc_policy: int,
97
- ) -> Event:
95
+ self,
96
+ manual_add_contacts: bool,
97
+ telemetry_mode_base: int,
98
+ telemetry_mode_loc: int,
99
+ telemetry_mode_env: int,
100
+ advert_loc_policy: int,
101
+ ) -> Event:
98
102
  telemetry_mode = (
99
103
  (telemetry_mode_base & 0b11)
100
104
  | ((telemetry_mode_loc & 0b11) << 2)
@@ -108,55 +112,50 @@ class DeviceCommands(CommandHandlerBase):
108
112
  )
109
113
  return await self.send(data, [EventType.OK, EventType.ERROR])
110
114
 
115
+ async def set_other_params_from_infos(self, infos) -> Event:
116
+ telemetry_mode = (
117
+ (infos["telemetry_mode_base"] & 0b11)
118
+ | ((infos["telemetry_mode_loc"] & 0b11) << 2)
119
+ | ((infos["telemetry_mode_env"] & 0b11) << 4)
120
+ )
121
+ data = (
122
+ b"\x26"
123
+ + infos["manual_add_contacts"].to_bytes(1)
124
+ + telemetry_mode.to_bytes(1)
125
+ + infos["adv_loc_policy"].to_bytes(1)
126
+ + infos["multi_acks"].to_bytes(1)
127
+ )
128
+ return await self.send(data, [EventType.OK, EventType.ERROR])
129
+
111
130
  async def set_telemetry_mode_base(self, telemetry_mode_base: int) -> Event:
112
131
  infos = (await self.send_appstart()).payload
113
- return await self.set_other_params(
114
- infos["manual_add_contacts"],
115
- telemetry_mode_base,
116
- infos["telemetry_mode_loc"],
117
- infos["telemetry_mode_env"],
118
- infos["adv_loc_policy"],
119
- )
132
+ infos["telemetry_mode_base"] = telemetry_mode_base
133
+ return await self.set_other_params_from_infos(infos)
120
134
 
121
135
  async def set_telemetry_mode_loc(self, telemetry_mode_loc: int) -> Event:
122
136
  infos = (await self.send_appstart()).payload
123
- return await self.set_other_params(
124
- infos["manual_add_contacts"],
125
- infos["telemetry_mode_base"],
126
- telemetry_mode_loc,
127
- infos["telemetry_mode_env"],
128
- infos["adv_loc_policy"],
129
- )
137
+ infos["telemetry_mode_loc"] = telemetry_mode_loc
138
+ return await self.set_other_params_from_infos(infos)
130
139
 
131
140
  async def set_telemetry_mode_env(self, telemetry_mode_env: int) -> Event:
132
141
  infos = (await self.send_appstart()).payload
133
- return await self.set_other_params(
134
- infos["manual_add_contacts"],
135
- infos["telemetry_mode_base"],
136
- infos["telemetry_mode_loc"],
137
- telemetry_mode_env,
138
- infos["adv_loc_policy"],
139
- )
142
+ infos["telemetry_mode_env"] = telemetry_mode_env
143
+ return await self.set_other_params_from_infos(infos)
140
144
 
141
145
  async def set_manual_add_contacts(self, manual_add_contacts: bool) -> Event:
142
146
  infos = (await self.send_appstart()).payload
143
- return await self.set_other_params(
144
- manual_add_contacts,
145
- infos["telemetry_mode_base"],
146
- infos["telemetry_mode_loc"],
147
- infos["telemetry_mode_env"],
148
- infos["adv_loc_policy"],
149
- )
147
+ infos["manual_add_contacts"] = manual_add_contacts
148
+ return await self.set_other_params_from_infos(infos)
150
149
 
151
150
  async def set_advert_loc_policy(self, advert_loc_policy: int) -> Event:
152
151
  infos = (await self.send_appstart()).payload
153
- return await self.set_other_params(
154
- infos["manual_add_contacts"],
155
- infos["telemetry_mode_base"],
156
- infos["telemetry_mode_loc"],
157
- infos["telemetry_mode_env"],
158
- advert_loc_policy,
159
- )
152
+ infos["adv_loc_policy"] = advert_loc_policy
153
+ return await self.set_other_params_from_infos(infos)
154
+
155
+ async def set_multi_acks(self, multi_acks: int) -> Event:
156
+ infos = (await self.send_appstart()).payload
157
+ infos["multi_acks"] = multi_acks
158
+ return await self.set_other_params_from_infos(infos)
160
159
 
161
160
  async def set_devicepin(self, pin: int) -> Event:
162
161
  logger.debug(f"Setting device PIN to: {pin}")
@@ -1,8 +1,10 @@
1
1
  import logging
2
2
  import random
3
3
  from typing import Optional, Union
4
+ from hashlib import sha256
4
5
 
5
6
  from ..events import Event, EventType
7
+ from ..packets import PacketType
6
8
  from .base import CommandHandlerBase, DestinationType, _validate_destination
7
9
 
8
10
  logger = logging.getLogger("meshcore")
@@ -209,3 +211,20 @@ class MessagingCommands(CommandHandlerBase):
209
211
  return Event(EventType.ERROR, {"reason": "unsupported_path_type"})
210
212
 
211
213
  return await self.send(cmd_data, [EventType.MSG_SENT, EventType.ERROR])
214
+
215
+ async def set_flood_scope(self, scope):
216
+ if scope.startswith("#"): # an hash
217
+ logger.debug(f"Setting scope from hash {scope}")
218
+ scope_key = sha256(scope.encode("utf-8")).digest()[0:16]
219
+ elif scope == "0" or scope == "None" or scope == "*" or scope == "": # disable
220
+ scope_key = b"\0"*16
221
+ else: # assume the key has been sent directly
222
+ scope_key = scope.encode("utf-8")
223
+
224
+ logger.debug(f"Setting scope to {scope_key.hex()}")
225
+
226
+ cmd_data = bytearray([PacketType.SET_FLOOD_SCOPE.value])
227
+ cmd_data.extend(b"\0")
228
+ cmd_data.extend(scope_key)
229
+
230
+ return await self.send(cmd_data, [EventType.OK, EventType.ERROR])
@@ -44,6 +44,8 @@ class EventType(Enum):
44
44
  PATH_RESPONSE = "path_response"
45
45
  PRIVATE_KEY = "private_key"
46
46
  DISABLED = "disabled"
47
+ CONTROL_DATA = "control_data"
48
+ DISCOVER_RESPONSE = "discover_response"
47
49
 
48
50
  # Command response types
49
51
  OK = "command_ok"
@@ -165,6 +165,7 @@ class MeshCore:
165
165
  await self.dispatcher.start()
166
166
  result = await self.connection_manager.connect()
167
167
  if result is None:
168
+ await self.dispatcher.stop()
168
169
  raise ConnectionError("Failed to connect to device")
169
170
  return await self.commands.send_appstart()
170
171
 
@@ -7,6 +7,10 @@ class BinaryReqType(Enum):
7
7
  MMA = 0x04
8
8
  ACL = 0x05
9
9
 
10
+ class ControlType(Enum):
11
+ NODE_DISCOVER_REQ = 0x80
12
+ NODE_DISCOVER_RESP = 0x90
13
+
10
14
  # Packet prefixes for the protocol
11
15
  class PacketType(Enum):
12
16
  OK = 0
@@ -33,6 +37,9 @@ class PacketType(Enum):
33
37
  CUSTOM_VARS = 21
34
38
  BINARY_REQ = 50
35
39
  FACTORY_RESET = 51
40
+ PATH_DISCOVERY = 52
41
+ SET_FLOOD_SCOPE = 54
42
+ SEND_CONTROL_DATA = 55
36
43
 
37
44
  # Push notifications
38
45
  ADVERTISEMENT = 0x80
@@ -49,3 +56,4 @@ class PacketType(Enum):
49
56
  TELEMETRY_RESPONSE = 0x8B
50
57
  BINARY_RESPONSE = 0x8C
51
58
  PATH_DISCOVERY_RESPONSE = 0x8D
59
+ CONTROL_DATA = 0x8E
@@ -1,9 +1,10 @@
1
1
  import logging
2
2
  import json
3
3
  import time
4
+ import io
4
5
  from typing import Any, Dict
5
6
  from .events import Event, EventType, EventDispatcher
6
- from .packets import BinaryReqType, PacketType
7
+ from .packets import BinaryReqType, PacketType, ControlType
7
8
  from .parsing import lpp_parse, lpp_parse_mma, parse_acl, parse_status
8
9
  from cayennelpp import LppFrame, LppData
9
10
  from meshcore.lpp_json_encoder import lpp_json_encoder
@@ -18,7 +19,7 @@ class MessageReader:
18
19
  # before events are dispatched
19
20
  self.contacts = {} # Temporary storage during contact list building
20
21
  self.contact_nb = 0 # Used for contact processing
21
-
22
+
22
23
  # Track pending binary requests by tag for proper response parsing
23
24
  self.pending_binary_requests: Dict[str, Dict[str, Any]] = {} # tag -> {request_type, expires_at}
24
25
 
@@ -26,7 +27,7 @@ class MessageReader:
26
27
  """Register a pending binary request for proper response parsing"""
27
28
  # Clean up expired requests before adding new one
28
29
  self.cleanup_expired_requests()
29
-
30
+
30
31
  expires_at = time.time() + timeout_seconds
31
32
  self.pending_binary_requests[tag] = {
32
33
  "request_type": request_type,
@@ -42,13 +43,14 @@ class MessageReader:
42
43
  tag for tag, info in self.pending_binary_requests.items()
43
44
  if current_time > info["expires_at"]
44
45
  ]
45
-
46
+
46
47
  for tag in expired_tags:
47
48
  logger.debug(f"Cleaning up expired binary request: tag={tag}")
48
49
  del self.pending_binary_requests[tag]
49
-
50
+
50
51
  async def handle_rx(self, data: bytearray):
51
- packet_type_value = data[0]
52
+ dbuf = io.BytesIO(data)
53
+ packet_type_value = dbuf.read(1)[0]
52
54
  logger.debug(f"Received data: {data.hex()}")
53
55
 
54
56
  # Handle command responses
@@ -78,23 +80,24 @@ class MessageReader:
78
80
  or packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value
79
81
  ):
80
82
  c = {}
81
- c["public_key"] = data[1:33].hex()
82
- c["type"] = data[33]
83
- c["flags"] = data[34]
84
- c["out_path_len"] = int.from_bytes(data[35:36], signed=True, byteorder="little")
85
- plen = int.from_bytes(data[35:36], signed=True, byteorder="little")
83
+ c["public_key"] = dbuf.read(32).hex()
84
+ c["type"] = dbuf.read(1)[0]
85
+ c["flags"] = dbuf.read(1)[0]
86
+ plen = int.from_bytes(dbuf.read(1), signed=True, byteorder="little")
87
+ c["out_path_len"] = plen
86
88
  if plen == -1:
87
89
  plen = 0
88
- c["out_path"] = data[36 : 36 + plen].hex()
89
- c["adv_name"] = data[100:132].decode("utf-8", "ignore").replace("\0", "")
90
- c["last_advert"] = int.from_bytes(data[132:136], byteorder="little")
90
+ path = dbuf.read(64)
91
+ c["out_path"] = path[0:plen].hex()
92
+ c["adv_name"] = dbuf.read(32).decode("utf-8", "ignore").replace("\0", "")
93
+ c["last_advert"] = int.from_bytes(dbuf.read(4), byteorder="little")
91
94
  c["adv_lat"] = (
92
- int.from_bytes(data[136:140], byteorder="little", signed=True) / 1e6
95
+ int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
93
96
  )
94
97
  c["adv_lon"] = (
95
- int.from_bytes(data[140:144], byteorder="little", signed=True) / 1e6
98
+ int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
96
99
  )
97
- c["lastmod"] = int.from_bytes(data[144:148], byteorder="little")
100
+ c["lastmod"] = int.from_bytes(dbuf.read(4), byteorder="little")
98
101
 
99
102
  if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value:
100
103
  await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c))
@@ -103,7 +106,7 @@ class MessageReader:
103
106
  self.contacts[c["public_key"]] = c
104
107
 
105
108
  elif packet_type_value == PacketType.CONTACT_END.value:
106
- lastmod = int.from_bytes(data[1:5], byteorder="little")
109
+ lastmod = int.from_bytes(dbuf.read(4), byteorder="little")
107
110
  attributes = {
108
111
  "lastmod": lastmod,
109
112
  }
@@ -113,37 +116,39 @@ class MessageReader:
113
116
 
114
117
  elif packet_type_value == PacketType.SELF_INFO.value:
115
118
  self_info = {}
116
- self_info["adv_type"] = data[1]
117
- self_info["tx_power"] = data[2]
118
- self_info["max_tx_power"] = data[3]
119
- self_info["public_key"] = data[4:36].hex()
119
+ self_info["adv_type"] = dbuf.read(1)[0]
120
+ self_info["tx_power"] = dbuf.read(1)[0]
121
+ self_info["max_tx_power"] = dbuf.read(1)[0]
122
+ self_info["public_key"] = dbuf.read(32).hex()
120
123
  self_info["adv_lat"] = (
121
- int.from_bytes(data[36:40], byteorder="little", signed=True) / 1e6
124
+ int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
122
125
  )
123
126
  self_info["adv_lon"] = (
124
- int.from_bytes(data[40:44], byteorder="little", signed=True) / 1e6
127
+ int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
125
128
  )
126
- self_info["adv_loc_policy"] = data[45]
127
- self_info["telemetry_mode_env"] = (data[46] >> 4) & 0b11
128
- self_info["telemetry_mode_loc"] = (data[46] >> 2) & 0b11
129
- self_info["telemetry_mode_base"] = (data[46]) & 0b11
130
- self_info["manual_add_contacts"] = data[47] > 0
129
+ self_info["multi_acks"] = dbuf.read(1)[0]
130
+ self_info["adv_loc_policy"] = dbuf.read(1)[0]
131
+ telemetry_mode = dbuf.read(1)[0]
132
+ self_info["telemetry_mode_env"] = (telemetry_mode >> 4) & 0b11
133
+ self_info["telemetry_mode_loc"] = (telemetry_mode >> 2) & 0b11
134
+ self_info["telemetry_mode_base"] = (telemetry_mode) & 0b11
135
+ self_info["manual_add_contacts"] = dbuf.read(1)[0] > 0
131
136
  self_info["radio_freq"] = (
132
- int.from_bytes(data[48:52], byteorder="little") / 1000
137
+ int.from_bytes(dbuf.read(4), byteorder="little") / 1000
133
138
  )
134
139
  self_info["radio_bw"] = (
135
- int.from_bytes(data[52:56], byteorder="little") / 1000
140
+ int.from_bytes(dbuf.read(4), byteorder="little") / 1000
136
141
  )
137
- self_info["radio_sf"] = data[56]
138
- self_info["radio_cr"] = data[57]
139
- self_info["name"] = data[58:].decode("utf-8", "ignore")
142
+ self_info["radio_sf"] = dbuf.read(1)[0]
143
+ self_info["radio_cr"] = dbuf.read(1)[0]
144
+ self_info["name"] = dbuf.read().decode("utf-8", "ignore")
140
145
  await self.dispatcher.dispatch(Event(EventType.SELF_INFO, self_info))
141
146
 
142
147
  elif packet_type_value == PacketType.MSG_SENT.value:
143
148
  res = {}
144
- res["type"] = data[1]
145
- res["expected_ack"] = bytes(data[2:6])
146
- res["suggested_timeout"] = int.from_bytes(data[6:10], byteorder="little")
149
+ res["type"] = dbuf.read(1)[0]
150
+ res["expected_ack"] = dbuf.read(4)
151
+ res["suggested_timeout"] = int.from_bytes(dbuf.read(4), byteorder="little")
147
152
 
148
153
  attributes = {
149
154
  "type": res["type"],
@@ -155,15 +160,14 @@ class MessageReader:
155
160
  elif packet_type_value == PacketType.CONTACT_MSG_RECV.value:
156
161
  res = {}
157
162
  res["type"] = "PRIV"
158
- res["pubkey_prefix"] = data[1:7].hex()
159
- res["path_len"] = data[7]
160
- res["txt_type"] = data[8]
161
- res["sender_timestamp"] = int.from_bytes(data[9:13], byteorder="little")
162
- if data[8] == 2:
163
- res["signature"] = data[13:17].hex()
164
- res["text"] = data[17:].decode("utf-8", "ignore")
165
- else:
166
- res["text"] = data[13:].decode("utf-8", "ignore")
163
+ res["pubkey_prefix"] = dbuf.read(6).hex()
164
+ res["path_len"] = dbuf.read(1)[0]
165
+ txt_type = dbuf.read(1)[0]
166
+ res["txt_type"] = txt_type
167
+ res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
168
+ if txt_type == 2:
169
+ res["signature"] = dbuf.read(4).hex()
170
+ res["text"] = dbuf.read().decode("utf-8", "ignore")
167
171
 
168
172
  attributes = {
169
173
  "pubkey_prefix": res["pubkey_prefix"],
@@ -177,16 +181,16 @@ class MessageReader:
177
181
  elif packet_type_value == 16: # A reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
178
182
  res = {}
179
183
  res["type"] = "PRIV"
180
- res["SNR"] = int.from_bytes(data[1:2], byteorder="little", signed=True) / 4
181
- res["pubkey_prefix"] = data[4:10].hex()
182
- res["path_len"] = data[10]
183
- res["txt_type"] = data[11]
184
- res["sender_timestamp"] = int.from_bytes(data[12:16], byteorder="little")
185
- if data[11] == 2:
186
- res["signature"] = data[16:20].hex()
187
- res["text"] = data[20:].decode("utf-8", "ignore")
188
- else:
189
- res["text"] = data[16:].decode("utf-8", "ignore")
184
+ res["SNR"] = int.from_bytes(dbuf.read(2), byteorder="little", signed=True) / 4
185
+ dbuf.read(1) # reserved
186
+ res["pubkey_prefix"] = dbuf.read(6).hex()
187
+ res["path_len"] = dbuf.read(1)[0]
188
+ txt_type = dbuf.read(1)[0]
189
+ res["txt_type"] = txt_type
190
+ res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
191
+ if txt_type == 2:
192
+ res["signature"] = dbuf.read(4).hex()
193
+ res["text"] = dbuf.read().decode("utf-8", "ignore")
190
194
 
191
195
  attributes = {
192
196
  "pubkey_prefix": res["pubkey_prefix"],
@@ -200,11 +204,11 @@ class MessageReader:
200
204
  elif packet_type_value == PacketType.CHANNEL_MSG_RECV.value:
201
205
  res = {}
202
206
  res["type"] = "CHAN"
203
- res["channel_idx"] = data[1]
204
- res["path_len"] = data[2]
205
- res["txt_type"] = data[3]
206
- res["sender_timestamp"] = int.from_bytes(data[4:8], byteorder="little")
207
- res["text"] = data[8:].decode("utf-8", "ignore")
207
+ res["channel_idx"] = dbuf.read(1)[0]
208
+ res["path_len"] = dbuf.read(1)[0]
209
+ res["txt_type"] = dbuf.read(1)[0]
210
+ res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
211
+ res["text"] = dbuf.read().decode("utf-8", "ignore")
208
212
 
209
213
  attributes = {
210
214
  "channel_idx": res["channel_idx"],
@@ -218,12 +222,13 @@ class MessageReader:
218
222
  elif packet_type_value == 17: # A reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
219
223
  res = {}
220
224
  res["type"] = "CHAN"
221
- res["SNR"] = int.from_bytes(data[1:2], byteorder="little", signed=True) / 4
222
- res["channel_idx"] = data[4]
223
- res["path_len"] = data[5]
224
- res["txt_type"] = data[6]
225
- res["sender_timestamp"] = int.from_bytes(data[7:11], byteorder="little")
226
- res["text"] = data[11:].decode("utf-8", "ignore")
225
+ res["SNR"] = int.from_bytes(dbuf.read(2), byteorder="little", signed=True) / 4
226
+ dbuf.read(1) # reserved
227
+ res["channel_idx"] = dbuf.read(1)[0]
228
+ res["path_len"] = dbuf.read(1)[0]
229
+ res["txt_type"] = dbuf.read(1)[0]
230
+ res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
231
+ res["text"] = dbuf.read().decode("utf-8", "ignore")
227
232
 
228
233
  attributes = {
229
234
  "channel_idx": res["channel_idx"],
@@ -235,7 +240,7 @@ class MessageReader:
235
240
  )
236
241
 
237
242
  elif packet_type_value == PacketType.CURRENT_TIME.value:
238
- time_value = int.from_bytes(data[1:5], byteorder="little")
243
+ time_value = int.from_bytes(dbuf.read(4), byteorder="little")
239
244
  result = {"time": time_value}
240
245
  await self.dispatcher.dispatch(Event(EventType.CURRENT_TIME, result))
241
246
 
@@ -244,34 +249,34 @@ class MessageReader:
244
249
  await self.dispatcher.dispatch(Event(EventType.NO_MORE_MSGS, result))
245
250
 
246
251
  elif packet_type_value == PacketType.CONTACT_URI.value:
247
- contact_uri = "meshcore://" + data[1:].hex()
252
+ contact_uri = "meshcore://" + dbuf.read().hex()
248
253
  result = {"uri": contact_uri}
249
254
  await self.dispatcher.dispatch(Event(EventType.CONTACT_URI, result))
250
255
 
251
256
  elif packet_type_value == PacketType.BATTERY.value:
252
- battery_level = int.from_bytes(data[1:3], byteorder="little")
257
+ battery_level = int.from_bytes(dbuf.read(2), byteorder="little")
253
258
  result = {"level": battery_level}
254
259
  if len(data) > 3: # has storage info as well
255
- result["used_kb"] = int.from_bytes(data[3:7], byteorder="little")
256
- result["total_kb"] = int.from_bytes(data[7:11], byteorder="little")
260
+ result["used_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
261
+ result["total_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
257
262
  await self.dispatcher.dispatch(Event(EventType.BATTERY, result))
258
263
 
259
264
  elif packet_type_value == PacketType.DEVICE_INFO.value:
260
265
  res = {}
261
- res["fw ver"] = data[1]
266
+ res["fw ver"] = dbuf.read(1)[0]
262
267
  if data[1] >= 3:
263
- res["max_contacts"] = data[2] * 2
264
- res["max_channels"] = data[3]
265
- res["ble_pin"] = int.from_bytes(data[4:8], byteorder="little")
266
- res["fw_build"] = data[8:20].decode("utf-8", "ignore").replace("\0", "")
267
- res["model"] = data[20:60].decode("utf-8", "ignore").replace("\0", "")
268
- res["ver"] = data[60:80].decode("utf-8", "ignore").replace("\0", "")
268
+ res["max_contacts"] = dbuf.read(1)[0] * 2
269
+ res["max_channels"] = dbuf.read(1)[0]
270
+ res["ble_pin"] = int.from_bytes(dbuf.read(4), byteorder="little")
271
+ res["fw_build"] = dbuf.read(12).decode("utf-8", "ignore").replace("\0", "")
272
+ res["model"] = dbuf.read(40).decode("utf-8", "ignore").replace("\0", "")
273
+ res["ver"] = dbuf.read(20).decode("utf-8", "ignore").replace("\0", "")
269
274
  await self.dispatcher.dispatch(Event(EventType.DEVICE_INFO, res))
270
275
 
271
276
  elif packet_type_value == PacketType.CUSTOM_VARS.value:
272
277
  logger.debug(f"received custom vars response: {data.hex()}")
273
278
  res = {}
274
- rawdata = data[1:].decode("utf-8", "ignore")
279
+ rawdata = dbuf.read().decode("utf-8", "ignore")
275
280
  if not rawdata == "":
276
281
  pairs = rawdata.split(",")
277
282
  for p in pairs:
@@ -283,30 +288,30 @@ class MessageReader:
283
288
  elif packet_type_value == PacketType.CHANNEL_INFO.value:
284
289
  logger.debug(f"received channel info response: {data.hex()}")
285
290
  res = {}
286
- res["channel_idx"] = data[1]
291
+ res["channel_idx"] = dbuf.read(1)[0]
287
292
 
288
293
  # Channel name is null-terminated, so find the first null byte
289
- name_bytes = data[2:34]
294
+ name_bytes = dbuf.read(32)
290
295
  null_pos = name_bytes.find(0)
291
296
  if null_pos >= 0:
292
297
  res["channel_name"] = name_bytes[:null_pos].decode("utf-8", "ignore")
293
298
  else:
294
299
  res["channel_name"] = name_bytes.decode("utf-8", "ignore")
295
300
 
296
- res["channel_secret"] = data[34:50]
301
+ res["channel_secret"] = dbuf.read(16)
297
302
  await self.dispatcher.dispatch(Event(EventType.CHANNEL_INFO, res, res))
298
303
 
299
304
  # Push notifications
300
305
  elif packet_type_value == PacketType.ADVERTISEMENT.value:
301
306
  logger.debug("Advertisement received")
302
307
  res = {}
303
- res["public_key"] = data[1:33].hex()
308
+ res["public_key"] = dbuf.read(32).hex()
304
309
  await self.dispatcher.dispatch(Event(EventType.ADVERTISEMENT, res, res))
305
310
 
306
311
  elif packet_type_value == PacketType.PATH_UPDATE.value:
307
312
  logger.debug("Code path update")
308
313
  res = {}
309
- res["public_key"] = data[1:33].hex()
314
+ res["public_key"] = dbuf.read(32).hex()
310
315
  await self.dispatcher.dispatch(Event(EventType.PATH_UPDATE, res, res))
311
316
 
312
317
  elif packet_type_value == PacketType.ACK.value:
@@ -314,7 +319,7 @@ class MessageReader:
314
319
  ack_data = {}
315
320
 
316
321
  if len(data) >= 5:
317
- ack_data["code"] = bytes(data[1:5]).hex()
322
+ ack_data["code"] = dbuf.read(4).hex()
318
323
 
319
324
  attributes = {"code": ack_data.get("code", "")}
320
325
 
@@ -326,23 +331,24 @@ class MessageReader:
326
331
 
327
332
  elif packet_type_value == PacketType.RAW_DATA.value:
328
333
  res = {}
329
- res["SNR"] = data[1] / 4
330
- res["RSSI"] = data[2]
331
- res["payload"] = data[4:].hex()
334
+ res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
335
+ res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True)
336
+ res["payload"] = dbuf.read(4).hex()
332
337
  logger.debug("Received raw data")
333
338
  print(res)
334
339
  await self.dispatcher.dispatch(Event(EventType.RAW_DATA, res))
335
340
 
336
341
  elif packet_type_value == PacketType.LOGIN_SUCCESS.value:
337
342
  res = {}
343
+ attributes = {}
338
344
  if len(data) > 1:
339
- res["permissions"] = data[1]
340
- res["is_admin"] = (data[1] & 1) == 1 # Check if admin bit is set
345
+ perms = dbuf.read(1)[0]
346
+ res["permissions"] = perms
347
+ res["is_admin"] = (perms & 1) == 1 # Check if admin bit is set
341
348
 
342
- if len(data) > 7:
343
- res["pubkey_prefix"] = data[2:8].hex()
349
+ res["pubkey_prefix"] = dbuf.read(6).hex()
344
350
 
345
- attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
351
+ attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
346
352
 
347
353
  await self.dispatcher.dispatch(
348
354
  Event(EventType.LOGIN_SUCCESS, res, attributes)
@@ -350,11 +356,14 @@ class MessageReader:
350
356
 
351
357
  elif packet_type_value == PacketType.LOGIN_FAILED.value:
352
358
  res = {}
359
+ attributes = {}
360
+
361
+ pbuf.read(1)
353
362
 
354
363
  if len(data) > 7:
355
- res["pubkey_prefix"] = data[2:8].hex()
364
+ res["pubkey_prefix"] = pbuf.read(6).hex()
356
365
 
357
- attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
366
+ attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
358
367
 
359
368
  await self.dispatcher.dispatch(
360
369
  Event(EventType.LOGIN_FAILED, res, attributes)
@@ -368,12 +377,7 @@ class MessageReader:
368
377
  attributes = {
369
378
  "pubkey_prefix": res["pubkey_pre"],
370
379
  }
371
- data_hex = data[8:].hex()
372
- logger.debug(f"Status response: {data_hex}")
373
380
 
374
- attributes = {
375
- "pubkey_prefix": res["pubkey_pre"],
376
- }
377
381
  await self.dispatcher.dispatch(
378
382
  Event(EventType.STATUS_RESPONSE, res, attributes)
379
383
  )
@@ -386,22 +390,23 @@ class MessageReader:
386
390
 
387
391
  # First byte is SNR (signed byte, multiplied by 4)
388
392
  if len(data) > 1:
389
- snr_byte = data[1]
393
+ snr_byte = dbuf.read(1)[0]
390
394
  # Convert to signed value
391
395
  snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0
392
396
  log_data["snr"] = snr
393
397
 
394
398
  # Second byte is RSSI (signed byte)
395
399
  if len(data) > 2:
396
- rssi_byte = data[2]
400
+ rssi_byte = dbuf.read(1)[0]
397
401
  # Convert to signed value
398
402
  rssi = rssi_byte if rssi_byte < 128 else rssi_byte - 256
399
403
  log_data["rssi"] = rssi
400
404
 
401
405
  # Remaining bytes are the raw data payload
402
406
  if len(data) > 3:
403
- log_data["payload"] = data[3:].hex()
404
- log_data["payload_length"] = len(data) - 3
407
+ payload=dbuf.read()
408
+ log_data["payload"] = payload.hex()
409
+ log_data["payload_length"] = len(payload)
405
410
 
406
411
  attributes = {
407
412
  "pubkey_prefix": log_data["raw_hex"],
@@ -470,8 +475,10 @@ class MessageReader:
470
475
  logger.debug(f"Received telemetry data: {data.hex()}")
471
476
  res = {}
472
477
 
473
- res["pubkey_pre"] = data[2:8].hex()
474
- buf = data[8:]
478
+ dbuf.read(1)
479
+
480
+ res["pubkey_pre"] = dbuf.read(6).hex()
481
+ buf = dbuf.read()
475
482
 
476
483
  """Parse a given byte string and return as a LppFrame object."""
477
484
  i = 0
@@ -498,29 +505,30 @@ class MessageReader:
498
505
 
499
506
  elif packet_type_value == PacketType.BINARY_RESPONSE.value:
500
507
  logger.debug(f"Received binary data: {data.hex()}")
501
- tag = data[2:6].hex()
502
- response_data = data[6:]
503
-
508
+ dbuf.read(1)
509
+ tag = dbuf.read(4).hex()
510
+ response_data = dbuf.read()
511
+
504
512
  # Always dispatch generic BINARY_RESPONSE
505
513
  binary_res = {"tag": tag, "data": response_data.hex()}
506
514
  await self.dispatcher.dispatch(
507
515
  Event(EventType.BINARY_RESPONSE, binary_res, {"tag": tag})
508
516
  )
509
-
517
+
510
518
  # Check for tracked request type and dispatch specific response
511
519
  if tag in self.pending_binary_requests:
512
520
  request_type = self.pending_binary_requests[tag]["request_type"]
513
521
  pubkey_prefix = self.pending_binary_requests[tag]["pubkey_prefix"]
514
522
  del self.pending_binary_requests[tag]
515
523
  logger.debug(f"Processing binary response for tag {tag}, type {request_type}, pubkey_prefix {pubkey_prefix}")
516
-
524
+
517
525
  if request_type == BinaryReqType.STATUS and len(response_data) >= 52:
518
526
  res = {}
519
527
  res = parse_status(response_data, pubkey_prefix=pubkey_prefix)
520
528
  await self.dispatcher.dispatch(
521
529
  Event(EventType.STATUS_RESPONSE, res, {"pubkey_prefix": res["pubkey_pre"], "tag": tag})
522
530
  )
523
-
531
+
524
532
  elif request_type == BinaryReqType.TELEMETRY:
525
533
  try:
526
534
  lpp = lpp_parse(response_data)
@@ -530,7 +538,7 @@ class MessageReader:
530
538
  )
531
539
  except Exception as e:
532
540
  logger.error(f"Error parsing binary telemetry response: {e}")
533
-
541
+
534
542
  elif request_type == BinaryReqType.MMA:
535
543
  try:
536
544
  mma_result = lpp_parse_mma(response_data[4:]) # Skip 4-byte header
@@ -540,7 +548,7 @@ class MessageReader:
540
548
  )
541
549
  except Exception as e:
542
550
  logger.error(f"Error parsing binary MMA response: {e}")
543
-
551
+
544
552
  elif request_type == BinaryReqType.ACL:
545
553
  try:
546
554
  acl_result = parse_acl(response_data)
@@ -556,13 +564,14 @@ class MessageReader:
556
564
  elif packet_type_value == PacketType.PATH_DISCOVERY_RESPONSE.value:
557
565
  logger.debug(f"Received path discovery response: {data.hex()}")
558
566
  res = {}
559
- res["pubkey_pre"] = data[2:8].hex()
560
- opl = data[8]
567
+ dbuf.read(1)
568
+ res["pubkey_pre"] = dbuf.read(6).hex()
569
+ opl = dbuf.read(1)[0]
561
570
  res["out_path_len"] = opl
562
- res["out_path"] = data[9 : 9 + opl].hex()
563
- ipl = data[9 + opl]
571
+ res["out_path"] = dbuf.read(opl).hex()
572
+ ipl = dbuf.read(1)[0]
564
573
  res["in_path_len"] = ipl
565
- res["in_path"] = data[10 + opl : 10 + opl + ipl].hex()
574
+ res["in_path"] = dbuf.read(ipl).hex()
566
575
 
567
576
  attributes = {"pubkey_pre": res["pubkey_pre"]}
568
577
 
@@ -573,7 +582,7 @@ class MessageReader:
573
582
  elif packet_type_value == PacketType.PRIVATE_KEY.value:
574
583
  logger.debug(f"Received private key response: {data.hex()}")
575
584
  if len(data) >= 65: # 1 byte response code + 64 bytes private key
576
- private_key = data[1:65] # Extract 64-byte private key
585
+ private_key = dbuf.read(64) # Extract 64-byte private key
577
586
  res = {"private_key": private_key}
578
587
  await self.dispatcher.dispatch(Event(EventType.PRIVATE_KEY, res))
579
588
  else:
@@ -584,6 +593,43 @@ class MessageReader:
584
593
  res = {"reason": "private_key_export_disabled"}
585
594
  await self.dispatcher.dispatch(Event(EventType.DISABLED, res))
586
595
 
596
+ elif packet_type_value == PacketType.CONTROL_DATA.value:
597
+ logger.debug("Received control data packet")
598
+ res={}
599
+ res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
600
+ res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True)
601
+ res["path_len"] = dbuf.read(1)[0]
602
+ payload = dbuf.read()
603
+ payload_type = payload[0]
604
+ res["payload_type"] = payload_type
605
+ res["payload"] = payload
606
+
607
+ attributes = {"payload_type": payload_type}
608
+ await self.dispatcher.dispatch(
609
+ Event(EventType.CONTROL_DATA, res, attributes)
610
+ )
611
+
612
+ # decode NODE_DISCOVER_RESP
613
+ if payload_type & 0xF0 == ControlType.NODE_DISCOVER_RESP.value:
614
+ pbuf = io.BytesIO(payload[1:])
615
+ ndr = dict(res)
616
+ del ndr["payload_type"]
617
+ del ndr["payload"]
618
+ ndr["node_type"] = payload_type & 0x0F
619
+ ndr["SNR_in"] = int.from_bytes(pbuf.read(1), byteorder="little", signed=True)/4
620
+ ndr["tag"] = pbuf.read(4).hex()
621
+ ndr["pubkey"] = pbuf.read(32).hex()
622
+
623
+ attributes = {
624
+ "node_type" : ndr["node_type"],
625
+ "tag" : ndr["tag"],
626
+ "pubkey" : ndr["pubkey"],
627
+ }
628
+
629
+ await self.dispatcher.dispatch(
630
+ Event(EventType.DISCOVER_RESPONSE, ndr, attributes)
631
+ )
632
+
587
633
  else:
588
634
  logger.debug(f"Unhandled data received {data}")
589
635
  logger.debug(f"Unhandled packet type: {packet_type_value}")
File without changes
File without changes
File without changes
File without changes
File without changes