pypicoboot 1.1.2__tar.gz → 1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypicoboot
3
- Version: 1.1.2
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
@@ -17,4 +17,4 @@
17
17
  */
18
18
  """
19
19
 
20
- __version__ = "1.1.2"
20
+ __version__ = "1.1.3"
@@ -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,15 +185,21 @@ 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":
202
+ logger.info(f"Opening PicoBoot device with VID={vid:04x} and PIDs={[f'{p:04x}' for p in pid]}...")
166
203
  class find_vidpids(object):
167
204
 
168
205
  def __init__(self, vid: int, pids: list[int]):
@@ -177,10 +214,12 @@ class PicoBoot:
177
214
  devices = usb.core.find(find_all=True, custom_match=find_vidpids(vid, pid))
178
215
  devices = list(devices) if devices is not None else []
179
216
  if not devices:
217
+ logger.error("No device found in PICOBOOT mode")
180
218
  raise PicoBootError("No device found in PICOBOOT mode")
181
219
 
182
220
  dev = None
183
221
  if serial is None:
222
+ logger.info("No serial number provided, using the first device found.")
184
223
  dev = devices[0]
185
224
  else:
186
225
  for d in devices:
@@ -190,15 +229,20 @@ class PicoBoot:
190
229
  continue
191
230
  if s == serial:
192
231
  dev = d
232
+ logger.debug(f"Using device with serial number: {serial}")
193
233
  break
194
234
  if dev is None:
235
+ logger.error("No device found with this serial number")
195
236
  raise PicoBootError("No device found with this serial number")
196
237
 
197
238
  # Ensure active configuration
198
239
  # macOS does not allow detach_kernel_driver, and often returns Access Denied
199
240
  try:
241
+ logger.debug("Checking if kernel driver is active and detaching if necessary...")
200
242
  if dev.is_kernel_driver_active(1):
243
+ logger.debug("Kernel driver is active, detaching...")
201
244
  dev.detach_kernel_driver(1)
245
+ logger.debug("Kernel driver detached.")
202
246
  except usb.core.USBError:
203
247
  # If it fails, we continue anyway. It's normal on macOS.
204
248
  pass
@@ -207,7 +251,9 @@ class PicoBoot:
207
251
  pass
208
252
 
209
253
  #dev.set_configuration()
254
+ logger.debug("Getting active configuration...")
210
255
  cfg = dev.get_active_configuration()
256
+ logger.debug("Searching for PICOBOOT interface...")
211
257
 
212
258
  intf = None
213
259
  for i in cfg:
@@ -215,31 +261,41 @@ class PicoBoot:
215
261
  intf = i
216
262
  break
217
263
  if intf is None:
264
+ logger.error("No interface found with PICOBOOT at the device")
218
265
  raise PicoBootError("No interface found with PICOBOOT at the device")
219
266
 
220
267
  #usb.util.claim_interface(dev, intf.bInterfaceNumber)
221
268
 
269
+ logger.debug("Finding BULK_IN and BULK_OUT endpoints...")
222
270
  ep_in = ep_out = None
223
271
  for ep in intf.endpoints():
224
272
  if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
273
+ logger.debug(f"Found BULK_IN endpoint: 0x{ep.bEndpointAddress:02X}")
225
274
  ep_in = ep
226
275
  else:
276
+ logger.debug(f"Found BULK_OUT endpoint: 0x{ep.bEndpointAddress:02X}")
227
277
  ep_out = ep
228
278
 
229
279
  if ep_in is None or ep_out is None:
280
+ logger.error("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
230
281
  raise PicoBootError("No PICOBOOT BULK_IN/BULK_OUT endpoints found")
231
-
282
+ logger.info("PICOBOOT device opened successfully.")
232
283
  return cls(dev, intf, ep_out, ep_in)
233
284
 
234
285
  def close(self):
286
+ logger.debug("Closing PicoBoot device...")
235
287
  if self.dev:
288
+ self.__monitor.stop()
289
+ logger.debug("Releasing USB resources...")
236
290
  usb.util.dispose_resources(self.dev)
291
+ logger.debug("PicoBoot device closed.")
237
292
  self.dev = None
238
293
 
239
294
  def has_device(self):
240
295
  return self.dev is not None
241
296
 
242
297
  def interface_reset(self) -> None:
298
+ logger.debug("Resetting interface...")
243
299
  self.dev.ctrl_transfer(
244
300
  ControlRequest.BMREQ_RESET,
245
301
  ControlRequest.REQ_INTERFACE_RESET,
@@ -247,8 +303,10 @@ class PicoBoot:
247
303
  self.intf.bInterfaceNumber,
248
304
  None
249
305
  )
306
+ logger.debug("Interface reset command sent.")
250
307
 
251
308
  def get_command_status(self) -> dict:
309
+ logger.debug("Getting command status...")
252
310
  data = self.dev.ctrl_transfer(
253
311
  ControlRequest.BMREQ_GET_STATUS,
254
312
  ControlRequest.REQ_GET_COMMAND_STATUS,
@@ -256,6 +314,7 @@ class PicoBoot:
256
314
  self.intf.bInterfaceNumber,
257
315
  16,
258
316
  )
317
+ logger.debug(f"Command status data: {hexlify(data).decode()}")
259
318
  b = bytes(data)
260
319
  dToken, dStatusCode = struct.unpack_from("<II", b, 0)
261
320
  bCmdId = b[8]
@@ -303,9 +362,10 @@ class PicoBoot:
303
362
  transfer_length = 0 if data_out is None else len(data_out)
304
363
 
305
364
  token, header = self._build_command(cmd_id, args=args, transfer_length=transfer_length)
306
- 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}")
307
366
 
308
367
  self.ep_out.write(header, timeout=timeout)
368
+ logger.debug(f"Command header sent: {hexlify(header).decode()}")
309
369
 
310
370
  data_in = b""
311
371
 
@@ -322,63 +382,79 @@ class PicoBoot:
322
382
  remaining -= len(chunk)
323
383
  data_in = b"".join(chunks)
324
384
  if len(data_in) != transfer_length:
385
+ logger.error(f"Expected {transfer_length} bytes, got {len(data_in)}")
325
386
  raise PicoBootError(f"Expected {transfer_length} bytes, got {len(data_in)}")
326
387
  else:
327
388
  if data_out is None or len(data_out) < transfer_length:
389
+ logger.error("data_out missing or too short for OUT command")
328
390
  raise ValueError("data_out missing or too short for OUT command")
329
391
  self.ep_out.write(data_out[:transfer_length], timeout=timeout)
330
392
 
331
393
  try:
394
+ logger.debug("Waiting for ACK...")
332
395
  if is_in:
333
396
  self.ep_out.write(b"", timeout=timeout)
334
397
  else:
335
398
  ack = self.ep_in.read(1, timeout=timeout)
336
399
  except usb.core.USBError:
400
+ logger.error("No ACK received after command")
337
401
  raise PicoBootError("No ACK received after command")
338
402
 
339
403
  return data_in
340
404
 
341
405
 
342
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")
343
408
  if addr % 4096 != 0 or size % 4096 != 0:
409
+ logger.error("addr i size must be aligned to 4kB")
344
410
  raise ValueError("addr i size must be aligned to 4kB")
345
411
  args = struct.pack("<II", addr, size)
346
412
  self._send_command(CommandID.FLASH_ERASE, args=args, transfer_length=0)
347
413
 
348
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")
349
416
  args = struct.pack("<II", addr, size)
350
417
  data = self._send_command(CommandID.READ, args=args, transfer_length=size)
351
418
  if len(data) != size:
419
+ logger.error(f"READ returned {len(data)} bytes, expected {size}")
352
420
  raise PicoBootError(f"READ returned {len(data)} bytes, expected {size}")
353
421
  return data
354
422
 
355
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")
356
425
  if addr % 256 != 0 or len(data) % 256 != 0:
426
+ logger.error("addr i len(data) must be aligned/multiple of 256 bytes")
357
427
  raise ValueError("addr i len(data) must be aligned/multiple of 256 bytes")
358
428
  args = struct.pack("<II", addr, len(data))
359
429
  self._send_command(CommandID.WRITE, args=args, data_out=data, transfer_length=len(data))
360
430
 
361
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}")
362
433
  args = struct.pack("<III", pc, sp, delay_ms)
363
434
  self._send_command(CommandID.REBOOT, args=args, transfer_length=0)
364
435
 
365
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}")
366
438
  args = struct.pack("<IIII", flags, delay_ms, p0, p1)
367
439
  self._send_command(CommandID.REBOOT2, args=args, transfer_length=0)
368
440
 
369
441
  def reboot(self, delay_ms: int = 100) -> None:
442
+ logger.debug(f"Rebooting device with delay_ms={delay_ms}")
370
443
  if (self.model == Model.RP2040):
371
444
  self.reboot1(delay_ms=delay_ms)
372
445
  elif (self.model == Model.RP2350):
373
446
  self.reboot2(delay_ms=delay_ms)
374
447
 
375
448
  def exit_xip(self) -> None:
449
+ logger.debug("Exiting XIP mode...")
376
450
  self._send_command(CommandID.EXIT_XIP, transfer_length=0)
377
451
 
378
452
  def exclusive_access(self) -> None:
453
+ logger.debug("Requesting exclusive access to flash...")
379
454
  self._send_command(CommandID.EXCLUSIVE_ACCESS, args=struct.pack("<B", 1), transfer_length=0)
380
455
 
381
456
  def _determine_model(self) -> str:
457
+ logger.debug("Determining device model...")
382
458
  if (hasattr(self, "_model")) and (self._model is not None):
383
459
  return self._model
384
460
  data = self.flash_read(Addresses.BOOTROM_MAGIC, 4)
@@ -390,6 +466,7 @@ class PicoBoot:
390
466
  return self._model
391
467
 
392
468
  def _guess_flash_size(self) -> int:
469
+ logger.debug("Guessing flash size...")
393
470
  if (hasattr(self, "_memory")) and (self._memory is not None):
394
471
  return self._memory
395
472
  FLASH_BASE = 0x10000000
@@ -428,6 +505,7 @@ class PicoBoot:
428
505
  return self._memory
429
506
 
430
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}")
431
509
  args = struct.pack("<IIII", info_type, param0, 0, 0)
432
510
  data = self._send_command(CommandID.GET_INFO, args=args, transfer_length=max_len)
433
511
  return data
@@ -455,6 +533,7 @@ class PicoBoot:
455
533
  }
456
534
 
457
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}")
458
537
  data = self.get_info(InfoType.SYS, param0=flags, max_len=256)
459
538
  if len(data) < 24:
460
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.2
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes