meshcore 2.3.4__tar.gz → 2.3.6__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 (73) hide show
  1. {meshcore-2.3.4 → meshcore-2.3.6}/PKG-INFO +1 -1
  2. {meshcore-2.3.4 → meshcore-2.3.6}/pyproject.toml +1 -1
  3. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/base.py +1 -0
  4. meshcore-2.3.6/src/meshcore/commands/binary.py +357 -0
  5. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/messaging.py +48 -10
  6. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/meshcore_parser.py +5 -8
  7. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_commands.py +33 -43
  8. meshcore-2.3.6/tests/unit/test_lpp_parsing.py +39 -0
  9. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_private_key_export.py +18 -9
  10. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_reader.py +4 -3
  11. meshcore-2.3.4/src/meshcore/commands/binary.py +0 -356
  12. {meshcore-2.3.4 → meshcore-2.3.6}/.github/python-test.yml +0 -0
  13. {meshcore-2.3.4 → meshcore-2.3.6}/.gitignore +0 -0
  14. {meshcore-2.3.4 → meshcore-2.3.6}/LICENSE +0 -0
  15. {meshcore-2.3.4 → meshcore-2.3.6}/README.md +0 -0
  16. {meshcore-2.3.4 → meshcore-2.3.6}/examples/.pubsub_example.py.swp +0 -0
  17. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_chat.py +0 -0
  18. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_pin_pairing_example.py +0 -0
  19. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_private_key_export.py +0 -0
  20. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_sign_example.py +0 -0
  21. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_stats.py +0 -0
  22. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_chan_msg.py +0 -0
  23. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_custom_vars.py +0 -0
  24. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_infos.py +0 -0
  25. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_msg.py +0 -0
  26. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_msg_retries.py +0 -0
  27. {meshcore-2.3.4 → meshcore-2.3.6}/examples/ble_t1000_set_cv.py +0 -0
  28. {meshcore-2.3.4 → meshcore-2.3.6}/examples/chan_recv_with_path.py +0 -0
  29. {meshcore-2.3.4 → meshcore-2.3.6}/examples/connection_events_example.py +0 -0
  30. {meshcore-2.3.4 → meshcore-2.3.6}/examples/mepo_mc_gps.py +0 -0
  31. {meshcore-2.3.4 → meshcore-2.3.6}/examples/pubsub_example.py +0 -0
  32. {meshcore-2.3.4 → meshcore-2.3.6}/examples/rf_packet_monitor.py +0 -0
  33. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_battery_monitor.py +0 -0
  34. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_channel_manager.py +0 -0
  35. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_chat.py +0 -0
  36. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_contacts.py +0 -0
  37. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_infos.py +0 -0
  38. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_meshcore_ollama.py +0 -0
  39. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_msg.py +0 -0
  40. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_pingbot.py +0 -0
  41. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_repeater_status.py +0 -0
  42. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_repeater_telemetry.py +0 -0
  43. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_rss_bot.py +0 -0
  44. {meshcore-2.3.4 → meshcore-2.3.6}/examples/serial_trace.py +0 -0
  45. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_chat.py +0 -0
  46. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_login_status.py +0 -0
  47. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_mchome_contacts.py +0 -0
  48. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_mchome_infos.py +0 -0
  49. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_mchome_msg.py +0 -0
  50. {meshcore-2.3.4 → meshcore-2.3.6}/examples/tcp_mchome_readmsgs.py +0 -0
  51. {meshcore-2.3.4 → meshcore-2.3.6}/pytest.ini +0 -0
  52. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/__init__.py +0 -0
  53. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/ble_cx.py +0 -0
  54. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/__init__.py +0 -0
  55. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/contact.py +0 -0
  56. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/control_data.py +0 -0
  57. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/commands/device.py +0 -0
  58. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/connection_manager.py +0 -0
  59. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/events.py +0 -0
  60. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/lpp_json_encoder.py +0 -0
  61. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/meshcore.py +0 -0
  62. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/packets.py +0 -0
  63. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/parsing.py +0 -0
  64. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/reader.py +0 -0
  65. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/serial_cx.py +0 -0
  66. {meshcore-2.3.4 → meshcore-2.3.6}/src/meshcore/tcp_cx.py +0 -0
  67. {meshcore-2.3.4 → meshcore-2.3.6}/tests/README.md +0 -0
  68. {meshcore-2.3.4 → meshcore-2.3.6}/tests/test_ble_connection.py +0 -0
  69. {meshcore-2.3.4 → meshcore-2.3.6}/tests/test_ble_pin_pairing.py +0 -0
  70. {meshcore-2.3.4 → meshcore-2.3.6}/tests/test_meshcore_ble_pin.py +0 -0
  71. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_events.py +0 -0
  72. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_path_discovery_response.py +0 -0
  73. {meshcore-2.3.4 → meshcore-2.3.6}/tests/unit/test_serial_connection.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcore
3
- Version: 2.3.4
3
+ Version: 2.3.6
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.3.4"
7
+ version = "2.3.6"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  { name="Alex Wolden", email="awolden@gmail.com" },
@@ -64,6 +64,7 @@ class CommandHandlerBase:
64
64
  self._sender_func: Optional[Callable[[bytes], Coroutine[Any, Any, None]]] = None
65
65
  self._reader: Optional[MessageReader] = None
66
66
  self.dispatcher: Optional[EventDispatcher] = None
67
+ self._mesh_request_lock = asyncio.Lock()
67
68
  self.default_timeout = (
68
69
  default_timeout if default_timeout is not None else self.DEFAULT_TIMEOUT
69
70
  )
@@ -0,0 +1,357 @@
1
+ import asyncio
2
+ import logging
3
+ import random
4
+ import io
5
+
6
+ from .base import CommandHandlerBase
7
+ from ..events import EventType
8
+ from ..packets import BinaryReqType
9
+ from ..packets import AnonReqType
10
+
11
+ logger = logging.getLogger("meshcore")
12
+
13
+
14
+ class BinaryCommandHandler(CommandHandlerBase):
15
+ """Helper functions to handle binary requests through binary commands"""
16
+
17
+ async def req_status(self, contact, timeout=0, min_timeout=0):
18
+ logger.error("*** please consider using req_status_sync instead of req_status")
19
+ return await self.req_status_sync(contact, timeout, min_timeout)
20
+
21
+ async def req_status_sync(self, contact, timeout=0, min_timeout=0):
22
+ async with self._mesh_request_lock:
23
+ res = await self.send_binary_req(
24
+ contact,
25
+ BinaryReqType.STATUS,
26
+ timeout=timeout,
27
+ min_timeout=min_timeout
28
+ )
29
+ if res.type == EventType.ERROR:
30
+ return None
31
+
32
+ exp_tag = res.payload["expected_ack"].hex()
33
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
34
+ timeout = timeout if min_timeout < timeout else min_timeout
35
+
36
+ if self.dispatcher is None:
37
+ return None
38
+
39
+ status_event = await self.dispatcher.wait_for_event(
40
+ EventType.STATUS_RESPONSE,
41
+ attribute_filters={"tag": exp_tag},
42
+ timeout=timeout,
43
+ )
44
+
45
+ return status_event.payload if status_event else None
46
+
47
+ async def req_telemetry(self, contact, timeout=0, min_timeout=0):
48
+ logger.error("*** please consider using req_telemetry_sync instead of req_telemetry")
49
+ return await self.req_telemetry_sync(contact, timeout, min_timeout)
50
+
51
+ async def req_telemetry_sync(self, contact, timeout=0, min_timeout=0):
52
+ async with self._mesh_request_lock:
53
+ res = await self.send_binary_req(
54
+ contact,
55
+ BinaryReqType.TELEMETRY,
56
+ timeout=timeout,
57
+ min_timeout=min_timeout
58
+ )
59
+ if res.type == EventType.ERROR:
60
+ return None
61
+
62
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
63
+ timeout = timeout if min_timeout < timeout else min_timeout
64
+
65
+ if self.dispatcher is None:
66
+ return None
67
+
68
+ telem_event = await self.dispatcher.wait_for_event(
69
+ EventType.TELEMETRY_RESPONSE,
70
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
71
+ timeout=timeout,
72
+ )
73
+
74
+ return telem_event.payload["lpp"] if telem_event else None
75
+
76
+ async def req_mma(self, contact, timeout=0, min_timeout=0):
77
+ logger.error("*** please consider using req_mma_sync instead of req_mma")
78
+ return await self.req_mma_sync(contact, start, end, timeout,min_timeout)
79
+
80
+ async def req_mma_sync(self, contact, start, end, timeout=0,min_timeout=0):
81
+ async with self._mesh_request_lock:
82
+ req = (
83
+ start.to_bytes(4, "little", signed=False)
84
+ + end.to_bytes(4, "little", signed=False)
85
+ + b"\0\0"
86
+ )
87
+ res = await self.send_binary_req(
88
+ contact,
89
+ BinaryReqType.MMA,
90
+ data=req,
91
+ timeout=timeout
92
+ )
93
+ if res.type == EventType.ERROR:
94
+ return None
95
+
96
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
97
+ timeout = timeout if min_timeout < timeout else min_timeout
98
+
99
+ if self.dispatcher is None:
100
+ return None
101
+
102
+ mma_event = await self.dispatcher.wait_for_event(
103
+ EventType.MMA_RESPONSE,
104
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
105
+ timeout=timeout,
106
+ )
107
+
108
+ return mma_event.payload["mma_data"] if mma_event else None
109
+
110
+ async def req_acl(self, contact, timeout=0, min_timeout=0):
111
+ logger.error("*** please consider using req_acl_sync instead of req_acl")
112
+ return await self.req_acl_sync(contact, timeout, min_timeout)
113
+
114
+ async def req_acl_sync(self, contact, timeout=0, min_timeout=0):
115
+ async with self._mesh_request_lock:
116
+ req = b"\0\0"
117
+ res = await self.send_binary_req(
118
+ contact,
119
+ BinaryReqType.ACL,
120
+ data=req,
121
+ timeout=timeout
122
+ )
123
+ if res.type == EventType.ERROR:
124
+ return None
125
+
126
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
127
+ timeout = timeout if timeout > min_timeout else min_timeout
128
+
129
+ if self.dispatcher is None:
130
+ return None
131
+
132
+ acl_event = await self.dispatcher.wait_for_event(
133
+ EventType.ACL_RESPONSE,
134
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
135
+ timeout=timeout,
136
+ )
137
+
138
+ return acl_event.payload["acl_data"] if acl_event else None
139
+
140
+ async def req_neighbours_async(self,
141
+ contact,
142
+ count=255,
143
+ offset=0,
144
+ order_by=0,
145
+ pubkey_prefix_length=4,
146
+ timeout=0,
147
+ min_timeout=0
148
+ ):
149
+ req = (b"\x00" # version : 0
150
+ + count.to_bytes(1, "little", signed=False)
151
+ + offset.to_bytes(2, "little", signed=False)
152
+ + order_by.to_bytes(1, "little", signed=False)
153
+ + pubkey_prefix_length.to_bytes(1, "little", signed=False)
154
+ + random.randint(1, 0xFFFFFFFF).to_bytes(4, "little", signed=False)
155
+ )
156
+
157
+ logger.debug(f"Sending binary neighbours req, count: {count}, offset: {offset} {req.hex()}")
158
+
159
+ return await self.send_binary_req (
160
+ contact,
161
+ BinaryReqType.NEIGHBOURS,
162
+ data=req,
163
+ timeout=timeout,
164
+ context={"pubkey_prefix_length": pubkey_prefix_length}
165
+ )
166
+
167
+ async def req_neighbours_sync(self,
168
+ contact,
169
+ count=255,
170
+ offset=0,
171
+ order_by=0,
172
+ pubkey_prefix_length=4,
173
+ timeout=0,
174
+ min_timeout=0
175
+ ):
176
+ async with self._mesh_request_lock:
177
+ res = await self.req_neighbours_async(contact,
178
+ count=count,
179
+ offset=offset,
180
+ order_by=order_by,
181
+ pubkey_prefix_length=pubkey_prefix_length,
182
+ timeout=timeout,
183
+ min_timeout=min_timeout)
184
+
185
+ if res is None or res.type == EventType.ERROR:
186
+ return None
187
+
188
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
189
+ timeout = timeout if min_timeout < timeout else min_timeout
190
+
191
+ if self.dispatcher is None:
192
+ return None
193
+
194
+ neighbours_event = await self.dispatcher.wait_for_event(
195
+ EventType.NEIGHBOURS_RESPONSE,
196
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
197
+ timeout=timeout,
198
+ )
199
+
200
+ return neighbours_event.payload if neighbours_event else None
201
+
202
+ # do several queries if not all neighbours have been obtained
203
+ async def fetch_all_neighbours(self,
204
+ contact,
205
+ order_by=0,
206
+ pubkey_prefix_length=4,
207
+ timeout=0,
208
+ min_timeout=0
209
+ ):
210
+
211
+ # Initial request
212
+ res = await self.req_neighbours_sync(contact,
213
+ count=255,
214
+ offset=0,
215
+ order_by=order_by,
216
+ pubkey_prefix_length=pubkey_prefix_length,
217
+ timeout=timeout,
218
+ min_timeout=min_timeout)
219
+
220
+ if res is None:
221
+ return None
222
+
223
+ neighbours_count = res["neighbours_count"] # total neighbours
224
+ results_count = res["results_count"] # obtained neighbours
225
+
226
+ del res["tag"]
227
+
228
+ tries = 0
229
+ while results_count < neighbours_count:
230
+ #await asyncio.sleep(2) # wait 2s before next fetch
231
+ next_res = await self.req_neighbours_sync(contact,
232
+ count=255,
233
+ offset=results_count,
234
+ order_by=order_by,
235
+ pubkey_prefix_length=pubkey_prefix_length,
236
+ timeout=timeout,
237
+ min_timeout=min_timeout+25) # requests are close and responses big,
238
+ #so let's have some more timeout for duty cycle
239
+ if next_res is None :
240
+ if tries > 0:
241
+ return res # caller should check it has everything
242
+ else:
243
+ tries += 1
244
+ else:
245
+ tries = 0
246
+ results_count += next_res["results_count"]
247
+ res["results_count"] = results_count
248
+ res["neighbours"] += next_res["neighbours"]
249
+
250
+ return res
251
+
252
+ async def req_regions_async(self, contact, timeout=0, min_timeout=0):
253
+ req = b"\0" # The return path, I currently do nothing with, so direct only
254
+ return await self.send_anon_req(
255
+ contact,
256
+ AnonReqType.REGIONS,
257
+ data=req,
258
+ timeout=timeout
259
+ )
260
+
261
+ async def req_regions_sync(self, contact, timeout=0, min_timeout=0):
262
+ async with self._mesh_request_lock:
263
+ res = await self.req_regions_async(contact, timeout, min_timeout)
264
+
265
+ if res.type == EventType.ERROR:
266
+ return None
267
+
268
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
269
+ timeout = timeout if timeout > min_timeout else min_timeout
270
+
271
+ if self.dispatcher is None:
272
+ return None
273
+
274
+ region_event = await self.dispatcher.wait_for_event(
275
+ EventType.BINARY_RESPONSE,
276
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
277
+ timeout=timeout,
278
+ )
279
+
280
+ if region_event is None:
281
+ return None
282
+
283
+ pkt = bytes().fromhex(region_event.payload["data"])
284
+ pbuf = io.BytesIO(pkt)
285
+ tag_again = pbuf.read(4)
286
+ return pbuf.read().decode("utf-8", "ignore").strip("\x00")
287
+
288
+ async def req_owner_async(self, contact, timeout=0, min_timeout=0):
289
+ req = b"\0"
290
+ return await self.send_anon_req(
291
+ contact,
292
+ AnonReqType.OWNER,
293
+ data=req,
294
+ timeout=timeout
295
+ )
296
+
297
+ async def req_owner_sync(self, contact, timeout=0, min_timeout=0):
298
+ async with self._mesh_request_lock:
299
+ res = await self.req_owner_async(contact, timeout, min_timeout)
300
+
301
+ if res.type == EventType.ERROR:
302
+ return None
303
+
304
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
305
+ timeout = timeout if timeout > min_timeout else min_timeout
306
+
307
+ if self.dispatcher is None:
308
+ return None
309
+
310
+ owner_event = await self.dispatcher.wait_for_event(
311
+ EventType.BINARY_RESPONSE,
312
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
313
+ timeout=timeout,
314
+ )
315
+
316
+ if owner_event is None:
317
+ return None
318
+
319
+ pkt = bytes().fromhex(owner_event.payload["data"])
320
+ pbuf = io.BytesIO(pkt)
321
+ tag_again = pbuf.read(4)
322
+ strings = pbuf.read().decode("utf-8", "ignore").split("\n", 1)
323
+
324
+ return dict(name=strings[0], owner=strings[1].strip("\x00"))
325
+
326
+ async def req_basic_async(self, contact, timeout=0, min_timeout=0):
327
+ req = b"\0"
328
+ return await self.send_anon_req(
329
+ contact,
330
+ AnonReqType.BASIC,
331
+ data=req,
332
+ timeout=timeout
333
+ )
334
+
335
+ async def req_basic_sync(self, contact, timeout=0, min_timeout=0):
336
+ async with self._mesh_request_lock:
337
+ res = await self.req_basic_async(contact, timeout, min_timeout)
338
+
339
+ if res.type == EventType.ERROR:
340
+ return None
341
+
342
+ timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
343
+ timeout = timeout if timeout > min_timeout else min_timeout
344
+
345
+ if self.dispatcher is None:
346
+ return None
347
+
348
+ basic_event = await self.dispatcher.wait_for_event(
349
+ EventType.BINARY_RESPONSE,
350
+ attribute_filters={"tag": res.payload["expected_ack"].hex()},
351
+ timeout=timeout,
352
+ )
353
+
354
+ if basic_event is None:
355
+ return None
356
+
357
+ return basic_event.payload
@@ -24,18 +24,37 @@ class MessagingCommands(CommandHandlerBase):
24
24
  timeout,
25
25
  )
26
26
 
27
- async def send_login(self, dst: DestinationType, pwd: str) -> Event:
27
+ async def _send_login_raw(self, dst: DestinationType, pwd: str) -> Event:
28
28
  dst_bytes = _validate_destination(dst, prefix_length=32)
29
29
  logger.debug(f"Sending login request to: {dst_bytes.hex()}")
30
30
  data = b"\x1a" + dst_bytes + pwd.encode("utf-8")
31
31
  return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
32
32
 
33
+ async def send_login(self, dst: DestinationType, pwd: str) -> Event:
34
+ logger.warning("*** please consider using send_login_sync instead of send_login")
35
+ return await self._send_login_raw(dst, pwd)
36
+
37
+ async def send_login_sync(self, dst: DestinationType, pwd: str, timeout=0, min_timeout=0) -> Optional[Event]:
38
+ """Send login to a remote node and wait for the response."""
39
+ async with self._mesh_request_lock:
40
+ result = await self._send_login_raw(dst, pwd)
41
+ if result is None or result.type == EventType.ERROR:
42
+ return None
43
+ timeout = result.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
44
+ timeout = timeout if timeout > min_timeout else min_timeout
45
+ login_event = await self.dispatcher.wait_for_event(
46
+ EventType.LOGIN_SUCCESS,
47
+ timeout=timeout,
48
+ )
49
+ return login_event
50
+
33
51
  async def send_logout(self, dst: DestinationType) -> Event:
34
52
  dst_bytes = _validate_destination(dst, prefix_length=32)
35
53
  data = b"\x1d" + dst_bytes
36
54
  return await self.send(data, [EventType.OK, EventType.ERROR])
37
55
 
38
56
  async def send_statusreq(self, dst: DestinationType) -> Event:
57
+ logger.warning("*** please consider using req_status_sync instead of send_statusreq")
39
58
  dst_bytes = _validate_destination(dst, prefix_length=32)
40
59
  logger.debug(f"Sending status request to: {dst_bytes.hex()}")
41
60
  data = b"\x1b" + dst_bytes
@@ -85,7 +104,7 @@ class MessagingCommands(CommandHandlerBase):
85
104
  self, dst: DestinationType, msg: str, timestamp: Optional[int] = None,
86
105
  max_attempts=3, max_flood_attempts=2, flood_after=2, timeout=0, min_timeout=0
87
106
  ) -> Event:
88
-
107
+
89
108
  try:
90
109
  dst_bytes = _validate_destination(dst, prefix_length=32)
91
110
  # with 32 bytes we can reset to flood
@@ -105,8 +124,8 @@ class MessagingCommands(CommandHandlerBase):
105
124
  # we can't know if we're flood without fetching all contacts
106
125
  # if we have a full key (meaning we can reset path) consider direct
107
126
  # else consider flood
108
- flood = len(dst_bytes) < 32
109
- logger.info(f"send_msg_with_retry: can't determine if flood, assume {flood}")
127
+ flood = len(dst_bytes) < 32
128
+ logger.info(f"send_msg_with_retry: can't determine if flood, assume {flood}")
110
129
  res = None
111
130
  while attempts < max_attempts and res is None \
112
131
  and (not flood or flood_attempts < max_flood_attempts):
@@ -122,8 +141,8 @@ class MessagingCommands(CommandHandlerBase):
122
141
  contact["out_path_len"] = -1
123
142
 
124
143
  if attempts > 0:
125
- logger.info(f"Retry sending msg: {attempts + 1}")
126
-
144
+ logger.info(f"Retry sending msg: {attempts + 1}")
145
+
127
146
  result = await self.send_msg(dst, msg, timestamp, attempt=attempts)
128
147
  if result.type == EventType.ERROR:
129
148
  logger.error(f"⚠️ Failed to send message: {result.payload}")
@@ -131,14 +150,14 @@ class MessagingCommands(CommandHandlerBase):
131
150
  exp_ack = result.payload["expected_ack"].hex()
132
151
  timeout = result.payload["suggested_timeout"] / 1000 * 1.2 if timeout==0 else timeout
133
152
  timeout = timeout if timeout > min_timeout else min_timeout
134
- res = await self.dispatcher.wait_for_event(EventType.ACK,
135
- attribute_filters={"code": exp_ack},
153
+ res = await self.dispatcher.wait_for_event(EventType.ACK,
154
+ attribute_filters={"code": exp_ack},
136
155
  timeout=timeout)
137
156
 
138
157
  attempts = attempts + 1
139
158
  if flood :
140
159
  flood_attempts = flood_attempts + 1
141
-
160
+
142
161
  return None if res is None else result
143
162
 
144
163
  async def send_chan_msg(self, chan: int, msg: str, timestamp: Optional[int|bytes] = None) -> Event:
@@ -166,17 +185,36 @@ class MessagingCommands(CommandHandlerBase):
166
185
  return await self.send(data, [EventType.OK, EventType.ERROR])
167
186
 
168
187
  async def send_telemetry_req(self, dst: DestinationType) -> Event:
188
+ logger.warning("*** please consider using req_telemetry_sync instead of send_telemetry_req")
169
189
  dst_bytes = _validate_destination(dst, prefix_length=32)
170
190
  logger.debug(f"Asking telemetry to {dst_bytes.hex()}")
171
191
  data = b"\x27\x00\x00\x00" + dst_bytes
172
192
  return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
173
193
 
174
- async def send_path_discovery(self, dst: DestinationType) -> Event:
194
+ async def _send_path_discovery_raw(self, dst: DestinationType) -> Event:
175
195
  dst_bytes = _validate_destination(dst, prefix_length=32)
176
196
  logger.debug(f"Path discovery request for {dst_bytes.hex()}")
177
197
  data = b"\x34\x00" + dst_bytes
178
198
  return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
179
199
 
200
+ async def send_path_discovery(self, dst: DestinationType) -> Event:
201
+ logger.warning("*** please consider using send_path_discovery_sync instead of send_path_discovery")
202
+ return await self._send_path_discovery_raw(dst)
203
+
204
+ async def send_path_discovery_sync(self, dst: DestinationType, timeout=0, min_timeout=0) -> Optional[Event]:
205
+ """Send path discovery request and wait for the response."""
206
+ async with self._mesh_request_lock:
207
+ result = await self._send_path_discovery_raw(dst)
208
+ if result is None or result.type == EventType.ERROR:
209
+ return None
210
+ timeout = result.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
211
+ timeout = timeout if timeout > min_timeout else min_timeout
212
+ path_event = await self.dispatcher.wait_for_event(
213
+ EventType.PATH_RESPONSE,
214
+ timeout=timeout,
215
+ )
216
+ return path_event
217
+
180
218
  async def send_trace(
181
219
  self,
182
220
  auth_code: int = 0,
@@ -108,17 +108,14 @@ class MeshcorePacketParser:
108
108
  channel = c
109
109
  break
110
110
 
111
- chan_name = ""
112
-
113
- if channel is None :
114
- chan_name = chan_hash
115
- else:
116
- chan_name = channel["channel_name"]
117
-
118
111
  log_data["chan_hash"] = chan_hash
119
112
  log_data["cipher_mac"] = cipher_mac.hex()
120
113
  log_data["crypted"] = msg.hex()
121
- log_data["chan_name"] = chan_name
114
+
115
+ chan_name = ""
116
+ if not channel is None :
117
+ chan_name = channel["channel_name"]
118
+ log_data["chan_name"] = chan_name
122
119
 
123
120
  if not channel is None and self.decrypt_channels:
124
121