pypicoboot 1.1.5__tar.gz → 1.3__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.
- {pypicoboot-1.1.5 → pypicoboot-1.3}/PKG-INFO +1 -1
- pypicoboot-1.3/picoboot/__init__.py +3 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/_version.py +1 -1
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/core/__init__.py +1 -0
- pypicoboot-1.3/picoboot/core/exceptions.py +28 -0
- pypicoboot-1.3/picoboot/core/log.py +58 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/picoboot.py +27 -35
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pypicoboot.egg-info/PKG-INFO +1 -1
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pypicoboot.egg-info/SOURCES.txt +2 -0
- pypicoboot-1.1.5/picoboot/__init__.py +0 -2
- {pypicoboot-1.1.5 → pypicoboot-1.3}/LICENSE +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/README.md +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/core/enums.py +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/picobootmonitor.py +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/tools/picotool.py +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/picoboot/utils.py +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pypicoboot.egg-info/dependency_links.txt +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pypicoboot.egg-info/requires.txt +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pypicoboot.egg-info/top_level.txt +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/pyproject.toml +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/setup.cfg +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/setup.py +0 -0
- {pypicoboot-1.1.5 → pypicoboot-1.3}/tests/test_000_init.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
/*
|
|
3
|
+
* This file is part of the pypicoboot distribution (https://github.com/polhenarejos/pypicoboot).
|
|
4
|
+
* Copyright (c) 2025 Pol Henarejos.
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
* the Free Software Foundation, version 3.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful, but
|
|
11
|
+
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
13
|
+
* Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PicoBootError(Exception):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
class PicoBootNotFoundError(PicoBootError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class PicoBootInvalidStateError(PicoBootError):
|
|
28
|
+
pass
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
/*
|
|
3
|
+
* This file is part of the pypicoboot distribution (https://github.com/polhenarejos/pypicoboot).
|
|
4
|
+
* Copyright (c) 2025 Pol Henarejos.
|
|
5
|
+
*
|
|
6
|
+
* This program is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
* the Free Software Foundation, version 3.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful, but
|
|
11
|
+
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
13
|
+
* Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
TRACE_LEVEL = 5
|
|
24
|
+
logging.addLevelName(TRACE_LEVEL, "TRACE")
|
|
25
|
+
|
|
26
|
+
def trace(self, message, *args, **kwargs):
|
|
27
|
+
if self.isEnabledFor(TRACE_LEVEL):
|
|
28
|
+
self._log(TRACE_LEVEL, message, args, **kwargs)
|
|
29
|
+
|
|
30
|
+
# Afegeix logger.trace()
|
|
31
|
+
logging.Logger.trace = trace
|
|
32
|
+
|
|
33
|
+
def get_logger(name: str):
|
|
34
|
+
env_level = os.getenv("PICOBOOT_LOG", "CRITICAL").upper()
|
|
35
|
+
|
|
36
|
+
valid_levels = {
|
|
37
|
+
"TRACE": TRACE_LEVEL,
|
|
38
|
+
"DEBUG": logging.DEBUG,
|
|
39
|
+
"INFO": logging.INFO,
|
|
40
|
+
"WARNING": logging.WARNING,
|
|
41
|
+
"ERROR": logging.ERROR,
|
|
42
|
+
"CRITICAL": logging.CRITICAL,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
level = valid_levels.get(env_level, logging.CRITICAL)
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(name)
|
|
48
|
+
logger.setLevel(level)
|
|
49
|
+
|
|
50
|
+
if not logger.handlers:
|
|
51
|
+
handler = logging.StreamHandler()
|
|
52
|
+
handler.setFormatter(logging.Formatter(
|
|
53
|
+
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
54
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
55
|
+
))
|
|
56
|
+
logger.addHandler(handler)
|
|
57
|
+
|
|
58
|
+
return logger
|
|
@@ -16,31 +16,6 @@
|
|
|
16
16
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
"""
|
|
19
|
-
import os
|
|
20
|
-
import logging
|
|
21
|
-
|
|
22
|
-
def get_logger(name: str):
|
|
23
|
-
env_level = os.getenv("PICOBOOT_LOG", "CRITICAL").upper()
|
|
24
|
-
|
|
25
|
-
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
26
|
-
if env_level not in valid_levels:
|
|
27
|
-
print(f"[logger] Warning: nivell '{env_level}' invàlid. Usant INFO.")
|
|
28
|
-
env_level = "INFO"
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger(name)
|
|
31
|
-
logger.setLevel(env_level)
|
|
32
|
-
|
|
33
|
-
if not logger.handlers:
|
|
34
|
-
handler = logging.StreamHandler()
|
|
35
|
-
handler.setFormatter(logging.Formatter(
|
|
36
|
-
fmt="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
37
|
-
datefmt="%Y-%m-%d %H:%M:%S"
|
|
38
|
-
))
|
|
39
|
-
logger.addHandler(handler)
|
|
40
|
-
|
|
41
|
-
return logger
|
|
42
|
-
|
|
43
|
-
logger = get_logger("picoboot")
|
|
44
19
|
|
|
45
20
|
from binascii import hexlify
|
|
46
21
|
from typing import Optional
|
|
@@ -51,6 +26,10 @@ import itertools
|
|
|
51
26
|
from .utils import uint_to_int
|
|
52
27
|
from .core.enums import NamedIntEnum
|
|
53
28
|
from .picobootmonitor import PicoBootMonitor, PicoBootMonitorObserver
|
|
29
|
+
from .core.log import get_logger
|
|
30
|
+
from .core.exceptions import PicoBootError, PicoBootNotFoundError, PicoBootInvalidStateError
|
|
31
|
+
|
|
32
|
+
logger = get_logger("PicoBoot")
|
|
54
33
|
|
|
55
34
|
# Valors per defecte segons el datasheet (es poden canviar via OTP) :contentReference[oaicite:4]{index=4}
|
|
56
35
|
DEFAULT_VID = 0x2E8A
|
|
@@ -156,9 +135,6 @@ class Model(NamedIntEnum):
|
|
|
156
135
|
class Addresses(NamedIntEnum):
|
|
157
136
|
BOOTROM_MAGIC = 0x00000010
|
|
158
137
|
|
|
159
|
-
class PicoBootError(Exception):
|
|
160
|
-
pass
|
|
161
|
-
|
|
162
138
|
class PicoBoot:
|
|
163
139
|
|
|
164
140
|
def __init__(self, dev: usb.core.Device, intf, ep_out, ep_in) -> None:
|
|
@@ -215,7 +191,7 @@ class PicoBoot:
|
|
|
215
191
|
devices = list(devices) if devices is not None else []
|
|
216
192
|
if not devices:
|
|
217
193
|
logger.error("No device found in PICOBOOT mode")
|
|
218
|
-
raise
|
|
194
|
+
raise PicoBootNotFoundError("No device found in PICOBOOT mode")
|
|
219
195
|
|
|
220
196
|
dev = None
|
|
221
197
|
if serial is None:
|
|
@@ -233,7 +209,7 @@ class PicoBoot:
|
|
|
233
209
|
break
|
|
234
210
|
if dev is None:
|
|
235
211
|
logger.error("No device found with this serial number")
|
|
236
|
-
raise
|
|
212
|
+
raise PicoBootNotFoundError("No device found with this serial number")
|
|
237
213
|
|
|
238
214
|
# Ensure active configuration
|
|
239
215
|
# macOS does not allow detach_kernel_driver, and often returns Access Denied
|
|
@@ -262,7 +238,7 @@ class PicoBoot:
|
|
|
262
238
|
break
|
|
263
239
|
if intf is None:
|
|
264
240
|
logger.error("No interface found with PICOBOOT at the device")
|
|
265
|
-
raise
|
|
241
|
+
raise PicoBootNotFoundError("No interface found with PICOBOOT at the device")
|
|
266
242
|
|
|
267
243
|
#usb.util.claim_interface(dev, intf.bInterfaceNumber)
|
|
268
244
|
|
|
@@ -278,7 +254,7 @@ class PicoBoot:
|
|
|
278
254
|
|
|
279
255
|
if ep_in is None or ep_out is None:
|
|
280
256
|
logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
281
|
-
raise
|
|
257
|
+
raise PicoBootNotFoundError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
282
258
|
logger.info("PICOBOOT device opened successfully.")
|
|
283
259
|
return cls(dev, intf, ep_out, ep_in)
|
|
284
260
|
|
|
@@ -369,7 +345,12 @@ class PicoBoot:
|
|
|
369
345
|
raise
|
|
370
346
|
logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
|
|
371
347
|
|
|
372
|
-
|
|
348
|
+
logger.trace(f"Command header: {hexlify(header).decode()}")
|
|
349
|
+
try:
|
|
350
|
+
self.ep_out.write(header, timeout=timeout)
|
|
351
|
+
except usb.core.USBError as e:
|
|
352
|
+
logger.error(f"Failed to send command header: {e}")
|
|
353
|
+
raise PicoBootInvalidStateError("Failed to send command header: " + str(e))
|
|
373
354
|
logger.debug(f"Command header sent: {hexlify(header).decode()}")
|
|
374
355
|
|
|
375
356
|
data_in = b""
|
|
@@ -380,12 +361,17 @@ class PicoBoot:
|
|
|
380
361
|
chunks = []
|
|
381
362
|
maxpkt = self.ep_in.wMaxPacketSize
|
|
382
363
|
while remaining > 0:
|
|
383
|
-
|
|
364
|
+
try:
|
|
365
|
+
chunk = bytes(self.ep_in.read(min(maxpkt, remaining), timeout=timeout))
|
|
366
|
+
except usb.core.USBError as e:
|
|
367
|
+
logger.error(f"Failed to read data_in: {e}")
|
|
368
|
+
raise PicoBootInvalidStateError("Failed to read data_in: " + str(e))
|
|
384
369
|
if not chunk:
|
|
385
370
|
break
|
|
386
371
|
chunks.append(chunk)
|
|
387
372
|
remaining -= len(chunk)
|
|
388
373
|
data_in = b"".join(chunks)
|
|
374
|
+
logger.trace(f"Received data_in: {hexlify(data_in).decode()}")
|
|
389
375
|
if len(data_in) != transfer_length:
|
|
390
376
|
logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
391
377
|
raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
|
|
@@ -393,7 +379,12 @@ class PicoBoot:
|
|
|
393
379
|
if data_out is None or len(data_out) < transfer_length:
|
|
394
380
|
logger.error("data_out missing or too short for OUT command")
|
|
395
381
|
raise ValueError("data_out missing or too short for OUT command")
|
|
396
|
-
|
|
382
|
+
logger.trace(f"Sending data_out: {hexlify(data_out[:transfer_length]).decode()}")
|
|
383
|
+
try:
|
|
384
|
+
self.ep_out.write(data_out[:transfer_length], timeout=timeout)
|
|
385
|
+
except usb.core.USBError as e:
|
|
386
|
+
logger.error(f"Failed to send data_out: {e}")
|
|
387
|
+
raise PicoBootInvalidStateError("Failed to send data_out: " + str(e))
|
|
397
388
|
|
|
398
389
|
try:
|
|
399
390
|
logger.debug("Waiting for ACK...")
|
|
@@ -404,6 +395,7 @@ class PicoBoot:
|
|
|
404
395
|
except usb.core.USBError:
|
|
405
396
|
logger.error("No ACK received after command")
|
|
406
397
|
raise PicoBootError("No ACK received after command")
|
|
398
|
+
logger.debug("ACK received.")
|
|
407
399
|
|
|
408
400
|
return data_in
|
|
409
401
|
|
|
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
|