meshcore 2.3.16__tar.gz → 2.3.19__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 (84) hide show
  1. meshcore-2.3.19/.github/workflows/python-import.yml +24 -0
  2. {meshcore-2.3.16 → meshcore-2.3.19}/PKG-INFO +1 -1
  3. {meshcore-2.3.16 → meshcore-2.3.19}/pyproject.toml +1 -1
  4. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/contact.py +29 -50
  5. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/messaging.py +20 -21
  6. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/meshcore.py +21 -4
  7. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/serial_cx.py +5 -3
  8. {meshcore-2.3.16 → meshcore-2.3.19}/.github/funding.yml +0 -0
  9. {meshcore-2.3.16 → meshcore-2.3.19}/.github/python-test.yml +0 -0
  10. {meshcore-2.3.16 → meshcore-2.3.19}/.gitignore +0 -0
  11. {meshcore-2.3.16 → meshcore-2.3.19}/LICENSE +0 -0
  12. {meshcore-2.3.16 → meshcore-2.3.19}/README.md +0 -0
  13. {meshcore-2.3.16 → meshcore-2.3.19}/examples/.pubsub_example.py.swp +0 -0
  14. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_chat.py +0 -0
  15. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_pin_pairing_example.py +0 -0
  16. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_private_key_export.py +0 -0
  17. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_sign_example.py +0 -0
  18. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_stats.py +0 -0
  19. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_chan_msg.py +0 -0
  20. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_custom_vars.py +0 -0
  21. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_infos.py +0 -0
  22. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_msg.py +0 -0
  23. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_msg_retries.py +0 -0
  24. {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_set_cv.py +0 -0
  25. {meshcore-2.3.16 → meshcore-2.3.19}/examples/chan_recv_with_path.py +0 -0
  26. {meshcore-2.3.16 → meshcore-2.3.19}/examples/connection_events_example.py +0 -0
  27. {meshcore-2.3.16 → meshcore-2.3.19}/examples/mepo_mc_gps.py +0 -0
  28. {meshcore-2.3.16 → meshcore-2.3.19}/examples/pubsub_example.py +0 -0
  29. {meshcore-2.3.16 → meshcore-2.3.19}/examples/rf_packet_monitor.py +0 -0
  30. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_battery_monitor.py +0 -0
  31. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_channel_manager.py +0 -0
  32. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_chat.py +0 -0
  33. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_contacts.py +0 -0
  34. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_infos.py +0 -0
  35. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_meshcore_ollama.py +0 -0
  36. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_msg.py +0 -0
  37. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_pingbot.py +0 -0
  38. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_repeater_status.py +0 -0
  39. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_repeater_telemetry.py +0 -0
  40. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_rss_bot.py +0 -0
  41. {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_trace.py +0 -0
  42. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_chat.py +0 -0
  43. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_login_status.py +0 -0
  44. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_contacts.py +0 -0
  45. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_infos.py +0 -0
  46. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_msg.py +0 -0
  47. {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_readmsgs.py +0 -0
  48. {meshcore-2.3.16 → meshcore-2.3.19}/pytest.ini +0 -0
  49. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/__init__.py +0 -0
  50. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/ble_cx.py +0 -0
  51. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/__init__.py +0 -0
  52. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/base.py +0 -0
  53. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/binary.py +0 -0
  54. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/control_data.py +0 -0
  55. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/device.py +0 -0
  56. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/connection_manager.py +0 -0
  57. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/events.py +0 -0
  58. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/lpp_json_encoder.py +0 -0
  59. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/meshcore_parser.py +0 -0
  60. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/packets.py +0 -0
  61. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/parsing.py +0 -0
  62. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/reader.py +0 -0
  63. {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/tcp_cx.py +0 -0
  64. {meshcore-2.3.16 → meshcore-2.3.19}/tests/README.md +0 -0
  65. {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_ble_connection.py +0 -0
  66. {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_ble_pin_pairing.py +0 -0
  67. {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_meshcore_ble_pin.py +0 -0
  68. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_asyncio_lifecycle.py +0 -0
  69. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_ble_reconnect_cleanup.py +0 -0
  70. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_commands.py +0 -0
  71. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_connection_manager.py +0 -0
  72. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_error_handling.py +0 -0
  73. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_events.py +0 -0
  74. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_lpp_parsing.py +0 -0
  75. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_parse_status.py +0 -0
  76. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_path_discovery_response.py +0 -0
  77. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_private_key_export.py +0 -0
  78. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_protocol_surface_gaps.py +0 -0
  79. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_reader.py +0 -0
  80. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_rxlog_keyerror_regression.py +0 -0
  81. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_send_msg_with_retry.py +0 -0
  82. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_serial_connection.py +0 -0
  83. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_standalone_fixes.py +0 -0
  84. {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_transport_symmetry.py +0 -0
@@ -0,0 +1,24 @@
1
+ name: Python package import
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+ branches: ["main"]
8
+
9
+ jobs:
10
+ import:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.10", "3.11"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install package
22
+ run: python -m pip install .
23
+ - name: Import installed package
24
+ run: python -c "import meshcore"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: meshcore
3
- Version: 2.3.16
3
+ Version: 2.3.19
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.16"
7
+ version = "2.3.19"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  { name="Alex Wolden", email="awolden@gmail.com" },
@@ -18,60 +18,39 @@ class ContactCommands(CommandHandlerBase):
18
18
  await self.send(data)
19
19
 
20
20
  async def get_contacts(self, lastmod=0, timeout=5) -> Event:
21
- await self.get_contacts_async(lastmod)
22
-
23
- # Inline wait for events to continue waiting for CONTACTS event
24
- # while receiving NEXT_CONTACTs (or it might be missed over serial)
25
- try:
26
- # Create futures for all expected events
27
- futures = []
28
- for event_type in [EventType.ERROR, EventType.NEXT_CONTACT, EventType.CONTACTS] :
29
- future = asyncio.create_task(
30
- self.dispatcher.wait_for_event(event_type, {}, timeout=timeout)
31
- )
32
- futures.append(future)
33
-
34
- while True:
21
+ loop = asyncio.get_running_loop()
22
+ result = loop.create_future()
23
+ state = {"activity": False}
24
+
25
+ def on_next(event):
26
+ state["activity"] = True
27
+
28
+ def on_end(event):
29
+ if not result.done():
30
+ result.set_result(event)
35
31
 
36
- # Wait for the first event to complete or all to timeout
37
- done, pending = await asyncio.wait(
38
- futures, timeout=timeout, return_when=asyncio.FIRST_COMPLETED
39
- )
32
+ subs = [
33
+ self.dispatcher.subscribe(EventType.NEXT_CONTACT, on_next),
34
+ self.dispatcher.subscribe(EventType.CONTACTS, on_end),
35
+ self.dispatcher.subscribe(EventType.ERROR, on_end),
36
+ ]
40
37
 
41
- # Check if any future completed successfully
42
- if len(done) == 0:
38
+ try:
39
+ await self.get_contacts_async(lastmod)
40
+
41
+ while True:
42
+ state["activity"] = False
43
+ try:
44
+ # shield so the timeout doesn't cancel the future itself
45
+ return await asyncio.wait_for(asyncio.shield(result), timeout)
46
+ except asyncio.TimeoutError:
47
+ if state["activity"]:
48
+ continue # still receiving contacts, keep waiting
43
49
  logger.debug("Timeout while getting contacts")
44
- for future in pending: # cancel all futures
45
- future.cancel()
46
50
  return Event(EventType.ERROR, {"reason": "timeout waiting for contacts"})
47
-
48
- for future in done:
49
- event = await future
50
- if event is None:
51
- for f in pending:
52
- f.cancel()
53
- return Event(EventType.ERROR, {"reason": "no event received during contacts retrieval"})
54
- if event.type != EventType.NEXT_CONTACT:
55
- for f in pending:
56
- f.cancel()
57
- return event
58
-
59
- futures = []
60
-
61
- for future in pending: # put back pending
62
- futures.append(future)
63
-
64
- future = asyncio.create_task( # and recreate NEXT_CONTACT
65
- self.dispatcher.wait_for_event(EventType.NEXT_CONTACT, {}, timeout)
66
- )
67
- futures.append(future)
68
-
69
- except asyncio.TimeoutError:
70
- logger.debug(f"Timeout receiving contacts")
71
- return Event(EventType.ERROR, {"reason": "asyncio timeout receiving contacts"})
72
- except Exception as e:
73
- logger.debug(f"Command error: {e}")
74
- return Event(EventType.ERROR, {"error": str(e)})
51
+ finally:
52
+ for s in subs:
53
+ s.unsubscribe()
75
54
 
76
55
  async def reset_path(self, key: DestinationType) -> Event:
77
56
  key_bytes = _validate_destination(key, prefix_length=32)
@@ -404,7 +404,7 @@ class MessagingCommands(CommandHandlerBase):
404
404
  raise ValueError("path must be shorter than 255 bytes")
405
405
  path_len = int(len(path) / (path_hash_mode + 1))
406
406
  path_bytes = bytes([path_len | (path_hash_mode << 6)]) + path
407
- logger.debug(f"Sending raw data {payload.hex()} through path {"Flood" if path is None else path.hex()} of len {"-1" if path is None else path_len} with mode {path_hash_mode} (raw path: {path_bytes.hex()})")
407
+ logger.debug(f"Sending raw data {payload.hex()} through path {'Flood' if path is None else path.hex()} of len {'-1' if path is None else path_len} with mode {path_hash_mode} (raw path: {path_bytes.hex()})")
408
408
  data = bytes([CommandType.SEND_CHANNEL_DATA.value, chan]) + path_bytes + data_type.to_bytes(2, "little") + bytes(payload)
409
409
  return await self.send(data, [EventType.OK, EventType.ERROR])
410
410
 
@@ -454,30 +454,29 @@ class MessagingCommands(CommandHandlerBase):
454
454
  return await self.set_flood_scope(b"", force_unscoped=True)
455
455
 
456
456
  async def set_default_flood_scope(self, scope):
457
- if scope is None:
457
+
458
+ if not (scope is None or isinstance(scope, str)):
459
+ raise TypeError(f"set_flood_scope: unsupported scope type {type(scope).__name__}")
460
+
461
+ cmd_data = bytearray([CommandType.SET_DEFAULT_FLOOD_SCOPE.value])
462
+
463
+ if scope is None or scope == "0" or scope == "None" or scope == "*" or scope == "": # disable
458
464
  logger.debug(f"Resetting default scope")
459
- scope_key = b"\0"*16
460
- scope_name = ""
461
- elif isinstance (scope, str):
462
- if scope == "0" or scope == "None" or scope == "*" or scope == "": # disable
463
- logger.debug ("Resetting default scope")
464
- scope_key = b"\0"*16
465
- scope_name = ""
466
- else:
467
- logger.debug (f"Setting scope to {scope}")
468
- if scope[0] != "#":
469
- scope = "#" + scope
470
- scope_name = scope
471
- scope_key = sha256(scope.encode("utf-8")).digest()[0:16]
465
+
472
466
  else:
473
- raise TypeError(f"set_flood_scope: unsupported scope type {type(scope).__name__}")
467
+ if scope[0] != "#":
468
+ scope = "#" + scope
469
+ scope_key = sha256(scope.encode("utf-8")).digest()[0:16]
474
470
 
475
- logger.debug(f"Setting scope key to {scope_key.hex()}")
471
+ logger.debug (f"Setting default scope to {scope} key : {scope_key.hex()}")
476
472
 
477
- cmd_data = bytearray([CommandType.SET_DEFAULT_FLOOD_SCOPE.value])
478
- cmd_data.extend(scope_name.encode("utf-8"))
479
- cmd_data.extend((31-len(scope))*b'\0')
480
- cmd_data.extend(scope_key)
473
+ name = scope.encode("utf-8")
474
+ if len(name) >= 31:
475
+ raise ValueError("Scope name must be shorter than 31 bytes")
476
+
477
+ cmd_data.extend(name)
478
+ cmd_data.extend((31-len(name))*b'\0')
479
+ cmd_data.extend(scope_key)
481
480
 
482
481
  return await self.send(cmd_data, [EventType.OK, EventType.ERROR])
483
482
 
@@ -118,8 +118,8 @@ class MeshCore:
118
118
  auto_reconnect: bool = False,
119
119
  max_reconnect_attempts: int = 3,
120
120
  cx_dly: float = 0.1,
121
- rts = False,
122
- dtr = True
121
+ rts = None,
122
+ dtr = None
123
123
  ) -> "MeshCore":
124
124
  """Create and connect a MeshCore instance using serial connection"""
125
125
  connection = SerialConnection(port, baudrate, cx_dly=cx_dly, rts=rts, dtr=dtr)
@@ -133,10 +133,27 @@ class MeshCore:
133
133
  max_reconnect_attempts=max_reconnect_attempts,
134
134
  )
135
135
  res = await mc.connect()
136
+ if res is None and dtr is None:
137
+ logger.info("No response from meshcore node, trying to force DTR = True")
138
+ dtr = True
139
+ await mc.disconnect()
140
+ await asyncio.sleep(1)
141
+ connection = SerialConnection(port, baudrate, cx_dly=cx_dly, rts=rts, dtr=dtr)
142
+ mc = cls(
143
+ connection,
144
+ debug=debug,
145
+ only_error=only_error,
146
+ default_timeout=default_timeout,
147
+ auto_reconnect=auto_reconnect,
148
+ max_reconnect_attempts=max_reconnect_attempts,
149
+ )
150
+ res = await mc.connect()
136
151
  if res is None:
137
- logger.info("No response from meshcore node, trying to invert dtr")
152
+ dtr = not dtr
153
+ logger.info(f"No response from meshcore node, trying to invert dtr (now {dtr})")
138
154
  await mc.disconnect()
139
- connection = SerialConnection(port, baudrate, cx_dly=cx_dly, rts=rts, dtr=not dtr)
155
+ await asyncio.sleep(1)
156
+ connection = SerialConnection(port, baudrate, cx_dly=cx_dly, rts=rts, dtr=dtr)
140
157
  mc = cls(
141
158
  connection,
142
159
  debug=debug,
@@ -11,7 +11,7 @@ logger = logging.getLogger("meshcore")
11
11
 
12
12
 
13
13
  class SerialConnection:
14
- def __init__(self, port, baudrate, cx_dly=0.2, rts=False, dtr=True):
14
+ def __init__(self, port, baudrate, cx_dly=0.2, rts=None, dtr=None):
15
15
  self.port = port
16
16
  self.baudrate = baudrate
17
17
  self.transport = None
@@ -43,8 +43,10 @@ class SerialConnection:
43
43
  self.cx.transport = transport
44
44
  logger.debug('port opened')
45
45
  if isinstance(transport, serial_asyncio.SerialTransport) and transport.serial:
46
- transport.serial.dtr = self.cx.dtr # DTR should be deasserted on heltec_v2 to avoid serial-open reset/handshake issues
47
- transport.serial.rts = self.cx.rts
46
+ if not self.cx.dtr is None:
47
+ transport.serial.dtr = self.cx.dtr # DTR should be deasserted on heltec_v2 to avoid serial-open reset/handshake issues
48
+ if not self.cx.rts is None:
49
+ transport.serial.rts = self.cx.rts
48
50
  self.cx._connected_event.set()
49
51
 
50
52
  def data_received(self, data):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes