pypicoboot 1.4__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.
Files changed (26) hide show
  1. {pypicoboot-1.4 → pypicoboot-2.0}/PKG-INFO +3 -1
  2. pypicoboot-2.0/picoboot/__init__.py +12 -0
  3. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/_version.py +1 -1
  4. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/core/exceptions.py +11 -1
  5. pypicoboot-2.0/picoboot/espboot.py +330 -0
  6. pypicoboot-2.0/picoboot/espbootmonitor.py +79 -0
  7. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/picoboot.py +20 -12
  8. pypicoboot-2.0/picoboot/platform.py +11 -0
  9. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/utils.py +15 -1
  10. {pypicoboot-1.4 → pypicoboot-2.0}/pypicoboot.egg-info/PKG-INFO +3 -1
  11. {pypicoboot-1.4 → pypicoboot-2.0}/pypicoboot.egg-info/SOURCES.txt +3 -0
  12. {pypicoboot-1.4 → pypicoboot-2.0}/pypicoboot.egg-info/requires.txt +2 -0
  13. {pypicoboot-1.4 → pypicoboot-2.0}/pyproject.toml +2 -1
  14. pypicoboot-1.4/picoboot/__init__.py +0 -3
  15. {pypicoboot-1.4 → pypicoboot-2.0}/LICENSE +0 -0
  16. {pypicoboot-1.4 → pypicoboot-2.0}/README.md +0 -0
  17. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/core/__init__.py +0 -0
  18. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/core/enums.py +0 -0
  19. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/core/log.py +0 -0
  20. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/picobootmonitor.py +0 -0
  21. {pypicoboot-1.4 → pypicoboot-2.0}/picoboot/tools/picotool.py +0 -0
  22. {pypicoboot-1.4 → pypicoboot-2.0}/pypicoboot.egg-info/dependency_links.txt +0 -0
  23. {pypicoboot-1.4 → pypicoboot-2.0}/pypicoboot.egg-info/top_level.txt +0 -0
  24. {pypicoboot-1.4 → pypicoboot-2.0}/setup.cfg +0 -0
  25. {pypicoboot-1.4 → pypicoboot-2.0}/setup.py +0 -0
  26. {pypicoboot-1.4 → 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: 1.4
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
+ )
@@ -17,4 +17,4 @@
17
17
  */
18
18
  """
19
19
 
20
- __version__ = "1.4"
20
+ __version__ = "2.0"
@@ -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)
@@ -23,11 +23,12 @@ import usb.core
23
23
  import usb.util
24
24
  import struct
25
25
  import itertools
26
- from .utils import uint_to_int
26
+ from .utils import uint_to_int, crc32_ieee
27
27
  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,13 +128,9 @@ 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
133
+ PHYMARKER = 0x10100000
137
134
 
138
135
  class PicoBoot:
139
136
 
@@ -277,19 +274,30 @@ class PicoBoot:
277
274
  def has_device(self):
278
275
  return self.dev is not None
279
276
 
280
- @property
281
- def serial_number(self) -> int:
282
- s = usb.util.get_string(self.dev, self.dev.iSerialNumber)
283
- return int(s, 16)
284
-
285
277
  @property
286
278
  def serial_number_str(self) -> str:
287
279
  try:
288
- s = usb.util.get_string(self.dev, self.dev.iSerialNumber)
280
+ s = None
281
+ if self.platform == Platform.RP2040:
282
+ r = self.flash_read(Addresses.PHYMARKER, 24)
283
+ magic = struct.unpack_from("<Q", r, 0)[0]
284
+ if (magic == 0x5049434F4B455953): # "PICOKEYS"
285
+ crc32 = crc32_ieee(r[0:20])
286
+ if crc32 == struct.unpack_from("<I", r, 20)[0]:
287
+ s = hexlify(r[12:20]).decode().upper()
288
+ if not s:
289
+ s = usb.util.get_string(self.dev, self.dev.iSerialNumber)
289
290
  except Exception:
290
291
  s = "unknown"
291
292
  return s
292
293
 
294
+ @property
295
+ def serial_number(self) -> int:
296
+ if self.dev is None:
297
+ raise PicoBootInvalidStateError("Device not connected")
298
+ s = self.serial_number_str
299
+ return int(s, 16)
300
+
293
301
  def interface_reset(self) -> None:
294
302
  logger.debug("Resetting interface...")
295
303
  self.dev.ctrl_transfer(
@@ -0,0 +1,11 @@
1
+
2
+ from .core.enums import NamedIntEnum
3
+
4
+
5
+ class Platform(NamedIntEnum):
6
+ RP2040 = 0x01754d
7
+ RP2350 = 0x02754d
8
+ ESP32S3 = 0x03754d
9
+ ESP32S2 = 0x04754d
10
+ ESP32C3 = 0x05754d
11
+ UNKNOWN = 0x000000
@@ -22,4 +22,18 @@ def uint_to_int(value: int, bits: int = 8) -> int:
22
22
  mask = (1 << bits) - 1
23
23
  value &= mask # ensure value fits in `bits`
24
24
  sign_bit = 1 << (bits - 1)
25
- return value - (1 << bits) if (value & sign_bit) else value
25
+ return value - (1 << bits) if (value & sign_bit) else value
26
+
27
+ def crc32_ieee(data: bytes) -> int:
28
+ crc = 0xFFFFFFFF
29
+
30
+ for b in data:
31
+ crc ^= b
32
+ for _ in range(8):
33
+ if crc & 1:
34
+ crc = (crc >> 1) ^ 0xEDB88320
35
+ else:
36
+ crc >>= 1
37
+
38
+ return crc ^ 0xFFFFFFFF
39
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypicoboot
3
- Version: 1.4
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
@@ -1,2 +1,4 @@
1
1
  setuptools
2
2
  pyusb
3
+ esptool
4
+ pyserial
@@ -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
-
@@ -1,3 +0,0 @@
1
- from ._version import __version__
2
- from .picoboot import PicoBoot, Platform
3
- from .core.exceptions import PicoBootError, PicoBootNotFoundError, PicoBootInvalidStateError
File without changes
File without changes
File without changes
File without changes
File without changes