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.
- meshcore-2.3.19/.github/workflows/python-import.yml +24 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/PKG-INFO +1 -1
- {meshcore-2.3.16 → meshcore-2.3.19}/pyproject.toml +1 -1
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/contact.py +29 -50
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/messaging.py +20 -21
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/meshcore.py +21 -4
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/serial_cx.py +5 -3
- {meshcore-2.3.16 → meshcore-2.3.19}/.github/funding.yml +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/.github/python-test.yml +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/.gitignore +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/LICENSE +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/README.md +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/.pubsub_example.py.swp +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_chat.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_pin_pairing_example.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_private_key_export.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_sign_example.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_stats.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_chan_msg.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_custom_vars.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_infos.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_msg.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_msg_retries.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/ble_t1000_set_cv.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/chan_recv_with_path.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/connection_events_example.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/mepo_mc_gps.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/pubsub_example.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/rf_packet_monitor.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_battery_monitor.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_channel_manager.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_chat.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_contacts.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_infos.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_meshcore_ollama.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_msg.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_pingbot.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_repeater_status.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_repeater_telemetry.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_rss_bot.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/serial_trace.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_chat.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_login_status.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_contacts.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_infos.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_msg.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/examples/tcp_mchome_readmsgs.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/pytest.ini +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/__init__.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/ble_cx.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/__init__.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/base.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/binary.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/control_data.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/commands/device.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/connection_manager.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/events.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/lpp_json_encoder.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/meshcore_parser.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/packets.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/parsing.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/reader.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/src/meshcore/tcp_cx.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/README.md +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_ble_connection.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_ble_pin_pairing.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/test_meshcore_ble_pin.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_asyncio_lifecycle.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_ble_reconnect_cleanup.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_commands.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_connection_manager.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_error_handling.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_events.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_lpp_parsing.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_parse_status.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_path_discovery_response.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_private_key_export.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_protocol_surface_gaps.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_reader.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_rxlog_keyerror_regression.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_send_msg_with_retry.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_serial_connection.py +0 -0
- {meshcore-2.3.16 → meshcore-2.3.19}/tests/unit/test_standalone_fixes.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
+
if scope[0] != "#":
|
|
468
|
+
scope = "#" + scope
|
|
469
|
+
scope_key = sha256(scope.encode("utf-8")).digest()[0:16]
|
|
474
470
|
|
|
475
|
-
|
|
471
|
+
logger.debug (f"Setting default scope to {scope} key : {scope_key.hex()}")
|
|
476
472
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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 =
|
|
122
|
-
dtr =
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
47
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|