pypicoboot 1.5__tar.gz → 2.0__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.5 → pypicoboot-2.0}/PKG-INFO +3 -1
- pypicoboot-2.0/picoboot/__init__.py +12 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/_version.py +1 -1
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/core/exceptions.py +11 -1
- pypicoboot-2.0/picoboot/espboot.py +330 -0
- pypicoboot-2.0/picoboot/espbootmonitor.py +79 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/picoboot.py +1 -5
- pypicoboot-2.0/picoboot/platform.py +11 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/pypicoboot.egg-info/PKG-INFO +3 -1
- {pypicoboot-1.5 → pypicoboot-2.0}/pypicoboot.egg-info/SOURCES.txt +3 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/pypicoboot.egg-info/requires.txt +2 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/pyproject.toml +2 -1
- pypicoboot-1.5/picoboot/__init__.py +0 -3
- {pypicoboot-1.5 → pypicoboot-2.0}/LICENSE +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/README.md +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/core/__init__.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/core/enums.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/core/log.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/picobootmonitor.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/tools/picotool.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/picoboot/utils.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/pypicoboot.egg-info/dependency_links.txt +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/pypicoboot.egg-info/top_level.txt +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/setup.cfg +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/setup.py +0 -0
- {pypicoboot-1.5 → pypicoboot-2.0}/tests/test_000_init.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypicoboot
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: Pico Boot for Python
|
|
5
5
|
Home-page: https://github.com/polhenarejos/pypicoboot
|
|
6
6
|
Author: Pol Henarejos
|
|
@@ -682,6 +682,8 @@ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
|
|
|
682
682
|
Description-Content-Type: text/x-rst
|
|
683
683
|
Requires-Dist: setuptools
|
|
684
684
|
Requires-Dist: pyusb
|
|
685
|
+
Requires-Dist: esptool
|
|
686
|
+
Requires-Dist: pyserial
|
|
685
687
|
Dynamic: author
|
|
686
688
|
Dynamic: home-page
|
|
687
689
|
Dynamic: requires-python
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from ._version import __version__
|
|
2
|
+
from .picoboot import PicoBoot, Platform
|
|
3
|
+
from .espboot import EspBoot
|
|
4
|
+
from .platform import Platform
|
|
5
|
+
from .core.exceptions import (
|
|
6
|
+
PicoBootError,
|
|
7
|
+
PicoBootNotFoundError,
|
|
8
|
+
PicoBootInvalidStateError,
|
|
9
|
+
EspBootError,
|
|
10
|
+
EspBootNotFoundError,
|
|
11
|
+
EspBootInvalidStateError,
|
|
12
|
+
)
|
|
@@ -25,4 +25,14 @@ class PicoBootNotFoundError(PicoBootError):
|
|
|
25
25
|
pass
|
|
26
26
|
|
|
27
27
|
class PicoBootInvalidStateError(PicoBootError):
|
|
28
|
-
pass
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EspBootError(Exception):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
class EspBootNotFoundError(EspBootError):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
class EspBootInvalidStateError(EspBootError):
|
|
38
|
+
pass
|
|
@@ -0,0 +1,330 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from contextlib import ExitStack
|
|
23
|
+
from enum import Enum
|
|
24
|
+
from typing import Iterable, Optional
|
|
25
|
+
|
|
26
|
+
from esptool.cmds import attach_flash, detect_chip, flash_id, reset_chip, run_stub, write_flash
|
|
27
|
+
|
|
28
|
+
from .core.log import get_logger
|
|
29
|
+
from .core.exceptions import EspBootError, EspBootNotFoundError
|
|
30
|
+
from .espbootmonitor import EspBootMonitor, EspBootMonitorObserver
|
|
31
|
+
from .platform import Platform
|
|
32
|
+
|
|
33
|
+
logger = get_logger("EspBoot")
|
|
34
|
+
|
|
35
|
+
DEFAULT_CONNECT_ATTEMPTS = 1
|
|
36
|
+
ESP_SERIAL_VIDS = {
|
|
37
|
+
0x303A, # Espressif
|
|
38
|
+
0x10C4, # Silicon Labs CP210x
|
|
39
|
+
0x1A86, # WCH CH34x/CH910x
|
|
40
|
+
0x0403, # FTDI
|
|
41
|
+
0x067B, # Prolific
|
|
42
|
+
}
|
|
43
|
+
ESP_SERIAL_KEYWORDS = ("esp", "cp210", "ch340", "ch910", "ftdi", "silicon labs")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EspBoot:
|
|
47
|
+
def __init__(self, port: str, esp, stack: ExitStack) -> None:
|
|
48
|
+
self.port = port
|
|
49
|
+
self._esp = esp
|
|
50
|
+
self._stack = stack
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def _list_serial_ports() -> list[str]:
|
|
54
|
+
try:
|
|
55
|
+
from serial.tools import list_ports
|
|
56
|
+
except Exception as e: # pragma: no cover
|
|
57
|
+
raise EspBootError("pyserial is required for auto port detection") from e
|
|
58
|
+
ports = list(list_ports.comports())
|
|
59
|
+
if not ports:
|
|
60
|
+
return []
|
|
61
|
+
preferred = []
|
|
62
|
+
others = []
|
|
63
|
+
for p in ports:
|
|
64
|
+
desc = " ".join(filter(None, [
|
|
65
|
+
getattr(p, "description", ""),
|
|
66
|
+
getattr(p, "manufacturer", ""),
|
|
67
|
+
getattr(p, "product", ""),
|
|
68
|
+
])).lower()
|
|
69
|
+
vid = getattr(p, "vid", None)
|
|
70
|
+
if (vid in ESP_SERIAL_VIDS) or any(k in desc for k in ESP_SERIAL_KEYWORDS):
|
|
71
|
+
preferred.append(p.device)
|
|
72
|
+
else:
|
|
73
|
+
others.append(p.device)
|
|
74
|
+
return preferred + others
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _auto_detect_port(cls, stack: ExitStack):
|
|
78
|
+
ports = cls._list_serial_ports()
|
|
79
|
+
if not ports:
|
|
80
|
+
raise EspBootNotFoundError("No serial ports found for ESP32 detection")
|
|
81
|
+
for port in ports:
|
|
82
|
+
try:
|
|
83
|
+
esp = stack.enter_context(detect_chip(port, connect_attempts=DEFAULT_CONNECT_ATTEMPTS))
|
|
84
|
+
logger.debug(f"ESP32 detected on port {port}")
|
|
85
|
+
return port, esp
|
|
86
|
+
except Exception:
|
|
87
|
+
continue
|
|
88
|
+
raise EspBootNotFoundError("No ESP32 device detected. Pass port explicitly.")
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def _is_port_present(cls, port: str) -> bool:
|
|
92
|
+
try:
|
|
93
|
+
ports = cls._list_serial_ports()
|
|
94
|
+
except EspBootError:
|
|
95
|
+
return False
|
|
96
|
+
return port in ports
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def open(cls, port: Optional[str] = None, run_stub_flasher: bool = True) -> "EspBoot":
|
|
100
|
+
stack = ExitStack()
|
|
101
|
+
try:
|
|
102
|
+
try:
|
|
103
|
+
from esptool.logger import log as esptool_log
|
|
104
|
+
esptool_log.set_verbosity("silent")
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
if port is None or port == "auto":
|
|
108
|
+
port, esp = cls._auto_detect_port(stack)
|
|
109
|
+
else:
|
|
110
|
+
esp = stack.enter_context(detect_chip(port, connect_attempts=DEFAULT_CONNECT_ATTEMPTS))
|
|
111
|
+
if run_stub_flasher:
|
|
112
|
+
esp = run_stub(esp)
|
|
113
|
+
attach_flash(esp)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
stack.close()
|
|
116
|
+
raise EspBootError(f"Failed to open ESP32 on port {port}: {e}") from e
|
|
117
|
+
logger.info(f"ESP32 opened on port {port}")
|
|
118
|
+
device = cls(port, esp, stack)
|
|
119
|
+
device._flash_size = None
|
|
120
|
+
try:
|
|
121
|
+
from esptool.cmds import _get_flash_info # type: ignore
|
|
122
|
+
_, _, size = _get_flash_info(esp)
|
|
123
|
+
device._flash_size = cls._flash_size_to_bytes(size)
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
class EspBootObserver(EspBootMonitorObserver):
|
|
128
|
+
def __init__(self, dev: EspBoot):
|
|
129
|
+
self.__device = dev
|
|
130
|
+
|
|
131
|
+
def update(self, actions: tuple[Optional[str], Optional[str]]) -> None:
|
|
132
|
+
(connected, disconnected) = actions
|
|
133
|
+
if connected:
|
|
134
|
+
logger.debug("ESP32 device connected")
|
|
135
|
+
if disconnected:
|
|
136
|
+
logger.debug("ESP32 device disconnected")
|
|
137
|
+
self.__device.close()
|
|
138
|
+
|
|
139
|
+
device.__observer = EspBootObserver(device)
|
|
140
|
+
device.__monitor = EspBootMonitor(port=port, cls_callback=device.__observer)
|
|
141
|
+
return device
|
|
142
|
+
|
|
143
|
+
def close(self) -> None:
|
|
144
|
+
if self._stack is not None:
|
|
145
|
+
if hasattr(self, "_EspBoot__monitor") and self.__monitor is not None:
|
|
146
|
+
self.__monitor.stop()
|
|
147
|
+
self._stack.close()
|
|
148
|
+
if hasattr(self, "_EspBoot__monitor"):
|
|
149
|
+
self.__monitor = None
|
|
150
|
+
self._stack = None
|
|
151
|
+
self._esp = None
|
|
152
|
+
logger.debug("ESP32 connection closed")
|
|
153
|
+
|
|
154
|
+
def __enter__(self) -> "EspBoot":
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
def __exit__(self, exc_type, exc, tb) -> None:
|
|
158
|
+
self.close()
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def chip_name(self) -> str:
|
|
162
|
+
if self._esp is None:
|
|
163
|
+
raise EspBootError("Device not connected")
|
|
164
|
+
return getattr(self._esp, "CHIP_NAME", "unknown")
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def chip_id(self) -> Optional[int]:
|
|
168
|
+
if self._esp is None:
|
|
169
|
+
raise EspBootError("Device not connected")
|
|
170
|
+
get_id = getattr(self._esp, "get_chip_id", None)
|
|
171
|
+
if callable(get_id):
|
|
172
|
+
try:
|
|
173
|
+
return get_id()
|
|
174
|
+
except Exception:
|
|
175
|
+
return None
|
|
176
|
+
return getattr(self._esp, "chip_id", None)
|
|
177
|
+
|
|
178
|
+
def _determine_platform(self) -> Platform:
|
|
179
|
+
name = self.chip_name.lower()
|
|
180
|
+
if "esp32-s3" in name:
|
|
181
|
+
return Platform.ESP32S3
|
|
182
|
+
if "esp32-s2" in name:
|
|
183
|
+
return Platform.ESP32S2
|
|
184
|
+
if "esp32c3" in name:
|
|
185
|
+
return Platform.ESP32C3
|
|
186
|
+
if "esp32" in name:
|
|
187
|
+
return Platform.ESP32
|
|
188
|
+
return Platform.UNKNOWN
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
def platform(self) -> Platform:
|
|
192
|
+
return self._determine_platform()
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def serial_number_str(self) -> str:
|
|
196
|
+
if self._esp is None:
|
|
197
|
+
raise EspBootError("Device not connected")
|
|
198
|
+
mac = None
|
|
199
|
+
if hasattr(self._esp, "read_mac"):
|
|
200
|
+
try:
|
|
201
|
+
mac = self._esp.read_mac()
|
|
202
|
+
except Exception:
|
|
203
|
+
mac = None
|
|
204
|
+
if mac is None:
|
|
205
|
+
raise EspBootError("Unable to read MAC address")
|
|
206
|
+
if isinstance(mac, int):
|
|
207
|
+
mac = mac.to_bytes(6, "big")
|
|
208
|
+
if isinstance(mac, (bytes, bytearray)):
|
|
209
|
+
mac_bytes = mac
|
|
210
|
+
else:
|
|
211
|
+
mac_bytes = bytes(mac)
|
|
212
|
+
mac_bytes = b"\x00" * (8 - len(mac_bytes)) + mac_bytes # Pad to 8 bytes if shorter
|
|
213
|
+
if len(mac_bytes) != 8:
|
|
214
|
+
raise EspBootError("Invalid MAC address length")
|
|
215
|
+
# Return little-endian without separators to match expected format.
|
|
216
|
+
return "".join(f"{b:02X}" for b in mac_bytes[::-1])
|
|
217
|
+
|
|
218
|
+
def get_chip_info(self) -> dict:
|
|
219
|
+
if self._esp is None:
|
|
220
|
+
raise EspBootError("Device not connected")
|
|
221
|
+
info = {
|
|
222
|
+
"chip_name": self.chip_name,
|
|
223
|
+
"chip_id": self.chip_id,
|
|
224
|
+
"platform": self.platform.value,
|
|
225
|
+
"port": self.port,
|
|
226
|
+
}
|
|
227
|
+
get_desc = getattr(self._esp, "get_chip_description", None)
|
|
228
|
+
if callable(get_desc):
|
|
229
|
+
try:
|
|
230
|
+
info["description"] = get_desc()
|
|
231
|
+
except Exception:
|
|
232
|
+
pass
|
|
233
|
+
get_features = getattr(self._esp, "get_chip_features", None)
|
|
234
|
+
if callable(get_features):
|
|
235
|
+
try:
|
|
236
|
+
info["features"] = get_features()
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
return info
|
|
240
|
+
|
|
241
|
+
def get_flash_size(self):
|
|
242
|
+
if self._esp is None:
|
|
243
|
+
raise EspBootError("Device not connected")
|
|
244
|
+
if getattr(self, "_flash_size", None) is not None:
|
|
245
|
+
return self._flash_size
|
|
246
|
+
size = None
|
|
247
|
+
try:
|
|
248
|
+
from esptool.cmds import _get_flash_info # type: ignore
|
|
249
|
+
except Exception:
|
|
250
|
+
_get_flash_info = None
|
|
251
|
+
if _get_flash_info is not None:
|
|
252
|
+
try:
|
|
253
|
+
_, _, size = _get_flash_info(self._esp)
|
|
254
|
+
except Exception:
|
|
255
|
+
size = None
|
|
256
|
+
if size is None:
|
|
257
|
+
for attr in ("flash_size", "FLASH_SIZE", "FLASH_SIZE_BYTES"):
|
|
258
|
+
if hasattr(self._esp, attr):
|
|
259
|
+
size = getattr(self._esp, attr)
|
|
260
|
+
break
|
|
261
|
+
if size is None:
|
|
262
|
+
try:
|
|
263
|
+
flash_id(self._esp) # Emits detected flash size via esptool logger.
|
|
264
|
+
except Exception:
|
|
265
|
+
pass
|
|
266
|
+
raise EspBootError("Unable to determine flash size programmatically")
|
|
267
|
+
size = self._flash_size_to_bytes(size)
|
|
268
|
+
if size is None:
|
|
269
|
+
raise EspBootError("Unable to determine flash size programmatically")
|
|
270
|
+
self._flash_size = size
|
|
271
|
+
return size
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def memory(self) -> Optional[int]:
|
|
275
|
+
try:
|
|
276
|
+
return self.get_flash_size()
|
|
277
|
+
except EspBootError:
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
@staticmethod
|
|
281
|
+
def _flash_size_to_bytes(size):
|
|
282
|
+
if size is None:
|
|
283
|
+
return None
|
|
284
|
+
if isinstance(size, int):
|
|
285
|
+
return size
|
|
286
|
+
if isinstance(size, str):
|
|
287
|
+
s = size.strip().upper()
|
|
288
|
+
try:
|
|
289
|
+
if s.endswith("MB"):
|
|
290
|
+
return int(s[:-2]) * 1024 * 1024
|
|
291
|
+
if s.endswith("KB"):
|
|
292
|
+
return int(s[:-2]) * 1024
|
|
293
|
+
if s.endswith("B"):
|
|
294
|
+
return int(s[:-1])
|
|
295
|
+
return int(s)
|
|
296
|
+
except Exception:
|
|
297
|
+
return None
|
|
298
|
+
try:
|
|
299
|
+
return int(size)
|
|
300
|
+
except Exception:
|
|
301
|
+
return None
|
|
302
|
+
|
|
303
|
+
def is_connected(self) -> bool:
|
|
304
|
+
if self.port is None:
|
|
305
|
+
return False
|
|
306
|
+
return self._is_port_present(self.port)
|
|
307
|
+
|
|
308
|
+
def write_flash_files(self, segments: Iterable[tuple[int, str]]) -> None:
|
|
309
|
+
if self._esp is None:
|
|
310
|
+
raise EspBootError("Device not connected")
|
|
311
|
+
opened = []
|
|
312
|
+
try:
|
|
313
|
+
for addr, path in segments:
|
|
314
|
+
f = open(path, "rb")
|
|
315
|
+
opened.append((addr, f))
|
|
316
|
+
write_flash(self._esp, opened)
|
|
317
|
+
finally:
|
|
318
|
+
for _, f in opened:
|
|
319
|
+
try:
|
|
320
|
+
f.close()
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
def reset(self, mode: str = "hard-reset") -> None:
|
|
325
|
+
if self._esp is None:
|
|
326
|
+
raise EspBootError("Device not connected")
|
|
327
|
+
try:
|
|
328
|
+
reset_chip(self._esp, mode)
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
@@ -0,0 +1,79 @@
|
|
|
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 threading
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
from serial.tools import list_ports
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EspBootMonitorObserver:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def notifyObservers(self, actions):
|
|
31
|
+
func = getattr(self, "update", None)
|
|
32
|
+
if callable(func):
|
|
33
|
+
func(actions)
|
|
34
|
+
|
|
35
|
+
def on_connect(self, port):
|
|
36
|
+
self.notifyObservers((port, None))
|
|
37
|
+
|
|
38
|
+
def on_disconnect(self, port):
|
|
39
|
+
self.notifyObservers((None, port))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EspBootMonitor:
|
|
43
|
+
def __init__(self, port, cls_callback: EspBootMonitorObserver, interval=0.5):
|
|
44
|
+
self._port = port
|
|
45
|
+
self._cls_callback = cls_callback
|
|
46
|
+
self.interval = interval
|
|
47
|
+
self._running = False
|
|
48
|
+
self._device_present = False
|
|
49
|
+
self._thread = None
|
|
50
|
+
self.start()
|
|
51
|
+
|
|
52
|
+
def start(self):
|
|
53
|
+
if self._running:
|
|
54
|
+
return
|
|
55
|
+
self._running = True
|
|
56
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
57
|
+
self._thread.start()
|
|
58
|
+
|
|
59
|
+
def stop(self):
|
|
60
|
+
self._running = False
|
|
61
|
+
# if self._thread:
|
|
62
|
+
# self._thread.join()
|
|
63
|
+
|
|
64
|
+
def _run(self):
|
|
65
|
+
while self._running:
|
|
66
|
+
ports = [p.device for p in list_ports.comports()]
|
|
67
|
+
present = self._port in ports
|
|
68
|
+
|
|
69
|
+
if present and not self._device_present:
|
|
70
|
+
self._device_present = True
|
|
71
|
+
if self._cls_callback:
|
|
72
|
+
self._cls_callback.on_connect(self._port)
|
|
73
|
+
|
|
74
|
+
if not present and self._device_present:
|
|
75
|
+
self._device_present = False
|
|
76
|
+
if self._cls_callback:
|
|
77
|
+
self._cls_callback.on_disconnect(self._port)
|
|
78
|
+
|
|
79
|
+
time.sleep(self.interval)
|
|
@@ -28,6 +28,7 @@ from .core.enums import NamedIntEnum
|
|
|
28
28
|
from .picobootmonitor import PicoBootMonitor, PicoBootMonitorObserver
|
|
29
29
|
from .core.log import get_logger
|
|
30
30
|
from .core.exceptions import PicoBootError, PicoBootNotFoundError, PicoBootInvalidStateError
|
|
31
|
+
from .platform import Platform
|
|
31
32
|
|
|
32
33
|
logger = get_logger("PicoBoot")
|
|
33
34
|
|
|
@@ -127,11 +128,6 @@ class PartitionInfoType(NamedIntEnum):
|
|
|
127
128
|
SLOT_1 = -3
|
|
128
129
|
IMAGE = -4
|
|
129
130
|
|
|
130
|
-
class Platform(NamedIntEnum):
|
|
131
|
-
RP2040 = 0x01754d
|
|
132
|
-
RP2350 = 0x02754d
|
|
133
|
-
UNKNOWN = 0x000000
|
|
134
|
-
|
|
135
131
|
class Addresses(NamedIntEnum):
|
|
136
132
|
BOOTROM_MAGIC = 0x00000010
|
|
137
133
|
PHYMARKER = 0x10100000
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypicoboot
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: Pico Boot for Python
|
|
5
5
|
Home-page: https://github.com/polhenarejos/pypicoboot
|
|
6
6
|
Author: Pol Henarejos
|
|
@@ -682,6 +682,8 @@ Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
|
|
|
682
682
|
Description-Content-Type: text/x-rst
|
|
683
683
|
Requires-Dist: setuptools
|
|
684
684
|
Requires-Dist: pyusb
|
|
685
|
+
Requires-Dist: esptool
|
|
686
|
+
Requires-Dist: pyserial
|
|
685
687
|
Dynamic: author
|
|
686
688
|
Dynamic: home-page
|
|
687
689
|
Dynamic: requires-python
|
|
@@ -4,8 +4,11 @@ pyproject.toml
|
|
|
4
4
|
setup.py
|
|
5
5
|
picoboot/__init__.py
|
|
6
6
|
picoboot/_version.py
|
|
7
|
+
picoboot/espboot.py
|
|
8
|
+
picoboot/espbootmonitor.py
|
|
7
9
|
picoboot/picoboot.py
|
|
8
10
|
picoboot/picobootmonitor.py
|
|
11
|
+
picoboot/platform.py
|
|
9
12
|
picoboot/utils.py
|
|
10
13
|
picoboot/core/__init__.py
|
|
11
14
|
picoboot/core/enums.py
|
|
@@ -16,6 +16,8 @@ authors = [
|
|
|
16
16
|
dependencies = [
|
|
17
17
|
"setuptools",
|
|
18
18
|
"pyusb",
|
|
19
|
+
"esptool",
|
|
20
|
+
"pyserial",
|
|
19
21
|
]
|
|
20
22
|
|
|
21
23
|
classifiers = [
|
|
@@ -45,4 +47,3 @@ include-package-data = true
|
|
|
45
47
|
[tool.setuptools.dynamic]
|
|
46
48
|
version = { attr = "picoboot._version.__version__" }
|
|
47
49
|
readme = { file = "README.md" }
|
|
48
|
-
|
|
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
|