src/eric7/MicroPython/UF2FlashDialog.py

branch
eric7
changeset 9954
7c5fa3eef082
parent 9953
1aa8517bc61f
child 10112
dcbb8703b5b2
equal deleted inserted replaced
9953:1aa8517bc61f 9954:7c5fa3eef082
5 5
6 """ 6 """
7 Module implementing a dialog to flash any UF2 capable device. 7 Module implementing a dialog to flash any UF2 capable device.
8 """ 8 """
9 9
10 import contextlib
11 import os 10 import os
12 import shutil 11 import shutil
13 12
14 from PyQt6.QtCore import QCoreApplication, QEventLoop, Qt, QThread, pyqtSlot 13 from PyQt6.QtCore import QCoreApplication, QEventLoop, Qt, QThread, pyqtSlot
15 from PyQt6.QtSerialPort import QSerialPortInfo 14 from PyQt6.QtSerialPort import QSerialPortInfo
578 "<li>Select the firmware file to be flashed and click the" 577 "<li>Select the firmware file to be flashed and click the"
579 " flash button.</li>" 578 " flash button.</li>"
580 "</ol>", 579 "</ol>",
581 ), 580 ),
582 "show_all": True, 581 "show_all": True,
583 "firmware": "CircuitPython",
584 },
585 "circuitpython_rp2040": {
586 "volumes": {
587 (0x239A, 0x80F4): [
588 ("RPI-RP2", "Raspberry Pi Pico"),
589 ],
590 },
591 "instructions": QCoreApplication.translate(
592 "UF2FlashDialog",
593 "<h3>Pi Pico (RP2040) Board</h3>"
594 "<p>In order to prepare the board for flashing follow these"
595 " steps:</p><ol>"
596 "<li>Enter 'bootloader' mode (board <b>without</b> RESET button):"
597 "<ul>"
598 "<li>Plug in your board while holding the BOOTSEL button.</li>"
599 "</ul>"
600 "Enter 'bootloader' mode (board <b>with</b> RESET button):"
601 "<ul>"
602 "<li>hold down RESET</li>"
603 "<li>hold down BOOTSEL</li>"
604 "<li>release RESET</li>"
605 "<li>release BOOTSEL</li>"
606 "</ul></li>"
607 "<li>Wait until the device has entered 'bootloader' mode.</li>"
608 "<li>Ensure the boot volume is available (this may require"
609 " mounting it).</li>"
610 "<li>Select the firmware file to be flashed and click the"
611 " flash button.</li>"
612 "</ol>",
613 ),
614 "show_all": False,
615 "firmware": "CircuitPython", 582 "firmware": "CircuitPython",
616 }, 583 },
617 "rp2040": { 584 "rp2040": {
618 "volumes": { 585 "volumes": {
619 (0x0000, 0x0000): [ 586 (0x0000, 0x0000): [
659 VID and PID 626 VID and PID
660 @rtype list of tuple of (str, str, (int, int)) 627 @rtype list of tuple of (str, str, (int, int))
661 """ 628 """
662 foundDevices = set() 629 foundDevices = set()
663 630
631 # step 1: determine all known UF2 volumes that are mounted
632 boardTypes = [boardType] if boardType else list(SupportedUF2Boards.keys())
633 for board in boardTypes:
634 for vidpid, volumes in SupportedUF2Boards[board]["volumes"].items():
635 for volume, description in volumes:
636 if FileSystemUtilities.findVolume(volume, findAll=True):
637 foundDevices.add((board, description, vidpid))
638
639 # set 2: determine all devices that have their UF2 volume not mounted
664 availablePorts = QSerialPortInfo.availablePorts() 640 availablePorts = QSerialPortInfo.availablePorts()
665 for port in availablePorts: 641 for port in availablePorts:
666 vid = port.vendorIdentifier() 642 vid = port.vendorIdentifier()
667 pid = port.productIdentifier() 643 pid = port.productIdentifier()
668 644
673 for board in SupportedUF2Boards: 649 for board in SupportedUF2Boards:
674 if (not boardType or (board.startswith(boardType))) and ( 650 if (not boardType or (board.startswith(boardType))) and (
675 vid, 651 vid,
676 pid, 652 pid,
677 ) in SupportedUF2Boards[board]["volumes"]: 653 ) in SupportedUF2Boards[board]["volumes"]:
678 foundDevices.add( 654 for device in foundDevices:
679 ( 655 if (vid, pid) == device[2]:
680 board, 656 break
681 port.description(), 657 else:
682 (vid, pid), 658 foundDevices.add(
659 (
660 board,
661 port.description(),
662 (vid, pid),
663 )
683 ) 664 )
684 )
685
686 # second run for boards needing special treatment (e.g. RP2040)
687 for board in SupportedUF2Boards:
688 if not boardType or (board == boardType):
689 with contextlib.suppress(KeyError):
690 # find mounted volume
691 volumes = SupportedUF2Boards[board]["volumes"][(0, 0)]
692 for volume, description in volumes:
693 if FileSystemUtilities.findVolume(volume, findAll=True):
694 foundDevices.add(
695 (board, port.description() or description, (0, 0))
696 )
697
698 # third run for CircuitPython boards in UF2 mode but with VID/PID being invalid
699 for vidpid, volumes in SupportedUF2Boards["circuitpython"]["volumes"].items():
700 for volume, description in volumes:
701 if FileSystemUtilities.findVolume(volume, findAll=True):
702 foundDevices.add(
703 (
704 "circuitpython",
705 port.description() or description,
706 vidpid,
707 )
708 )
709 665
710 return list(foundDevices) 666 return list(foundDevices)
711 667
712 668
713 class UF2FlashDialog(QDialog, Ui_UF2FlashDialog): 669 class UF2FlashDialog(QDialog, Ui_UF2FlashDialog):
751 self.__manualType = "<manual>" 707 self.__manualType = "<manual>"
752 708
753 self.__boardType = boardType 709 self.__boardType = boardType
754 710
755 self.__populate() 711 self.__populate()
756
757 self.__updateFlashButton() 712 self.__updateFlashButton()
758 713
759 def __populate(self): 714 def __populate(self):
760 """ 715 """
761 Private method to (re-)populate the dialog. 716 Private method to (re-)populate the dialog.
808 ) 763 )
809 764
810 # select the remembered device, if it is still there 765 # select the remembered device, if it is still there
811 if currentDevice: 766 if currentDevice:
812 self.devicesComboBox.setCurrentText(currentDevice) 767 self.devicesComboBox.setCurrentText(currentDevice)
768 if self.devicesComboBox.currentIndex() == -1 and len(devices) == 1:
769 # the device name has changed but only one is present; select it
770 self.devicesComboBox.setCurrentIndex(0)
813 self.firmwarePicker.setText(firmwareFile) 771 self.firmwarePicker.setText(firmwareFile)
814 elif len(devices) == 1: 772 elif len(devices) == 1:
815 self.devicesComboBox.setCurrentIndex(0) 773 self.devicesComboBox.setCurrentIndex(0)
816 else: 774 else:
817 self.devicesComboBox.setCurrentIndex(-1) 775 self.devicesComboBox.setCurrentIndex(-1)
1030 @pyqtSlot() 988 @pyqtSlot()
1031 def on_refreshButton_clicked(self): 989 def on_refreshButton_clicked(self):
1032 """ 990 """
1033 Private slot to refresh the dialog. 991 Private slot to refresh the dialog.
1034 """ 992 """
1035 # special treatment for RPi Pico
1036 if self.__boardType == "circuitpython_rp2040":
1037 self.__boardType = "rp2040"
1038
1039 self.__populate() 993 self.__populate()
994 self.__updateFlashButton()
1040 995
1041 @pyqtSlot(int) 996 @pyqtSlot(int)
1042 def on_devicesComboBox_currentIndexChanged(self, index): 997 def on_devicesComboBox_currentIndexChanged(self, index):
1043 """ 998 """
1044 Private slot to handle the selection of a board. 999 Private slot to handle the selection of a board.
1067 self.__showNoVolumeInformation([v[0] for v in volumes], boardType) 1022 self.__showNoVolumeInformation([v[0] for v in volumes], boardType)
1068 self.bootPicker.clear() 1023 self.bootPicker.clear()
1069 elif len(foundVolumes) == 1: 1024 elif len(foundVolumes) == 1:
1070 self.bootPicker.setText(foundVolumes[0]) 1025 self.bootPicker.setText(foundVolumes[0])
1071 else: 1026 else:
1072 self.__showMultipleVolumesInformation() 1027 self.__showMultipleVolumesInformation(foundVolumes)
1073 self.bootPicker.clear() 1028 self.bootPicker.clear()
1074 1029
1075 self.__updateFlashButton() 1030 self.__updateFlashButton()
1076 1031
1077 @pyqtSlot(str) 1032 @pyqtSlot(str)

eric ide

mercurial