src/eric7/MicroPython/UF2FlashDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9051
903d596d7b17
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to flash any UF2 capable device.
8 """
9
10 import os
11 import shutil
12 import contextlib
13
14 from PyQt6.QtCore import pyqtSlot, Qt, QCoreApplication, QThread, QEventLoop
15 from PyQt6.QtWidgets import QDialog
16
17 from EricWidgets.EricPathPicker import EricPathPickerModes
18 from EricWidgets.EricApplication import ericApp
19
20 from .Ui_UF2FlashDialog import Ui_UF2FlashDialog
21
22 import UI.PixmapCache
23 import Utilities
24
25 from . import MicroPythonDevices
26
27 SupportedUF2Boards = {
28 "circuitpython": {
29 "volumes": {
30 (0x03EB, 0x2402): [
31 "SAMD21", # SAMD21 Board
32 "SAME54", # SAME54 Board
33 ],
34 (0x04D8, 0xEC44): [
35 "PYCUBEDBOOT", # PyCubedv04
36 ],
37 (0x04D8, 0xEC63): [
38 "BOOT", # CircuitBrains Basic
39 ],
40 (0x04D8, 0xEC64): [
41 "BOOT", # CircuitBrains Deluxe
42 ],
43 (0x04D8, 0xED5F): [
44 "UCHIPYBOOT", # uChip CircuitPython
45 ],
46 (0x04D8, 0xEDB3): [
47 "USBHUBBOOT", # Programmable USB Hub
48 ],
49 (0x04D8, 0xEDBE): [
50 "SAM32BOOT", # SAM32
51 ],
52 (0x04D8, 0xEF66): [
53 "SENSEBOX", # senseBox MCU
54 ],
55 (0x1209, 0x2017): [
56 "MINISAMBOOT", # Mini SAM M4
57 ],
58 (0x1209, 0x3252): [
59 "MCBS2OMBOOT", # Module Clip w/Wroom
60 ],
61 (0x1209, 0x3253): [
62 "MCBS2ERBOOT", # Module Clip w/Wrover
63 ],
64 (0x1209, 0x4D44): [
65 "ROBOM0BOOT", # Robo HAT MM1
66 "ROBOM4BOOT", # Robo HAT MM1 M4
67 ],
68 (0x1209, 0x4DDD): [
69 "SapBOOT", # CP Sapling
70 ],
71 (0x1209, 0x7102): [
72 "MINISAMBOOT", # Mini SAM M0
73 ],
74 (0x1209, 0x7A01): [
75 "MIKOTO-BOOT", # Mikoto nRF52840
76 ],
77 (0x1209, 0x805A): [
78 "BASTBLE", # Bast BLE
79 ],
80 (0x1209, 0xE3E2): [
81 "StackRduino", # StackRduino M0 PRO
82 ],
83 (0x1209, 0xF501): [
84 "M4SHIMBOOT", # M4-Shim
85 ],
86 (0x15BA, 0x28DC): [
87 "OLMLIPOBOOT", # ESP32S2 DevKit Lipo
88 ],
89 (0x16D0, 0x0CDA): [
90 "AUTOMAT", # automat
91 ],
92 (0x1B4F, 0x0022): [
93 "SFMM852BOOT", # MicroMod nRF52840
94 ],
95 (0x1B4F, 0x002C): [
96 "THNG+32BOOT", # Thing Plus - STM32
97 ],
98 (0x1B4F, 0x0D22): [
99 "SPARKFUN", # SAMD21 Mini Breakout
100 ],
101 (0x1B4F, 0x0D23): [
102 "SPARKFUN", # SAMD21 Dev Breakout
103 ],
104 (0x1D50, 0x6110): [
105 "ROBOTICS", # Robotics
106 ],
107 (0x1D50, 0x6112): [
108 "RCBOOT", # Wattuino RC
109 ],
110 (0x1D50, 0x6157): [
111 "BBOARDBOOT", # nRF52840 BBoard
112 ],
113 (0x1D50, 0x6160): [
114 "BLUEMICRO", # BlueMicro
115 ],
116 (0x1D50, 0x616F): [
117 "BLUEMICRO", # BlueMicro
118 ],
119 (0x1FC9, 0x0094): [
120 "DblM33BOOT", # Double M33
121 "LPC5528BOOT", # LPCXpresso 55s28
122 "LPC5569BOOT", # LPCXpresso 55s69
123 ],
124 (0x1FC9, 0x0154): [
125 "K32L2BOOT", # FRDM-K32L2B3
126 "K32L2BOOT", # KUIIC
127 ],
128 (0x230A, 0x00E9): [
129 "TAU_BOOT", # Tau
130 ],
131 (0x2341, 0x0057): [
132 "NANOBOOT", # NANO 33 IoT
133 ],
134 (0x2341, 0x8053): [
135 "MKR1300", # MKR1300
136 ],
137 (0x239A, 0x000F): [
138 "ITSYBOOT", # ItsyBitsy M0 Express
139 ],
140 (0x239A, 0x0013): [
141 "METROBOOT", # Metro M0
142 ],
143 (0x239A, 0x0015): [
144 "FEATHERBOOT", # Feather M0
145 ],
146 (0x239A, 0x0018): [
147 "CPLAYBOOT", # CPlay Express
148 ],
149 (0x239A, 0x001B): [
150 "FEATHERBOOT", # Feather M0 Express
151 ],
152 (0x239A, 0x001C): [
153 "GEMMABOOT", # Gemma M0
154 ],
155 (0x239A, 0x001E): [
156 "TRINKETBOOT", # Trinket M0
157 ],
158 (0x239A, 0x0021): [
159 "METROM4BOOT", # Metro M4 Express
160 ],
161 (0x239A, 0x0022): [
162 "ARCADE-D5", # Feather Arcade D51
163 "FEATHERBOOT", # Feather M4 Express
164 ],
165 (0x239A, 0x0024): [
166 "RADIOBOOT", # Radiofruit M0
167 ],
168 (0x239A, 0x0027): [
169 "PIRKEYBOOT", # pIRKey M0
170 ],
171 (0x239A, 0x0029): [
172 "ARGONBOOT ", # Argon
173 "BORONBOOT ", # Boron
174 "FTHR840BOOT", # Feather nRF52840 Express
175 "MDK840DONGL", # MDK nRF52840 USB Dongle
176 "WS52840EVK", # Waveshare nRF52840 Eval
177 "XENONBOOT ", # Xenon
178 ],
179 (0x239A, 0x002B): [
180 "ARCADE-D5", # Itsy Arcade D51
181 "ITSYM4BOOT", # ItsyBitsy M4 Express
182 ],
183 (0x239A, 0x002D): [
184 "CRICKITBOOT", # crickit
185 ],
186 (0x239A, 0x002F): [
187 "TRELM4BOOT", # Trellis M4 Express
188 ],
189 (0x239A, 0x0031): [
190 "GCM4BOOT", # Grand Central M4 Express
191 ],
192 (0x239A, 0x0033): [
193 "PYBADGEBOOT", # PyBadge
194 ],
195 (0x239A, 0x0034): [
196 "BADGELCBOOT", # BadgeLC
197 "PEWBOOT", # PewPew
198 ],
199 (0x239A, 0x0035): [
200 "MKRZEROBOOT", # MKRZero
201 "PORTALBOOT", # PyPortal M4 Express
202 ],
203 (0x239A, 0x0037): [
204 "METROM4BOOT", # Metro M4 AirLift
205 ],
206 (0x239A, 0x003D): [
207 "PYGAMERBOOT", # PyGamer
208 ],
209 (0x239A, 0x003F): [
210 "METR840BOOT", # Metro nRF52840 Express
211 ],
212 (0x239A, 0x0045): [
213 "CPLAYBTBOOT", # Circuit Playground nRF52840
214 ],
215 (0x239A, 0x0047): [
216 "MASKM4BOOT", # Hallowing Mask M4
217 ],
218 (0x239A, 0x0049): [
219 "HALLOM4BOOT", # HalloWing M4
220 ],
221 (0x239A, 0x004D): [
222 "SNEKBOOT", # snekboard
223 ],
224 (0x239A, 0x0051): [
225 "ITSY840BOOT", # ItsyBitsy nRF52840 Express
226 ],
227 (0x239A, 0x0057): [
228 "SERPENTBOOT", # Serpente
229 ],
230 (0x239A, 0x0059): [
231 "FTHR405BOOT", # Feather STM32F405 Express
232 ],
233 (0x239A, 0x005D): [
234 "BlackPill", # STM32F401CxUx
235 "STMF411BOOT", # STM32F411 Discovery
236 ],
237 (0x239A, 0x0061): [
238 "SOLBOOT", # Sol
239 ],
240 (0x239A, 0x0063): [
241 "NANO33BOOT", # Nano 33 BLE
242 ],
243 (0x239A, 0x0065): [
244 "ND6BOOT", # ndBit6
245 ],
246 (0x239A, 0x0069): [
247 "STMF411BOOT", # STM32F411 BlackPill
248 ],
249 (0x239A, 0x006B): [
250 "shIRtty", # shIRtty
251 ],
252 (0x239A, 0x0071): [
253 "CLUEBOOT", # CLUE nRF52840
254 ],
255 (0x239A, 0x0077): [
256 "RT1010BOOT", # RT1010 EVK
257 ],
258 (0x239A, 0x0079): [
259 "ARAMBOOT", # ARAMCON Badge 2019
260 ],
261 (0x239A, 0x007B): [
262 "ARAMBOOT", # ARAMCON2 Badge
263 ],
264 (0x239A, 0x007D): [
265 "BOOKBOOT", # The Open Book Feather
266 ],
267 (0x239A, 0x007F): [
268 "BADGEBOOT", # OHS2020 Badge
269 ],
270 (0x239A, 0x0081): [
271 "RT1020BOOT", # RT1020 EVK
272 "RT1024BOOT", # RT1024 EVK
273 ],
274 (0x239A, 0x0083): [
275 "RT1060BOOT", # RT1060 EVK
276 "RT1064BOOT", # RT1064 EVK
277 ],
278 (0x239A, 0x0087): [
279 "FTHRSNSBOOT", # Feather nRF52840 Sense
280 ],
281 (0x239A, 0x0093): [
282 "ISVITABoot", # IkigaiSense Vita nRF52840
283 ],
284 (0x239A, 0x0095): [
285 "UARTLOGBOOT", # UARTLogger II
286 ],
287 (0x239A, 0x009F): [
288 "ADM840BOOT", # AtelierDuMaker NRF52840 Breakout
289 ],
290 (0x239A, 0x00A5): [
291 "S3DKC1BOOT", # ESP32S3 DevKitC 1
292 "S3DKM1BOOT", # ESP32S3 DevKitM 1
293 "SAOLA1RBOOT", # Saola 1R WROVER
294 ],
295 (0x239A, 0x00A7): [
296 "SAOLA1MBOOT", # Saola 1M WROOM
297 ],
298 (0x239A, 0x00AB): [
299 "UFTHRS2BOOT", # FeatherS2
300 ],
301 (0x239A, 0x00AF): [
302 "FLUFFBOOT", # Fluff M0
303 ],
304 (0x239A, 0x00B3): [
305 "NICENANO", # nice!nano
306 ],
307 (0x239A, 0x00B5): [
308 "E54XBOOT", # SAME54 Xplained
309 ],
310 (0x239A, 0x00B9): [
311 "ND7BOOT", # ndBit7
312 ],
313 (0x239A, 0x00BB): [
314 "MDBT50QBOOT", # Raytac MDBT50Q Demo Board 40
315 ],
316 (0x239A, 0x00BF): [
317 "BADGEBOOT", # BLM Badge
318 ],
319 (0x239A, 0x00C3): [
320 "GEMINIBOOT", # Gemini
321 ],
322 (0x239A, 0x00C5): [
323 "MICROS2BOOT", # microS2
324 ],
325 (0x239A, 0x00C7): [
326 "KALUGA1BOOT", # Kaluga 1
327 ],
328 (0x239A, 0x00C9): [
329 "MATRIXBOOT", # Matrix Portal M4
330 ],
331 (0x239A, 0x00CB): [
332 "QTPY_BOOT", # QT Py M0
333 ],
334 (0x239A, 0x00CD): [
335 "FTHRCANBOOT", # Feather M4 CAN Express
336 ],
337 (0x239A, 0x00DE): [
338 "NANOESPBOOT", # nanoESP32-S2 WROOM
339 ],
340 (0x239A, 0x00DF): [
341 "METROS2BOOT", # Metro ESP32-S2
342 ],
343 (0x239A, 0x00E1): [
344 "METROM7BOOT", # Metro M7 iMX RT1011
345 ],
346 (0x239A, 0x00E5): [
347 "MAGTAGBOOT", # Metro MagTag 2.9 Grayscale
348 "MAGTAGBOOT", # MagTag 2.9 Grayscale
349 ],
350 (0x239A, 0x00EB): [
351 "FTHRS2BOOT", # Feather ESP32-S2
352 ],
353 (0x239A, 0x00ED): [
354 "FTHRS2BOOT", # Feather ESP32-S2 Reverse TFT
355 ],
356 (0x239A, 0x00EF): [
357 "TRINKEYBOOT", # NeoPixel Trinkey M0
358 ],
359 (0x239A, 0x00F5): [
360 "STARBOOT", # Binary Star
361 ],
362 (0x239A, 0x00F9): [
363 "HOUSEBOOT", # FunHouse
364 ],
365 (0x239A, 0x00FB): [
366 "TRINKEYBOOT", # Rotary Trinkey M0
367 ],
368 (0x239A, 0x00FF): [
369 "TRINKEYBOOT", # NeoKey Trinkey M0
370 ],
371 (0x239A, 0x0101): [
372 "TRINKEYBOOT", # Slide Trinkey M0
373 ],
374 (0x239A, 0x0103): [
375 "TRINKEYBOOT", # ProxSense Trinkey M0
376 ],
377 (0x239A, 0x010B): [
378 "MDBT50QBOOT", # Raytac MDBT50Q-RX
379 ],
380 (0x239A, 0x010D): [
381 "GLASSESBOOT", # LED Glasses Driver nRF52840
382 ],
383 (0x239A, 0x010F): [
384 "FTHRS2BOOT", # Feather ESP32-S2 TFT
385 ],
386 (0x239A, 0x0111): [
387 "QTPYS2BOOT", # QT Py ESP32-S2
388 ],
389 (0x239A, 0x0113): [
390 "FTHRS3BOOT", # Feather ESP32-S3 No PSRAM
391 ],
392 (0x239A, 0x0115): [
393 "FEATHERBOOT", # Feather M4 Adalogger
394 ],
395 (0x239A, 0x0117): [
396 "CAMERABOOT", # Camera
397 ],
398 (0x239A, 0x0119): [
399 "QTPYS3BOOT", # QT Py ESP32-S3
400 ],
401 (0x239A, 0x800B): [
402 "ATMZBOOT", # ATMegaZero ESP32-S2
403 ],
404 (0x239A, 0xB000): [
405 "HALLOWBOOT", # Hallowing M0
406 ],
407 (0x239A, 0xE005): [
408 "HONKBOOT", # Big Honking Button
409 ],
410 (0x2886, 0x000D): [
411 "Grove Zero", # Grove Zero
412 ],
413 (0x2886, 0x002F): [
414 "Seeed XIAO", # Seeeduino XIAO
415 ],
416 (0x2886, 0xF00E): [
417 "PITAYAGO", # Pitaya Go
418 ],
419 (0x2886, 0xF00F): [
420 "M60KEYBOARD", # MakerDiary M60 Mechanical Keyboard
421 "nRF52840M2", # MakerDiary nRF52840 M.2 Module
422 ],
423 (0x303A, 0x7000): [
424 "ESPHMI1BOOT", # HMI 1
425 ],
426 (0x303A, 0x8005): [
427 "TINYS2BOOT", # TinyS2
428 ],
429 (0x303A, 0x8008): [
430 "TTGOS2BOOT", # TTGO_T8_S2_Display
431 ],
432 (0x303A, 0x800E): [
433 "CCMBRISBOOT", # CucumberRIS v1.1
434 ],
435 (0x303A, 0x80B0): [
436 "RD00RBOOT", # Reference Design RD00
437 ],
438 (0x303A, 0x80B3): [
439 "NANOESPBOOT", # nanoESP32-S2 WROVER
440 ],
441 (0x303A, 0x80B5): [
442 "FS2NEOBOOT", # FeatherS2 Neo
443 ],
444 (0x303A, 0x80B6): [
445 "MORPHBOOT", # MORPHESP-240
446 ],
447 (0x303A, 0x80C4): [
448 "S2MINIBOOT", # S2 Mini
449 ],
450 (0x303A, 0x80C7): [
451 "S2PICOBOOT", # S2 Pico
452 ],
453 (0x303A, 0x80D2): [
454 "TINYS3BOOT", # TinyS3
455 ],
456 (0x303A, 0x80D5): [
457 "PROS3BOOT", # ProS3
458 ],
459 (0x303A, 0x80D8): [
460 "UFTHRS3BOOT", # FeatherS3
461 ],
462 (0x303A, 0x80DA): [
463 "HEXKYBOOT", # HexKy-S2
464 ],
465 (0x303A, 0x80DE): [
466 "LEAFS3BOOT", # BPI-Leaf-S3
467 ],
468 (0x303A, 0x80E1): [
469 "LEAFS2BOOT", # BPI-Leaf-S2
470 ],
471 (0x303A, 0x80E4): [
472 "BITS2BOOT", # BPI-BIT-S2
473 ],
474 (0x303A, 0x80EB): [
475 "TTGOS2BOOT", # TTGO_T8_S2_WROOM
476 ],
477 (0x303A, 0x80EE): [
478 "TTGOS2BOOT", # TTGO_T8_S2
479 ],
480 (0x3171, 0x0100): [
481 "CMDBOOT", # COMMANDER
482 ],
483 (0x80E7, 0x8111): [
484 "IOTS2BOOT", # HiiBot IoTs2
485 ],
486 (0xCAFE, 0xFFFF): [
487 "F303BOOT", # STM32F303 Discovery
488 ],
489 },
490 "instructions": QCoreApplication.translate(
491 "UF2FlashDialog",
492 "<h3>CircuitPython Board</h3>"
493 "<p>In order to prepare the board for flashing follow these"
494 " steps:</p><ol>"
495 "<li>Switch your device to 'bootloader' mode by double-pressing"
496 " the reset button.</li>"
497 "<li>Wait until the device has entered 'bootloader' mode.</li>"
498 "<li>(If this does not happen, then try shorter or longer"
499 " pauses between presses.)</li>"
500 "<li>Ensure the boot volume is available (this may require"
501 " mounting it).</li>"
502 "<li>Select the firmware file to be flashed and click the"
503 " flash button.</li>"
504 "</ol>"
505 ),
506 "show_all": True,
507 "firmware": "CircuitPython",
508 },
509
510 "circuitpython_rp2040": {
511 "volumes": {
512 (0x239A, 0x80F4): [
513 "RPI-RP2", # Raspberry Pi Pico loaded with CircuitPython
514 ],
515 },
516 "instructions": QCoreApplication.translate(
517 "UF2FlashDialog",
518 "<h3>Pi Pico (RP2040) Board</h3>"
519 "<p>In order to prepare the board for flashing follow these"
520 " steps:</p><ol>"
521 "<li>Enter 'bootloader' mode (board <b>without</b> RESET button):"
522 "<ul>"
523 "<li>Plug in your board while holding the BOOTSEL button.</li>"
524 "</ul>"
525 "Enter 'bootloader' mode (board <b>with</b> RESET button):"
526 "<ul>"
527 "<li>hold down RESET</li>"
528 "<li>hold down BOOTSEL</li>"
529 "<li>release RESET</li>"
530 "<li>release BOOTSEL</li>"
531 "</ul></li>"
532 "<li>Wait until the device has entered 'bootloader' mode.</li>"
533 "<li>Ensure the boot volume is available (this may require"
534 " mounting it).</li>"
535 "<li>Select the firmware file to be flashed and click the"
536 " flash button.</li>"
537 "</ol>"
538 ),
539 "show_all": False,
540 "firmware": "CircuitPython",
541 },
542
543 "rp2040": {
544 "volumes": {
545 (0x0000, 0x0000): [
546 "RPI-RP2", # Raspberry Pi Pico does not present a TTY
547 ],
548 },
549 "instructions": QCoreApplication.translate(
550 "UF2FlashDialog",
551 "<h3>Pi Pico (RP2040) Board</h3>"
552 "<p>In order to prepare the board for flashing follow these"
553 " steps:</p><ol>"
554 "<li>Enter 'bootloader' mode (board <b>without</b> RESET button):"
555 "<ul>"
556 "<li>Plug in your board while holding the BOOTSEL button.</li>"
557 "</ul>"
558 "Enter 'bootloader' mode (board <b>with</b> RESET button):"
559 "<ul>"
560 "<li>hold down RESET</li>"
561 "<li>hold down BOOTSEL</li>"
562 "<li>release RESET</li>"
563 "<li>release BOOTSEL</li>"
564 "</ul></li>"
565 "<li>Wait until the device has entered 'bootloader' mode.</li>"
566 "<li>Ensure the boot volume is available (this may require"
567 " mounting it).</li>"
568 "<li>Select the firmware file to be flashed and click the"
569 " flash button.</li>"
570 "</ol>"
571 ),
572 "show_all": True,
573 "firmware": "MicroPython / CircuitPython",
574 },
575 }
576
577
578 def getFoundDevices(boardType=""):
579 """
580 Function to get the list of known serial devices supporting UF2.
581
582 @param boardType specific board type to search for
583 @type str
584 @return list of tuples with the board type, the port description, the
585 VID and PID
586 @rtype list of tuple of (str, str, int, int)
587 """
588 from PyQt6.QtSerialPort import QSerialPortInfo
589
590 foundDevices = []
591
592 availablePorts = QSerialPortInfo.availablePorts()
593 for port in availablePorts:
594 vid = port.vendorIdentifier()
595 pid = port.productIdentifier()
596
597 if vid == 0 and pid == 0:
598 # no device detected at port
599 continue
600
601 for board in SupportedUF2Boards:
602 if (
603 (not boardType or (board.startswith(boardType))) and
604 (vid, pid) in SupportedUF2Boards[board]["volumes"]
605 ):
606 foundDevices.append((
607 board,
608 port.description(),
609 (vid, pid),
610 ))
611
612 # second run for boards needing special treatment (e.g. RP2040)
613 for board in SupportedUF2Boards:
614 if not boardType or (board == boardType):
615 with contextlib.suppress(KeyError):
616 # find mounted volume
617 volumes = SupportedUF2Boards[board]["volumes"][(0, 0)]
618 foundVolumes = []
619 for volume in volumes:
620 foundVolumes += Utilities.findVolume(volume, findAll=True)
621 if foundVolumes:
622 foundDevices.append((
623 board,
624 QCoreApplication.translate(
625 "UF2FlashDialog", "'{0}' Board").format(board),
626 (0, 0), # VID/PID of (0, 0) is special
627 ))
628
629 return foundDevices
630
631
632 class UF2FlashDialog(QDialog, Ui_UF2FlashDialog):
633 """
634 Class implementing a dialog to flash any UF2 capable device.
635 """
636 DeviceTypeRole = Qt.ItemDataRole.UserRole
637 DeviceVidPidRole = Qt.ItemDataRole.UserRole + 1
638
639 def __init__(self, boardType="", parent=None):
640 """
641 Constructor
642
643 @param boardType specific board type to show the dialog for
644 @type str
645 @param parent reference to the parent widget (defaults to None)
646 @type QWidget (optional)
647 """
648 super().__init__(parent)
649 self.setupUi(self)
650
651 self.refreshButton.setIcon(UI.PixmapCache.getIcon("rescan"))
652
653 self.firmwarePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
654 self.firmwarePicker.setFilters(
655 self.tr("MicroPython/CircuitPython Files (*.uf2);;"
656 "All Files (*)"))
657
658 self.bootPicker.setMode(EricPathPickerModes.DIRECTORY_SHOW_FILES_MODE)
659 self.bootPicker.setEnabled(False)
660
661 self.__mandatoryStyleSheet = (
662 "QLineEdit {border: 2px solid; border-color: #dd8888}"
663 if ericApp().usesDarkPalette() else
664 "QLineEdit {border: 2px solid; border-color: #800000}"
665 )
666 self.__manualType = "<manual>"
667
668 self.__boardType = boardType
669
670 self.__populate()
671
672 self.__updateFlashButton()
673
674 def __populate(self):
675 """
676 Private method to (re-)populate the dialog.
677 """
678 # save the currently selected device
679 currentDevice = self.devicesComboBox.currentText()
680 firmwareFile = self.firmwarePicker.text()
681
682 # clear the entries first
683 self.devicesComboBox.clear()
684 self.firmwarePicker.clear()
685 self.bootPicker.clear()
686 self.infoLabel.clear()
687 self.infoEdit.clear()
688
689 # now populate the entries with data
690 devices = getFoundDevices(boardType=self.__boardType)
691 if len(devices) == 0:
692 # no device detected
693 devices = list(filter(
694 lambda x: x[0] in SupportedUF2Boards,
695 MicroPythonDevices.getFoundDevices()[0]
696 ))
697 if devices:
698 self.__showSpecificInstructions(list(devices))
699 else:
700 self.__showAllInstructions()
701 self.devicesComboBox.addItem("")
702 self.devicesComboBox.addItem(self.tr("Manual Select"))
703 self.devicesComboBox.setItemData(1, self.__manualType,
704 self.DeviceTypeRole)
705 elif len(devices) == 1:
706 # set the board type to the found one
707 self.__boardType = devices[0][0]
708
709 self.devicesComboBox.addItem(devices[0][1])
710 self.devicesComboBox.setItemData(
711 0, devices[0][0], self.DeviceTypeRole)
712 self.devicesComboBox.setItemData(
713 0, devices[0][2], self.DeviceVidPidRole)
714 self.devicesComboBox.addItem(self.tr("Manual Select"))
715 self.devicesComboBox.setItemData(1, self.__manualType,
716 self.DeviceTypeRole)
717 self.on_devicesComboBox_currentIndexChanged(0)
718 else:
719 self.devicesComboBox.addItem("")
720 for index, (boardType, description,
721 vidpid) in enumerate(sorted(devices), 1):
722 self.devicesComboBox.addItem(description)
723 self.devicesComboBox.setItemData(
724 index, boardType, self.DeviceTypeRole)
725 self.devicesComboBox.setItemData(
726 index, vidpid, self.DeviceVidPidRole)
727 self.devicesComboBox.addItem(self.tr("Manual Select"))
728 self.devicesComboBox.setItemData(index + 1, self.__manualType,
729 self.DeviceTypeRole)
730
731 # reselect the remembered device, if it is still there
732 if currentDevice:
733 self.devicesComboBox.setCurrentText(currentDevice)
734 self.firmwarePicker.setText(firmwareFile)
735 else:
736 self.devicesComboBox.setCurrentIndex(0)
737
738 def __updateFlashButton(self):
739 """
740 Private method to update the state of the Flash button and the retest
741 button.
742 """
743 firmwareFile = self.firmwarePicker.text()
744 if self.devicesComboBox.currentData(self.DeviceTypeRole) is not None:
745 if bool(firmwareFile) and os.path.exists(firmwareFile):
746 self.firmwarePicker.setStyleSheet("")
747 else:
748 self.firmwarePicker.setStyleSheet(self.__mandatoryStyleSheet)
749
750 if bool(self.bootPicker.text()):
751 self.bootPicker.setStyleSheet("")
752 else:
753 self.bootPicker.setStyleSheet(self.__mandatoryStyleSheet)
754 else:
755 self.firmwarePicker.setStyleSheet("")
756 self.bootPicker.setStyleSheet("")
757
758 enable = (
759 bool(self.bootPicker.text()) and
760 bool(firmwareFile) and
761 os.path.exists(firmwareFile)
762 )
763 self.flashButton.setEnabled(enable)
764
765 def __showAllInstructions(self):
766 """
767 Private method to show instructions for resetting devices to bootloader
768 mode.
769 """
770 self.infoLabel.setText(self.tr("Reset Instructions:"))
771
772 htmlText = self.tr(
773 "<h4>No known devices detected.</h4>"
774 "<p>Follow the appropriate instructions below to set <b>one</b>"
775 " board into 'bootloader' mode. Press <b>Refresh</b> when ready."
776 "</p>"
777 )
778 for boardType in SupportedUF2Boards:
779 if SupportedUF2Boards[boardType]["show_all"]:
780 htmlText += (
781 "<hr/>" +
782 SupportedUF2Boards[boardType]["instructions"]
783 )
784 self.infoEdit.setHtml(htmlText)
785
786 def __showSpecificInstructions(self, devices):
787 """
788 Private method to show instructions for resetting devices to bootloader
789 mode for a list of detected devices.
790
791 @param devices list of detected devices
792 @type list of str
793 """
794 boardTypes = {x[0] for x in devices}
795
796 self.infoLabel.setText(self.tr("Reset Instructions:"))
797
798 if self.__boardType:
799 htmlText = self.tr(
800 "<h4>Flash {0} Firmware</h4>"
801 "<p>Follow the instructions below to set <b>one</b> board into"
802 " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
803 "<hr/>{1}"
804 ).format(
805 SupportedUF2Boards[self.__boardType]["firmware"],
806 SupportedUF2Boards[self.__boardType]["instructions"],
807 )
808 else:
809 htmlText = self.tr(
810 "<h4>Potentially UF2 capable devices found</h4>"
811 "<p>Found these potentially UF2 capable devices:</p>"
812 "<ul><li>{0}</li></ul>"
813 "<p>Follow the instructions below to set <b>one</b> board into"
814 " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
815 ).format(
816 "</li><li>".join(sorted(x[1] for x in devices))
817 )
818 for boardType in sorted(boardTypes):
819 htmlText += (
820 "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
821 )
822 self.infoEdit.setHtml(htmlText)
823
824 def __showTypedInstructions(self, boardType):
825 """
826 Private method to show instructions for resetting devices to bootloader
827 mode for a specific board type.
828
829 @param boardType type of the board to show instructions for
830 @type str
831 """
832 self.infoLabel.setText(self.tr("Reset Instructions:"))
833
834 htmlText = self.tr(
835 "<h4>No known devices detected.</h4>"
836 "<p>Follow the instructions below to set <b>one</b> board into"
837 " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
838 )
839 htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
840 self.infoEdit.setHtml(htmlText)
841
842 def __showManualInstructions(self):
843 """
844 Private method to show instructions for flashing devices manually.
845 """
846 self.infoLabel.setText(self.tr("Flash Instructions:"))
847
848 htmlText = self.tr(
849 "<h4>Flash method 'manual' selected.</h4>"
850 "<p>Follow the instructions below to flash a device by entering"
851 " the data manually.</p><ol>"
852 "<li>Change the device to 'bootloader' mode.</li>"
853 "<li>Wait until the device has entered 'bootloader' mode.</li>"
854 "<li>Ensure the boot volume is available (this may require"
855 " mounting it) and select its path.</li>"
856 "<li>Select the firmware file to be flashed and click the"
857 " flash button.</li>"
858 "</ol>"
859 )
860 for boardType in SupportedUF2Boards:
861 htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
862 self.infoEdit.setHtml(htmlText)
863
864 def __showNoVolumeInformation(self, volumes, boardType):
865 """
866 Private method to show information about the expected boot volume(s).
867
868 @param volumes list of expected volume names
869 @type list of str
870 @param boardType type of the board to show instructions for
871 @type str
872 """
873 self.infoLabel.setText(self.tr("Boot Volume not found:"))
874
875 htmlText = self.tr(
876 "<h4>No Boot Volume detected.</h4>"
877 "<p>Please ensure that the boot volume of the device to be flashed"
878 " is available. "
879 )
880 if len(volumes) == 1:
881 htmlText += self.tr(
882 "This volume should be named <b>{0}</b>."
883 " Press <b>Refresh</b> when ready.</p>"
884 ).format(volumes[0])
885 else:
886 htmlText += self.tr(
887 "This volume should have one of these names.</p>"
888 "<ul><li>{0}</li></ul>"
889 "<p>Press <b>Refresh</b> when ready.</p>"
890 ).format("</li><li>".join(sorted(volumes)))
891
892 if boardType:
893 htmlText += self.tr(
894 "<h4>Reset Instructions</h4>"
895 "<p>Follow the instructions below to set the board into"
896 " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
897 )
898 htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
899
900 self.infoEdit.setHtml(htmlText)
901
902 def __showMultipleVolumesInformation(self, volumePaths):
903 """
904 Private method to show information because multiple devices of the
905 same type are ready for flashing.
906
907 Note: This is a dangerous situation!
908
909 @param volumePaths list of volume paths
910 @type list of str
911 """
912 self.infoLabel.setText(self.tr("Multiple Boot Volumes found:"))
913
914 htmlText = self.tr(
915 "<h4>Multiple Boot Volumes were found</h4>"
916 "<p>These volume paths were found.</p><ul><li>{0}</li></ul>"
917 "<p>Please ensure that only one device of a type is ready for"
918 " flashing. Press <b>Refresh</b> when ready.</p>"
919 ).format("</li><li>".join(sorted(volumePaths)))
920 self.infoEdit.setHtml(htmlText)
921
922 @pyqtSlot()
923 def on_flashButton_clicked(self):
924 """
925 Private slot to flash the selected MicroPython or CircuitPython
926 firmware onto the device.
927 """
928 boardType = self.devicesComboBox.currentData(self.DeviceTypeRole)
929 firmwarePath = self.firmwarePicker.text()
930 volumePath = self.bootPicker.text()
931 if os.path.exists(firmwarePath) and os.path.exists(volumePath):
932 if boardType == self.__manualType:
933 self.infoLabel.setText(self.tr("Flashing Firmware"))
934 self.infoEdit.setHtml(self.tr(
935 "<p>Flashing the selected firmware to the device. Please"
936 " wait until the device resets automatically.</p>")
937 )
938 else:
939 firmwareType = SupportedUF2Boards[boardType]["firmware"]
940 self.infoLabel.setText(
941 self.tr("Flashing {0}").format(firmwareType))
942 self.infoEdit.setHtml(self.tr(
943 "<p>Flashing the {0} firmware to the device. Please wait"
944 " until the device resets automatically.</p>"
945 ).format(firmwareType))
946 QCoreApplication.processEvents(
947 QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents)
948 shutil.copy2(firmwarePath, volumePath)
949 QThread.sleep(1)
950 self.on_refreshButton_clicked()
951
952 @pyqtSlot()
953 def on_refreshButton_clicked(self):
954 """
955 Private slot to refresh the dialog.
956 """
957 # special treatment for RPi Pico
958 if self.__boardType == "circuitpython_rp2040":
959 self.__boardType = "rp2040"
960
961 self.__populate()
962
963 @pyqtSlot(int)
964 def on_devicesComboBox_currentIndexChanged(self, index):
965 """
966 Private slot to handle the selection of a board.
967
968 @param index selected index
969 @type int
970 """
971 vidpid = self.devicesComboBox.itemData(index, self.DeviceVidPidRole)
972 boardType = self.devicesComboBox.itemData(index, self.DeviceTypeRole)
973
974 self.bootPicker.setEnabled(boardType == self.__manualType)
975 if boardType == self.__manualType:
976 self.__showManualInstructions()
977
978 if vidpid is None:
979 if boardType is None:
980 self.bootPicker.clear()
981 else:
982 volumes = SupportedUF2Boards[boardType]["volumes"][vidpid]
983 foundVolumes = []
984 for volume in volumes:
985 foundVolumes += Utilities.findVolume(volume, findAll=True)
986
987 if len(foundVolumes) == 0:
988 self.__showNoVolumeInformation(volumes, boardType)
989 self.bootPicker.clear()
990 elif len(foundVolumes) == 1:
991 self.bootPicker.setText(foundVolumes[0])
992 else:
993 self.__showMultipleVolumesInformation()
994 self.bootPicker.clear()
995
996 self.__updateFlashButton()
997
998 @pyqtSlot(str)
999 def on_firmwarePicker_textChanged(self, text):
1000 """
1001 Private slot handling a change of the firmware file.
1002
1003 @param text current text of the firmware edit
1004 @type str
1005 """
1006 self.__updateFlashButton()
1007
1008 @pyqtSlot(str)
1009 def on_bootPicker_textChanged(self, text):
1010 """
1011 Private slot handling a change of the boot volume.
1012
1013 @param text current text of the boot volume edit
1014 @type str
1015 """
1016 self.__updateFlashButton()

eric ide

mercurial