meshcore 2.2.2__py3-none-any.whl → 2.2.4__py3-none-any.whl
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/commands/base.py +1 -1
- meshcore/commands/device.py +67 -0
- meshcore/events.py +2 -0
- meshcore/reader.py +14 -0
- {meshcore-2.2.2.dist-info → meshcore-2.2.4.dist-info}/METADATA +9 -1
- {meshcore-2.2.2.dist-info → meshcore-2.2.4.dist-info}/RECORD +8 -8
- {meshcore-2.2.2.dist-info → meshcore-2.2.4.dist-info}/WHEEL +1 -1
- {meshcore-2.2.2.dist-info → meshcore-2.2.4.dist-info}/licenses/LICENSE +0 -0
meshcore/commands/base.py
CHANGED
|
@@ -122,7 +122,7 @@ class CommandHandlerBase:
|
|
|
122
122
|
# Create an error event when no event is received
|
|
123
123
|
return Event(EventType.ERROR, {"reason": "no_event_received"})
|
|
124
124
|
except asyncio.TimeoutError:
|
|
125
|
-
logger.debug(f"Command timed out {
|
|
125
|
+
logger.debug(f"Command timed out waiting for events {expected_events}")
|
|
126
126
|
return Event(EventType.ERROR, {"reason": "timeout"})
|
|
127
127
|
except Exception as e:
|
|
128
128
|
logger.debug(f"Command error: {e}")
|
meshcore/commands/device.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
3
|
from hashlib import sha256
|
|
3
4
|
from typing import Optional
|
|
@@ -206,6 +207,72 @@ class DeviceCommands(CommandHandlerBase):
|
|
|
206
207
|
logger.debug("Requesting private key export")
|
|
207
208
|
return await self.send(b"\x17", [EventType.PRIVATE_KEY, EventType.DISABLED, EventType.ERROR])
|
|
208
209
|
|
|
210
|
+
async def import_private_key(self, key) -> Event:
|
|
211
|
+
logger.debug("Requesting private key import")
|
|
212
|
+
data = b"\x18" + key
|
|
213
|
+
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
214
|
+
|
|
215
|
+
async def sign_start(self) -> Event:
|
|
216
|
+
logger.debug("Starting signing session on device")
|
|
217
|
+
return await self.send(b"\x21", [EventType.SIGN_START, EventType.ERROR])
|
|
218
|
+
|
|
219
|
+
async def sign_data(self, chunk: bytes) -> Event:
|
|
220
|
+
if not isinstance(chunk, (bytes, bytearray)):
|
|
221
|
+
raise TypeError("chunk must be bytes-like")
|
|
222
|
+
logger.debug(f"Sending signing data chunk ({len(chunk)} bytes)")
|
|
223
|
+
data = b"\x22" + bytes(chunk)
|
|
224
|
+
result = await self.send(data, [EventType.OK, EventType.ERROR], timeout=5.0)
|
|
225
|
+
|
|
226
|
+
# If we got an error (not just timeout), return it immediately
|
|
227
|
+
if result.type == EventType.ERROR:
|
|
228
|
+
# If it's a timeout/no_event, log a warning but continue - the data may have been received
|
|
229
|
+
if result.payload.get("reason") in ("timeout", "no_event_received"):
|
|
230
|
+
logger.warning(
|
|
231
|
+
f"sign_data OK response not received (timeout), but continuing - "
|
|
232
|
+
f"data may have been processed by device"
|
|
233
|
+
)
|
|
234
|
+
return Event(EventType.OK, {})
|
|
235
|
+
# For actual errors (bad state, table full, etc.), return the error
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
return result
|
|
239
|
+
|
|
240
|
+
async def sign_finish(self, timeout: Optional[float] = None, data_size: int = 0) -> Event:
|
|
241
|
+
logger.debug("Finalizing signing session on device")
|
|
242
|
+
if timeout is None:
|
|
243
|
+
base_timeout = max(self.default_timeout * 3, 15.0)
|
|
244
|
+
size_bonus = min(data_size / 2048.0, 5.0)
|
|
245
|
+
timeout = base_timeout + size_bonus
|
|
246
|
+
logger.debug(f"sign_finish using timeout={timeout:.1f} seconds (data_size={data_size} bytes)")
|
|
247
|
+
return await self.send(b"\x23", [EventType.SIGNATURE, EventType.ERROR], timeout=timeout)
|
|
248
|
+
|
|
249
|
+
async def sign(self, data: bytes, chunk_size: int = 120, timeout: Optional[float] = None) -> Event:
|
|
250
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
251
|
+
raise TypeError("data must be bytes-like")
|
|
252
|
+
if chunk_size <= 0:
|
|
253
|
+
raise ValueError("chunk_size must be > 0")
|
|
254
|
+
|
|
255
|
+
start_evt = await self.sign_start()
|
|
256
|
+
if start_evt.type == EventType.ERROR:
|
|
257
|
+
return start_evt
|
|
258
|
+
|
|
259
|
+
max_len = start_evt.payload.get("max_length", 0)
|
|
260
|
+
if max_len and len(data) > max_len:
|
|
261
|
+
return Event(EventType.ERROR, {"reason": "data_too_large", "max_length": max_len, "len": len(data)})
|
|
262
|
+
|
|
263
|
+
for idx in range(0, len(data), chunk_size):
|
|
264
|
+
chunk = data[idx : idx + chunk_size]
|
|
265
|
+
chunk_num = (idx // chunk_size) + 1
|
|
266
|
+
total_chunks = (len(data) + chunk_size - 1) // chunk_size
|
|
267
|
+
logger.debug(f"Sending chunk {chunk_num}/{total_chunks} ({len(chunk)} bytes)")
|
|
268
|
+
evt = await self.sign_data(chunk)
|
|
269
|
+
if evt.type == EventType.ERROR:
|
|
270
|
+
logger.error(f"Error sending chunk {chunk_num}/{total_chunks}: {evt.payload}")
|
|
271
|
+
return evt
|
|
272
|
+
logger.debug(f"Chunk {chunk_num}/{total_chunks} sent successfully")
|
|
273
|
+
|
|
274
|
+
return await self.sign_finish(timeout=timeout, data_size=len(data))
|
|
275
|
+
|
|
209
276
|
async def get_stats_core(self) -> Event:
|
|
210
277
|
logger.debug("Getting core statistics")
|
|
211
278
|
# CMD_GET_STATS (56) + STATS_TYPE_CORE (0)
|
meshcore/events.py
CHANGED
meshcore/reader.py
CHANGED
|
@@ -710,6 +710,20 @@ class MessageReader:
|
|
|
710
710
|
else:
|
|
711
711
|
logger.error(f"Invalid private key response length: {len(data)}")
|
|
712
712
|
|
|
713
|
+
elif packet_type_value == PacketType.SIGN_START.value:
|
|
714
|
+
logger.debug(f"Received sign start response: {data.hex()}")
|
|
715
|
+
# Payload: 1 reserved byte, 4-byte max length
|
|
716
|
+
dbuf.read(1)
|
|
717
|
+
max_len = int.from_bytes(dbuf.read(4), "little")
|
|
718
|
+
res = {"max_length": max_len}
|
|
719
|
+
await self.dispatcher.dispatch(Event(EventType.SIGN_START, res))
|
|
720
|
+
|
|
721
|
+
elif packet_type_value == PacketType.SIGNATURE.value:
|
|
722
|
+
logger.debug(f"Received signature: {data.hex()}")
|
|
723
|
+
signature = dbuf.read()
|
|
724
|
+
res = {"signature": signature}
|
|
725
|
+
await self.dispatcher.dispatch(Event(EventType.SIGNATURE, res))
|
|
726
|
+
|
|
713
727
|
elif packet_type_value == PacketType.DISABLED.value:
|
|
714
728
|
logger.debug("Received disabled response")
|
|
715
729
|
res = {"reason": "private_key_export_disabled"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
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
|
|
@@ -490,6 +490,8 @@ All events in MeshCore are represented by the `EventType` enum. These events are
|
|
|
490
490
|
| `LOG_DATA` | `"log_data"` | Generic log data | Various log information |
|
|
491
491
|
| **Binary Protocol Events** |||
|
|
492
492
|
| `BINARY_RESPONSE` | `"binary_response"` | Generic binary response | Tag and hex data |
|
|
493
|
+
| `SIGN_START` | `"sign_start"` | Start of an on-device signing session | Maximum buffer size (bytes) for data to sign |
|
|
494
|
+
| `SIGNATURE` | `"signature"` | Resulting on-device signature | Raw signature bytes |
|
|
493
495
|
| **Authentication Events** |||
|
|
494
496
|
| `LOGIN_SUCCESS` | `"login_success"` | Successful login | Permissions, admin status, pubkey prefix |
|
|
495
497
|
| `LOGIN_FAILED` | `"login_failed"` | Failed login attempt | Pubkey prefix |
|
|
@@ -586,6 +588,9 @@ All commands are async methods that return `Event` objects. Commands are organiz
|
|
|
586
588
|
| `req_telemetry(contact, timeout=0)` | `contact: dict, timeout: float` | `TELEMETRY_RESPONSE` | Get telemetry via binary protocol |
|
|
587
589
|
| `req_mma(contact, start, end, timeout=0)` | `contact: dict, start: int, end: int, timeout: float` | `MMA_RESPONSE` | Get historical telemetry data |
|
|
588
590
|
| `req_acl(contact, timeout=0)` | `contact: dict, timeout: float` | `ACL_RESPONSE` | Get access control list |
|
|
591
|
+
| `sign_start()` | None | `SIGN_START` | Begin a signing session; returns maximum buffer size for data to sign |
|
|
592
|
+
| `sign_data(chunk)` | `chunk: bytes` | `OK` | Append a data chunk to the current signing session (can be called multiple times) |
|
|
593
|
+
| `sign_finish()` | None | `SIGNATURE` | Finalize signing and return the signature for all accumulated data |
|
|
589
594
|
|
|
590
595
|
### Helper Methods
|
|
591
596
|
|
|
@@ -593,6 +598,7 @@ All commands are async methods that return `Event` objects. Commands are organiz
|
|
|
593
598
|
|--------|---------|-------------|
|
|
594
599
|
| `get_contact_by_name(name)` | `dict/None` | Find contact by advertisement name |
|
|
595
600
|
| `get_contact_by_key_prefix(prefix)` | `dict/None` | Find contact by partial public key |
|
|
601
|
+
| `sign(data, chunk_size=512)` | `Event` (`SIGNATURE`/`ERROR`) | High-level helper to sign arbitrary data on-device, handling chunking for you |
|
|
596
602
|
| `is_connected` | `bool` | Check if device is currently connected |
|
|
597
603
|
| `subscribe(event_type, callback, filters=None)` | `Subscription` | Subscribe to events with optional filtering |
|
|
598
604
|
| `unsubscribe(subscription)` | None | Remove event subscription |
|
|
@@ -632,6 +638,8 @@ Check the `examples/` directory for more:
|
|
|
632
638
|
- `pubsub_example.py`: Event subscription system with auto-fetching
|
|
633
639
|
- `serial_infos.py`: Quick device info retrieval
|
|
634
640
|
- `serial_msg.py`: Message sending and receiving
|
|
641
|
+
- `serial_pingbot.py`: Ping bot which can be run on a channel
|
|
642
|
+
- `serial_meshcore_ollama.py`: Simple Ollama to Meshcore gateway, a simple chat box
|
|
635
643
|
- `ble_pin_pairing_example.py`: BLE connection with PIN pairing
|
|
636
644
|
- `ble_private_key_export.py`: BLE private key export with PIN authentication
|
|
637
645
|
- `ble_t1000_infos.py`: BLE connections
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
meshcore/__init__.py,sha256=55PdT8gZ9Lf727s7BdSuCt3lIBlAhRzX28A8i0DHaRc,602
|
|
2
2
|
meshcore/ble_cx.py,sha256=S5uanBI88Mxwcs1zWbdQGVJ6GC4kdWz8-Z_2aQVpeGI,6864
|
|
3
3
|
meshcore/connection_manager.py,sha256=3U0TWuHDvL5FfwEwXRy5HjlCyV3nsWhhrEC7TVxFzZI,5899
|
|
4
|
-
meshcore/events.py,sha256=
|
|
4
|
+
meshcore/events.py,sha256=7LFVxsF_U2rdS2ZfNl9QsgYOqQh3rYHYKV50LLg3wkg,8296
|
|
5
5
|
meshcore/lpp_json_encoder.py,sha256=vyn7z3VYWOo_B9xmCzqblh5xCa0QKWKcMi2eOqw18RE,1875
|
|
6
6
|
meshcore/meshcore.py,sha256=n8fH7AEx7DR8BHOYL_Qex9ns6ACKklNNqYbPnNnvqyE,16110
|
|
7
7
|
meshcore/packets.py,sha256=3irQww-HBAe_O03oHPAdp8Z6pwenjsVX_u3F19fHKyc,1294
|
|
8
8
|
meshcore/parsing.py,sha256=48PQkig-sqvsRlkF9zkvWhJoSq6ERCbGb_aRuCND5NI,4044
|
|
9
|
-
meshcore/reader.py,sha256=
|
|
9
|
+
meshcore/reader.py,sha256=1fYsTODW8csC2NxntXHACC4SJv7PlSjtUEazLCcbfTs,34618
|
|
10
10
|
meshcore/serial_cx.py,sha256=-kaqnqk7Ydufu2DECFfPDv4Xs-7gHUBuz8v0xf8fvvE,3969
|
|
11
11
|
meshcore/tcp_cx.py,sha256=05YRVMnjY5aVBTJcHa0uG4VfFKGbV6hQ1pPIsJg4CDI,4227
|
|
12
12
|
meshcore/commands/__init__.py,sha256=qfGPzrha7pZQYHOXLZyDsK-QVRjiz5zxAEj8kMxO9uU,536
|
|
13
|
-
meshcore/commands/base.py,sha256=
|
|
13
|
+
meshcore/commands/base.py,sha256=78_fyKb80Rg4xr_-MRakSh5x2d2dJFwV9db86cQ4iGo,7428
|
|
14
14
|
meshcore/commands/binary.py,sha256=MihRjG4IppPYPeu2KMpctcBPac_hdClp6PgGirQfydg,8412
|
|
15
15
|
meshcore/commands/contact.py,sha256=7X4e17M2YxUZsfUhaN18XZG2inTuXknXJOBAoPblydw,5880
|
|
16
16
|
meshcore/commands/control_data.py,sha256=sXp1RoEw6Z0zPr0Nn5XBovEY6r9sePbWQwsbY0iYyXc,1512
|
|
17
|
-
meshcore/commands/device.py,sha256=
|
|
17
|
+
meshcore/commands/device.py,sha256=LEMEOQyJIcDNh5fBgX9GmFiD2AWTFhm3qaY7MTpQ5Es,12754
|
|
18
18
|
meshcore/commands/messaging.py,sha256=Mglog1xCz_DhKJU1vEv0AD7bBo5_OhEul1MpSY7dnXc,9806
|
|
19
|
-
meshcore-2.2.
|
|
20
|
-
meshcore-2.2.
|
|
21
|
-
meshcore-2.2.
|
|
22
|
-
meshcore-2.2.
|
|
19
|
+
meshcore-2.2.4.dist-info/METADATA,sha256=MCwlYln0nWR6L_kVUp-M5y_ToMN4O31UaGO1O40KK1Q,26169
|
|
20
|
+
meshcore-2.2.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
21
|
+
meshcore-2.2.4.dist-info/licenses/LICENSE,sha256=o62-JWT_C-ZqEtzb1Gl_PPtPr0pVT8KDmgji_Y_bejI,1075
|
|
22
|
+
meshcore-2.2.4.dist-info/RECORD,,
|
|
File without changes
|