pypicoboot 1.2__py3-none-any.whl → 1.3.1__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.
- picoboot/__init__.py +1 -0
- picoboot/_version.py +1 -1
- picoboot/core/__init__.py +1 -0
- picoboot/core/exceptions.py +28 -0
- picoboot/picoboot.py +43 -23
- {pypicoboot-1.2.dist-info → pypicoboot-1.3.1.dist-info}/METADATA +1 -1
- pypicoboot-1.3.1.dist-info/RECORD +14 -0
- pypicoboot-1.2.dist-info/RECORD +0 -13
- {pypicoboot-1.2.dist-info → pypicoboot-1.3.1.dist-info}/WHEEL +0 -0
- {pypicoboot-1.2.dist-info → pypicoboot-1.3.1.dist-info}/top_level.txt +0 -0
picoboot/__init__.py
CHANGED
picoboot/_version.py
CHANGED
picoboot/core/__init__.py
CHANGED
|
@@ -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
|
picoboot/picoboot.py
CHANGED
|
@@ -27,6 +27,7 @@ from .utils import uint_to_int
|
|
|
27
27
|
from .core.enums import NamedIntEnum
|
|
28
28
|
from .picobootmonitor import PicoBootMonitor, PicoBootMonitorObserver
|
|
29
29
|
from .core.log import get_logger
|
|
30
|
+
from .core.exceptions import PicoBootError, PicoBootNotFoundError, PicoBootInvalidStateError
|
|
30
31
|
|
|
31
32
|
logger = get_logger("PicoBoot")
|
|
32
33
|
|
|
@@ -126,7 +127,7 @@ class PartitionInfoType(NamedIntEnum):
|
|
|
126
127
|
SLOT_1 = -3
|
|
127
128
|
IMAGE = -4
|
|
128
129
|
|
|
129
|
-
class
|
|
130
|
+
class Platform(NamedIntEnum):
|
|
130
131
|
RP2040 = 0x01754d
|
|
131
132
|
RP2350 = 0x02754d
|
|
132
133
|
UNKNOWN = 0x000000
|
|
@@ -134,9 +135,6 @@ class Model(NamedIntEnum):
|
|
|
134
135
|
class Addresses(NamedIntEnum):
|
|
135
136
|
BOOTROM_MAGIC = 0x00000010
|
|
136
137
|
|
|
137
|
-
class PicoBootError(Exception):
|
|
138
|
-
pass
|
|
139
|
-
|
|
140
138
|
class PicoBoot:
|
|
141
139
|
|
|
142
140
|
def __init__(self, dev: usb.core.Device, intf, ep_out, ep_in) -> None:
|
|
@@ -151,9 +149,9 @@ class PicoBoot:
|
|
|
151
149
|
logger.debug("Guessing flash size...")
|
|
152
150
|
self._memory = self._guess_flash_size()
|
|
153
151
|
logger.debug(f"Detected flash size: {self._memory // 1024} kB")
|
|
154
|
-
logger.debug("Determining
|
|
155
|
-
self.
|
|
156
|
-
logger.debug(f"Detected
|
|
152
|
+
logger.debug("Determining platform...")
|
|
153
|
+
self._platform = self._determine_platform()
|
|
154
|
+
logger.debug(f"Detected platform: {self._platform.name}")
|
|
157
155
|
|
|
158
156
|
class PicoBootObserver(PicoBootMonitorObserver):
|
|
159
157
|
|
|
@@ -193,7 +191,7 @@ class PicoBoot:
|
|
|
193
191
|
devices = list(devices) if devices is not None else []
|
|
194
192
|
if not devices:
|
|
195
193
|
logger.error("No device found in PICOBOOT mode")
|
|
196
|
-
raise
|
|
194
|
+
raise PicoBootNotFoundError("No device found in PICOBOOT mode")
|
|
197
195
|
|
|
198
196
|
dev = None
|
|
199
197
|
if serial is None:
|
|
@@ -211,7 +209,7 @@ class PicoBoot:
|
|
|
211
209
|
break
|
|
212
210
|
if dev is None:
|
|
213
211
|
logger.error("No device found with this serial number")
|
|
214
|
-
raise
|
|
212
|
+
raise PicoBootNotFoundError("No device found with this serial number")
|
|
215
213
|
|
|
216
214
|
# Ensure active configuration
|
|
217
215
|
# macOS does not allow detach_kernel_driver, and often returns Access Denied
|
|
@@ -240,7 +238,7 @@ class PicoBoot:
|
|
|
240
238
|
break
|
|
241
239
|
if intf is None:
|
|
242
240
|
logger.error("No interface found with PICOBOOT at the device")
|
|
243
|
-
raise
|
|
241
|
+
raise PicoBootNotFoundError("No interface found with PICOBOOT at the device")
|
|
244
242
|
|
|
245
243
|
#usb.util.claim_interface(dev, intf.bInterfaceNumber)
|
|
246
244
|
|
|
@@ -256,7 +254,7 @@ class PicoBoot:
|
|
|
256
254
|
|
|
257
255
|
if ep_in is None or ep_out is None:
|
|
258
256
|
logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
259
|
-
raise
|
|
257
|
+
raise PicoBootNotFoundError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
|
|
260
258
|
logger.info("PICOBOOT device opened successfully.")
|
|
261
259
|
return cls(dev, intf, ep_out, ep_in)
|
|
262
260
|
|
|
@@ -272,6 +270,16 @@ class PicoBoot:
|
|
|
272
270
|
def has_device(self):
|
|
273
271
|
return self.dev is not None
|
|
274
272
|
|
|
273
|
+
@property
|
|
274
|
+
def serial_number(self) -> int:
|
|
275
|
+
s = usb.util.get_string(self.dev, self.dev.iSerialNumber)
|
|
276
|
+
return int(s, 16)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def serial_number_str(self) -> str:
|
|
280
|
+
s = usb.util.get_string(self.dev, self.dev.iSerialNumber)
|
|
281
|
+
return s
|
|
282
|
+
|
|
275
283
|
def interface_reset(self) -> None:
|
|
276
284
|
logger.debug("Resetting interface...")
|
|
277
285
|
self.dev.ctrl_transfer(
|
|
@@ -348,7 +356,11 @@ class PicoBoot:
|
|
|
348
356
|
logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
|
|
349
357
|
|
|
350
358
|
logger.trace(f"Command header: {hexlify(header).decode()}")
|
|
351
|
-
|
|
359
|
+
try:
|
|
360
|
+
self.ep_out.write(header, timeout=timeout)
|
|
361
|
+
except usb.core.USBError as e:
|
|
362
|
+
logger.error(f"Failed to send command header: {e}")
|
|
363
|
+
raise PicoBootInvalidStateError("Failed to send command header: " + str(e))
|
|
352
364
|
logger.debug(f"Command header sent: {hexlify(header).decode()}")
|
|
353
365
|
|
|
354
366
|
data_in = b""
|
|
@@ -359,7 +371,11 @@ class PicoBoot:
|
|
|
359
371
|
chunks = []
|
|
360
372
|
maxpkt = self.ep_in.wMaxPacketSize
|
|
361
373
|
while remaining > 0:
|
|
362
|
-
|
|
374
|
+
try:
|
|
375
|
+
chunk = bytes(self.ep_in.read(min(maxpkt, remaining), timeout=timeout))
|
|
376
|
+
except usb.core.USBError as e:
|
|
377
|
+
logger.error(f"Failed to read data_in: {e}")
|
|
378
|
+
raise PicoBootInvalidStateError("Failed to read data_in: " + str(e))
|
|
363
379
|
if not chunk:
|
|
364
380
|
break
|
|
365
381
|
chunks.append(chunk)
|
|
@@ -374,7 +390,11 @@ class PicoBoot:
|
|
|
374
390
|
logger.error("data_out missing or too short for OUT command")
|
|
375
391
|
raise ValueError("data_out missing or too short for OUT command")
|
|
376
392
|
logger.trace(f"Sending data_out: {hexlify(data_out[:transfer_length]).decode()}")
|
|
377
|
-
|
|
393
|
+
try:
|
|
394
|
+
self.ep_out.write(data_out[:transfer_length], timeout=timeout)
|
|
395
|
+
except usb.core.USBError as e:
|
|
396
|
+
logger.error(f"Failed to send data_out: {e}")
|
|
397
|
+
raise PicoBootInvalidStateError("Failed to send data_out: " + str(e))
|
|
378
398
|
|
|
379
399
|
try:
|
|
380
400
|
logger.debug("Waiting for ACK...")
|
|
@@ -427,9 +447,9 @@ class PicoBoot:
|
|
|
427
447
|
|
|
428
448
|
def reboot(self, delay_ms: int = 100) -> None:
|
|
429
449
|
logger.debug(f"Rebooting device with delay_ms={delay_ms}")
|
|
430
|
-
if (self.
|
|
450
|
+
if (self.platform == Platform.RP2040):
|
|
431
451
|
self.reboot1(delay_ms=delay_ms)
|
|
432
|
-
elif (self.
|
|
452
|
+
elif (self.platform == Platform.RP2350):
|
|
433
453
|
self.reboot2(delay_ms=delay_ms)
|
|
434
454
|
|
|
435
455
|
def exit_xip(self) -> None:
|
|
@@ -440,17 +460,17 @@ class PicoBoot:
|
|
|
440
460
|
logger.debug("Requesting exclusive access to flash...")
|
|
441
461
|
self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
|
|
442
462
|
|
|
443
|
-
def
|
|
444
|
-
logger.debug("Determining device
|
|
445
|
-
if (hasattr(self, "
|
|
446
|
-
return self.
|
|
463
|
+
def _determine_platform(self) -> str:
|
|
464
|
+
logger.debug("Determining device platform...")
|
|
465
|
+
if (hasattr(self, "_platform")) and (self._platform is not None):
|
|
466
|
+
return self._platform
|
|
447
467
|
data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
|
|
448
468
|
(magic,) = struct.unpack("<I", data)
|
|
449
|
-
return
|
|
469
|
+
return Platform(magic & 0xf0ffffff)
|
|
450
470
|
|
|
451
471
|
@property
|
|
452
|
-
def
|
|
453
|
-
return self.
|
|
472
|
+
def platform(self) -> str:
|
|
473
|
+
return self._platform
|
|
454
474
|
|
|
455
475
|
def _guess_flash_size(self) -> int:
|
|
456
476
|
logger.debug("Guessing flash size...")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
picoboot/__init__.py,sha256=L3OuhBO-lBObxfIWfshCvoncMsyGVNRgmIHfB1--gpg,165
|
|
2
|
+
picoboot/_version.py,sha256=t7RPxTvPfkVX0QdTJfYzldOUtpoKIDCY0wdBshFU7SE,778
|
|
3
|
+
picoboot/picoboot.py,sha256=EzSecCtLccsPi7KhdLvbHfNT9PCnM6ZSLIKkwOx8c6A,25927
|
|
4
|
+
picoboot/picobootmonitor.py,sha256=3msEP2S8ZS7P8QVgKpq9Z9Rcud78XygmG1_0L_OZOvs,2443
|
|
5
|
+
picoboot/utils.py,sha256=SjpAoPZrgyD9Ws7NIFVBHt0zPOb8Lpu2_lsmfeT2rXE,1083
|
|
6
|
+
picoboot/core/__init__.py,sha256=c1kJprnljn0eP1NR3wEw-wJSCSxSRF3DA2jF5MowHXk,57
|
|
7
|
+
picoboot/core/enums.py,sha256=ugXS-dbdnusLib_ge0vT1OugHVG9ZjJfpUR073MhwdQ,1353
|
|
8
|
+
picoboot/core/exceptions.py,sha256=AXilq0ViaXyA6Ls9aGx6DHE9TQyPacQbwnHBXgFD3EQ,909
|
|
9
|
+
picoboot/core/log.py,sha256=XWtN_hgmOtvpDMGDg-9f2BDLUe8elqUAbWti5yCJAA0,1768
|
|
10
|
+
picoboot/tools/picotool.py,sha256=e2xjIxP5UZn9yfjxUWkJXq6KQHyw4YsGbuQbTauo0-Q,94
|
|
11
|
+
pypicoboot-1.3.1.dist-info/METADATA,sha256=h0zIC0YjsXgaSyVsT0W4ZZV1jOaG_T5B7wLi_zNBYOI,41111
|
|
12
|
+
pypicoboot-1.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
pypicoboot-1.3.1.dist-info/top_level.txt,sha256=sjegZQO5-kQdFOXXJHm0P7Hg1Nw4Ri0WKHnRYDTsa4I,9
|
|
14
|
+
pypicoboot-1.3.1.dist-info/RECORD,,
|
pypicoboot-1.2.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
picoboot/__init__.py,sha256=cQPxH_qPx7ph9SQ-Z4Jc2YCU3SKYnDta6wzU-Q65suc,72
|
|
2
|
-
picoboot/_version.py,sha256=j-dtPcfbhr1IMhFhudY5UZVclyBgMpxp0yoDa7RLwT4,776
|
|
3
|
-
picoboot/picoboot.py,sha256=uzmd9W9JLNXCMSk4U0NzCnMCNAc4xnQg7es1lEJeN9A,24831
|
|
4
|
-
picoboot/picobootmonitor.py,sha256=3msEP2S8ZS7P8QVgKpq9Z9Rcud78XygmG1_0L_OZOvs,2443
|
|
5
|
-
picoboot/utils.py,sha256=SjpAoPZrgyD9Ws7NIFVBHt0zPOb8Lpu2_lsmfeT2rXE,1083
|
|
6
|
-
picoboot/core/__init__.py,sha256=fmoYRI4KbzhLjXblEs_H9zgdCs7oDuSKeNEG_-kMgYo,32
|
|
7
|
-
picoboot/core/enums.py,sha256=ugXS-dbdnusLib_ge0vT1OugHVG9ZjJfpUR073MhwdQ,1353
|
|
8
|
-
picoboot/core/log.py,sha256=XWtN_hgmOtvpDMGDg-9f2BDLUe8elqUAbWti5yCJAA0,1768
|
|
9
|
-
picoboot/tools/picotool.py,sha256=e2xjIxP5UZn9yfjxUWkJXq6KQHyw4YsGbuQbTauo0-Q,94
|
|
10
|
-
pypicoboot-1.2.dist-info/METADATA,sha256=GgnGeLWZEcQDGmvC7stKpgCrMxdlbKXF4h1UxGamc30,41109
|
|
11
|
-
pypicoboot-1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
pypicoboot-1.2.dist-info/top_level.txt,sha256=sjegZQO5-kQdFOXXJHm0P7Hg1Nw4Ri0WKHnRYDTsa4I,9
|
|
13
|
-
pypicoboot-1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|