pypicoboot 1.1.1__py3-none-any.whl → 1.1.3__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/_version.py CHANGED
@@ -17,4 +17,4 @@
17
17
  */
18
18
  """
19
19
 
20
- __version__ = "1.1.1"
20
+ __version__ = "1.1.3"
picoboot/picoboot.py CHANGED
@@ -16,6 +16,31 @@
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")
19
44
 
20
45
  from binascii import hexlify
21
46
  from typing import Optional
@@ -137,15 +162,21 @@ class PicoBootError(Exception):
137
162
  class PicoBoot:
138
163
 
139
164
  def __init__(self, dev: usb.core.Device, intf, ep_out, ep_in) -> None:
140
- print("Initializing PicoBoot device...")
165
+ logger.info("Initializing PicoBoot device...")
141
166
  self.dev = dev
142
167
  self.intf = intf
143
168
  self.ep_out = ep_out
144
169
  self.ep_in = ep_in
145
170
  self._token_counter = itertools.count(1)
171
+ logger.debug("Resetting interface...")
146
172
  self.interface_reset()
173
+ logger.debug("Guessing flash size...")
147
174
  self._memory = self._guess_flash_size()
175
+ logger.debug(f"Detected flash size: {self._memory // 1024} kB")
176
+ logger.debug("Determining model...")
148
177
  self._model = self._determine_model()
178
+ logger.debug(f"Detected model: {self._model.name}")
179
+
149
180
  class PicoBootObserver(PicoBootMonitorObserver):
150
181
 
151
182
  def __init__(self, device: PicoBoot):
@@ -154,32 +185,41 @@ class PicoBoot:
154
185
  def update(self, actions: tuple[list[PicoBoot], list[PicoBoot]]) -> None:
155
186
  (connected, disconnected) = actions
156
187
  if connected:
188
+ logger.debug("PicoBoot device connected")
157
189
  pass
158
190
  if disconnected:
191
+ logger.debug("PicoBoot device disconnected")
159
192
  self.__device.close()
160
193
 
194
+ logger.debug("Starting PicoBoot monitor...")
161
195
  self.__observer = PicoBootObserver(self)
196
+ logger.debug("PicoBoot monitor started.")
162
197
  self.__monitor = PicoBootMonitor(device=self.dev, cls_callback=self.__observer)
198
+ logger.debug("PicoBoot device initialized.")
163
199
 
164
200
  @classmethod
165
201
  def open(cls, vid: int = DEFAULT_VID, pid: list[int] = [DEFAULT_PID_RP2040, DEFAULT_PID_RP2350], serial: Optional[str] = None) -> "PicoBoot":
166
- class find_pids(object):
202
+ logger.info(f"Opening PicoBoot device with VID={vid:04x} and PIDs={[f'{p:04x}' for p in pid]}...")
203
+ class find_vidpids(object):
167
204
 
168
- def __init__(self, pids: list[int]):
205
+ def __init__(self, vid: int, pids: list[int]):
206
+ self._vid = vid
169
207
  self._pids = pids
170
208
 
171
209
  def __call__(self, device: usb.core.Device) -> bool:
172
- if device.idProduct in self._pids:
210
+ if device.idProduct in self._pids and device.idVendor == self._vid:
173
211
  return True
174
212
  return False
175
213
 
176
- devices = usb.core.find(find_all=True, custom_match=find_pids(pid))
214
+ devices = usb.core.find(find_all=True, custom_match=find_vidpids(vid, pid))
177
215
  devices = list(devices) if devices is not None else []
178
216
  if not devices:
217
+ logger.error("No device found in PICOBOOT mode")
179
218
  raise PicoBootError("No device found in PICOBOOT mode")
180
219
 
181
220
  dev = None
182
221
  if serial is None:
222
+ logger.info("No serial number provided, using the first device found.")
183
223
  dev = devices[0]
184
224
  else:
185
225
  for d in devices:
@@ -189,15 +229,20 @@ class PicoBoot:
189
229
  continue
190
230
  if s == serial:
191
231
  dev = d
232
+ logger.debug(f"Using device with serial number: {serial}")
192
233
  break
193
234
  if dev is None:
235
+ logger.error("No device found with this serial number")
194
236
  raise PicoBootError("No device found with this serial number")
195
237
 
196
238
  # Ensure active configuration
197
239
  # macOS does not allow detach_kernel_driver, and often returns Access Denied
198
240
  try:
199
- if dev.is_kernel_driver_active(0):
200
- dev.detach_kernel_driver(0)
241
+ logger.debug("Checking if kernel driver is active and detaching if necessary...")
242
+ if dev.is_kernel_driver_active(1):
243
+ logger.debug("Kernel driver is active, detaching...")
244
+ dev.detach_kernel_driver(1)
245
+ logger.debug("Kernel driver detached.")
201
246
  except usb.core.USBError:
202
247
  # If it fails, we continue anyway. It's normal on macOS.
203
248
  pass
@@ -205,8 +250,10 @@ class PicoBoot:
205
250
  # Also fine on backends that don't implement the function
206
251
  pass
207
252
 
208
- dev.set_configuration()
253
+ #dev.set_configuration()
254
+ logger.debug("Getting active configuration...")
209
255
  cfg = dev.get_active_configuration()
256
+ logger.debug("Searching for PICOBOOT interface...")
210
257
 
211
258
  intf = None
212
259
  for i in cfg:
@@ -214,31 +261,41 @@ class PicoBoot:
214
261
  intf = i
215
262
  break
216
263
  if intf is None:
264
+ logger.error("No interface found with PICOBOOT at the device")
217
265
  raise PicoBootError("No interface found with PICOBOOT at the device")
218
266
 
219
267
  #usb.util.claim_interface(dev, intf.bInterfaceNumber)
220
268
 
269
+ logger.debug("Finding BULK_IN and BULK_OUT endpoints...")
221
270
  ep_in = ep_out = None
222
271
  for ep in intf.endpoints():
223
272
  if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
273
+ logger.debug(f"Found BULK_IN endpoint: 0x{ep.bEndpointAddress:02X}")
224
274
  ep_in = ep
225
275
  else:
276
+ logger.debug(f"Found BULK_OUT endpoint: 0x{ep.bEndpointAddress:02X}")
226
277
  ep_out = ep
227
278
 
228
279
  if ep_in is None or ep_out is None:
280
+ logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
229
281
  raise PicoBootError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
230
-
282
+ logger.info("PICOBOOT device opened successfully.")
231
283
  return cls(dev, intf, ep_out, ep_in)
232
284
 
233
285
  def close(self):
286
+ logger.debug("Closing PicoBoot device...")
234
287
  if self.dev:
288
+ self.__monitor.stop()
289
+ logger.debug("Releasing USB resources...")
235
290
  usb.util.dispose_resources(self.dev)
291
+ logger.debug("PicoBoot device closed.")
236
292
  self.dev = None
237
293
 
238
294
  def has_device(self):
239
295
  return self.dev is not None
240
296
 
241
297
  def interface_reset(self) -> None:
298
+ logger.debug("Resetting interface...")
242
299
  self.dev.ctrl_transfer(
243
300
  ControlRequest.BMREQ_RESET,
244
301
  ControlRequest.REQ_INTERFACE_RESET,
@@ -246,8 +303,10 @@ class PicoBoot:
246
303
  self.intf.bInterfaceNumber,
247
304
  None
248
305
  )
306
+ logger.debug("Interface reset command sent.")
249
307
 
250
308
  def get_command_status(self) -> dict:
309
+ logger.debug("Getting command status...")
251
310
  data = self.dev.ctrl_transfer(
252
311
  ControlRequest.BMREQ_GET_STATUS,
253
312
  ControlRequest.REQ_GET_COMMAND_STATUS,
@@ -255,6 +314,7 @@ class PicoBoot:
255
314
  self.intf.bInterfaceNumber,
256
315
  16,
257
316
  )
317
+ logger.debug(f"Command status data: {hexlify(data).decode()}")
258
318
  b = bytes(data)
259
319
  dToken, dStatusCode = struct.unpack_from("<II", b, 0)
260
320
  bCmdId = b[8]
@@ -302,9 +362,10 @@ class PicoBoot:
302
362
  transfer_length = 0 if data_out is None else len(data_out)
303
363
 
304
364
  token, header = self._build_command(cmd_id, args=args, transfer_length=transfer_length)
305
- print(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
365
+ logger.debug(f"Sending command {cmd_id} (0x{cmd_id:02X}) with token {token} (0x{token:08X}) and transfer_length {transfer_length}")
306
366
 
307
367
  self.ep_out.write(header, timeout=timeout)
368
+ logger.debug(f"Command header sent: {hexlify(header).decode()}")
308
369
 
309
370
  data_in = b""
310
371
 
@@ -321,63 +382,79 @@ class PicoBoot:
321
382
  remaining -= len(chunk)
322
383
  data_in = b"".join(chunks)
323
384
  if len(data_in) != transfer_length:
385
+ logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
324
386
  raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
325
387
  else:
326
388
  if data_out is None or len(data_out) < transfer_length:
389
+ logger.error("data_out missing or too short for OUT command")
327
390
  raise ValueError("data_out missing or too short for OUT command")
328
391
  self.ep_out.write(data_out[:transfer_length], timeout=timeout)
329
392
 
330
393
  try:
394
+ logger.debug("Waiting for ACK...")
331
395
  if is_in:
332
396
  self.ep_out.write(b"", timeout=timeout)
333
397
  else:
334
398
  ack = self.ep_in.read(1, timeout=timeout)
335
399
  except usb.core.USBError:
400
+ logger.error("No ACK received after command")
336
401
  raise PicoBootError("No ACK received after command")
337
402
 
338
403
  return data_in
339
404
 
340
405
 
341
406
  def flash_erase(self, addr: int, size: int) -> None:
407
+ logger.debug(f"Erasing flash at address 0x{addr:08X} with size {size} bytes")
342
408
  if addr % 4096 != 0 or size % 4096 != 0:
409
+ logger.error("addr i size must be aligned to 4kB")
343
410
  raise ValueError("addr i size must be aligned to 4kB")
344
411
  args = struct.pack("<II", addr, size)
345
412
  self._send_command(CommandID.FLASH_ERASE, args=args, transfer_length=0)
346
413
 
347
414
  def flash_read(self, addr: int, size: int) -> bytes:
415
+ logger.debug(f"Reading flash at address 0x{addr:08X} with size {size} bytes")
348
416
  args = struct.pack("<II", addr, size)
349
417
  data = self._send_command(CommandID.READ, args=args, transfer_length=size)
350
418
  if len(data) != size:
419
+ logger.error(f"READ returned {len(data)} bytes, expected {size}")
351
420
  raise PicoBootError(f"READ returned {len(data)} bytes, expected {size}")
352
421
  return data
353
422
 
354
423
  def flash_write(self, addr: int, data: bytes) -> None:
424
+ logger.debug(f"Writing flash at address 0x{addr:08X} with size {len(data)} bytes")
355
425
  if addr % 256 != 0 or len(data) % 256 != 0:
426
+ logger.error("addr i len(data) must be aligned/multiple of 256 bytes")
356
427
  raise ValueError("addr i len(data) must be aligned/multiple of 256 bytes")
357
428
  args = struct.pack("<II", addr, len(data))
358
429
  self._send_command(CommandID.WRITE, args=args, data_out=data, transfer_length=len(data))
359
430
 
360
431
  def reboot1(self, pc: int = 0, sp: int = 0, delay_ms: int = 0) -> None:
432
+ logger.debug(f"Rebooting device (REBOOT1) with pc=0x{pc:08X}, sp=0x{sp:08X}, delay_ms={delay_ms}")
361
433
  args = struct.pack("<III", pc, sp, delay_ms)
362
434
  self._send_command(CommandID.REBOOT, args=args, transfer_length=0)
363
435
 
364
436
  def reboot2(self, flags: int = 0, delay_ms: int = 0, p0: int = 0, p1: int = 0) -> None:
437
+ logger.debug(f"Rebooting device (REBOOT2) with flags=0x{flags:08X}, delay_ms={delay_ms}, p0=0x{p0:08X}, p1=0x{p1:08X}")
365
438
  args = struct.pack("<IIII", flags, delay_ms, p0, p1)
366
439
  self._send_command(CommandID.REBOOT2, args=args, transfer_length=0)
367
440
 
368
441
  def reboot(self, delay_ms: int = 100) -> None:
442
+ logger.debug(f"Rebooting device with delay_ms={delay_ms}")
369
443
  if (self.model == Model.RP2040):
370
444
  self.reboot1(delay_ms=delay_ms)
371
445
  elif (self.model == Model.RP2350):
372
446
  self.reboot2(delay_ms=delay_ms)
373
447
 
374
448
  def exit_xip(self) -> None:
449
+ logger.debug("Exiting XIP mode...")
375
450
  self._send_command(CommandID.EXIT_XIP, transfer_length=0)
376
451
 
377
452
  def exclusive_access(self) -> None:
453
+ logger.debug("Requesting exclusive access to flash...")
378
454
  self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
379
455
 
380
456
  def _determine_model(self) -> str:
457
+ logger.debug("Determining device model...")
381
458
  if (hasattr(self, "_model")) and (self._model is not None):
382
459
  return self._model
383
460
  data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
@@ -389,6 +466,7 @@ class PicoBoot:
389
466
  return self._model
390
467
 
391
468
  def _guess_flash_size(self) -> int:
469
+ logger.debug("Guessing flash size...")
392
470
  if (hasattr(self, "_memory")) and (self._memory is not None):
393
471
  return self._memory
394
472
  FLASH_BASE = 0x10000000
@@ -427,6 +505,7 @@ class PicoBoot:
427
505
  return self._memory
428
506
 
429
507
  def get_info(self, info_type: InfoType, param0: int = 0, max_len: int = 32) -> bytes:
508
+ logger.debug(f"Getting info of type {info_type} with param0={param0} and max_len={max_len}")
430
509
  args = struct.pack("<IIII", info_type, param0, 0, 0)
431
510
  data = self._send_command(CommandID.GET_INFO, args=args, transfer_length=max_len)
432
511
  return data
@@ -454,6 +533,7 @@ class PicoBoot:
454
533
  }
455
534
 
456
535
  def get_info_sys(self, flags: SysInfoFlags = SysInfoFlags.CHIP_INFO | SysInfoFlags.CRITICAL | SysInfoFlags.CPU | SysInfoFlags.FLASH | SysInfoFlags.BOOT_RANDOM | SysInfoFlags.BOOT_INFO) -> dict:
536
+ logger.debug(f"Getting system info with flags: {flags}")
457
537
  data = self.get_info(InfoType.SYS, param0=flags, max_len=256)
458
538
  if len(data) < 24:
459
539
  raise PicoBootError("INFO_SYS response too short")
@@ -55,8 +55,8 @@ class PicoBootMonitor:
55
55
 
56
56
  def stop(self):
57
57
  self._running = False
58
- if self._thread:
59
- self._thread.join()
58
+ #if self._thread:
59
+ # self._thread.join()
60
60
 
61
61
  def _run(self):
62
62
  while self._running:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypicoboot
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: Pico Boot for Python
5
5
  Home-page: https://github.com/polhenarejos/pypicoboot
6
6
  Author: Pol Henarejos
@@ -0,0 +1,12 @@
1
+ picoboot/__init__.py,sha256=cQPxH_qPx7ph9SQ-Z4Jc2YCU3SKYnDta6wzU-Q65suc,72
2
+ picoboot/_version.py,sha256=FjzHapAlay2lqwhKnlSSCkLPonTSJdSVFJvqRgarBRs,778
3
+ picoboot/picoboot.py,sha256=GwL6DFVQSzdq7NvZE7zv_PT9mPG357j2GlHuPJoezus,24938
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=YQnCtiX3XwG5RtAIhTmD37DMZMEGmcPIaTiwsW3ckxw,1211
8
+ picoboot/tools/picotool.py,sha256=e2xjIxP5UZn9yfjxUWkJXq6KQHyw4YsGbuQbTauo0-Q,94
9
+ pypicoboot-1.1.3.dist-info/METADATA,sha256=bZg7x1-8o35H9oivMY5YPpa5_qKH9ntA2ZGhuguBTjs,41111
10
+ pypicoboot-1.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ pypicoboot-1.1.3.dist-info/top_level.txt,sha256=sjegZQO5-kQdFOXXJHm0P7Hg1Nw4Ri0WKHnRYDTsa4I,9
12
+ pypicoboot-1.1.3.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- picoboot/__init__.py,sha256=cQPxH_qPx7ph9SQ-Z4Jc2YCU3SKYnDta6wzU-Q65suc,72
2
- picoboot/_version.py,sha256=k8A7e2mGIRIiAmo76lyo9co0bfMZ58OCr5wJFaZxkgc,778
3
- picoboot/picoboot.py,sha256=tvw-RnhVCADQNVfvRru-x3gCmqLzD-BWHNACxjgNe1Y,20446
4
- picoboot/picobootmonitor.py,sha256=xZi8cr3Rbxz02LA1vMPxfIwO9kB_s5dQltHfJ88Gzec,2441
5
- picoboot/utils.py,sha256=SjpAoPZrgyD9Ws7NIFVBHt0zPOb8Lpu2_lsmfeT2rXE,1083
6
- picoboot/core/__init__.py,sha256=fmoYRI4KbzhLjXblEs_H9zgdCs7oDuSKeNEG_-kMgYo,32
7
- picoboot/core/enums.py,sha256=YQnCtiX3XwG5RtAIhTmD37DMZMEGmcPIaTiwsW3ckxw,1211
8
- picoboot/tools/picotool.py,sha256=e2xjIxP5UZn9yfjxUWkJXq6KQHyw4YsGbuQbTauo0-Q,94
9
- pypicoboot-1.1.1.dist-info/METADATA,sha256=j9jm7x4uOqjBuqVCzhrQbjnwrOLn_ana6KXHiJpz3ME,41111
10
- pypicoboot-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- pypicoboot-1.1.1.dist-info/top_level.txt,sha256=sjegZQO5-kQdFOXXJHm0P7Hg1Nw4Ri0WKHnRYDTsa4I,9
12
- pypicoboot-1.1.1.dist-info/RECORD,,