Fri, 02 Aug 2019 19:53:00 +0200
CircuitPython: added code to flash a new CircuitPython firmware.
--- a/eric6.e4p Fri Aug 02 19:52:11 2019 +0200 +++ b/eric6.e4p Fri Aug 02 19:53:00 2019 +0200 @@ -457,6 +457,7 @@ <Source>eric6/IconEditor/cursors/__init__.py</Source> <Source>eric6/IconEditor/cursors/cursors_rc.py</Source> <Source>eric6/MicroPython/CircuitPythonDevices.py</Source> + <Source>eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py</Source> <Source>eric6/MicroPython/EspDevices.py</Source> <Source>eric6/MicroPython/EspFirmwareSelectionDialog.py</Source> <Source>eric6/MicroPython/MicroPythonCommandsInterface.py</Source> @@ -1850,6 +1851,7 @@ <Form>eric6/HexEdit/HexEditReplaceWidget.ui</Form> <Form>eric6/HexEdit/HexEditSearchWidget.ui</Form> <Form>eric6/IconEditor/IconSizeDialog.ui</Form> + <Form>eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui</Form> <Form>eric6/MicroPython/EspFirmwareSelectionDialog.ui</Form> <Form>eric6/MicroPython/MicroPythonFileManagerWidget.ui</Form> <Form>eric6/MicroPython/MicroPythonProgressInfoDialog.ui</Form> @@ -2313,14 +2315,14 @@ <Other>docs/THANKS</Other> <Other>docs/changelog</Other> <Other>eric6.e4p</Other> - <Other>eric6/APIs/Python/zope-2.10.7.api</Other> - <Other>eric6/APIs/Python/zope-2.11.2.api</Other> - <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/Python3/PyQt4.bas</Other> <Other>eric6/APIs/Python3/PyQt5.bas</Other> <Other>eric6/APIs/Python3/QScintilla2.bas</Other> <Other>eric6/APIs/Python3/eric6.api</Other> <Other>eric6/APIs/Python3/eric6.bas</Other> + <Other>eric6/APIs/Python/zope-2.10.7.api</Other> + <Other>eric6/APIs/Python/zope-2.11.2.api</Other> + <Other>eric6/APIs/Python/zope-3.3.1.api</Other> <Other>eric6/APIs/QSS/qss.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.api</Other> <Other>eric6/APIs/Ruby/Ruby-1.8.7.bas</Other>
--- a/eric6/MicroPython/CircuitPythonDevices.py Fri Aug 02 19:52:11 2019 +0200 +++ b/eric6/MicroPython/CircuitPythonDevices.py Fri Aug 02 19:53:00 2019 +0200 @@ -9,6 +9,11 @@ from __future__ import unicode_literals +import shutil + +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QDialog + from E5Gui import E5MessageBox from .MicroPythonDevices import MicroPythonDevice @@ -37,9 +42,6 @@ Public method to enable the supported action buttons. """ super(CircuitPythonDevice, self).setButtons() -## self.microPython.setActionButtons( -## run=True, repl=True, chart=HAS_QTCHART) - # TODO: check, if this really works self.microPython.setActionButtons( run=True, repl=True, files=True, chart=HAS_QTCHART) @@ -97,7 +99,6 @@ pythonScript = script.split("\n") self.sendCommands(pythonScript) - # TODO: check, if this really works def canStartFileManager(self): """ Public method to determine, if a File Manager can be started. @@ -124,7 +125,7 @@ else: # return the default workspace and give the user a warning E5MessageBox.warning( - self.microPythonWidget, + self.microPython, self.tr("Workspace Directory"), self.tr("Python files for CircuitPython devices are stored on" " the device. Therefore, to edit these files you need" @@ -132,3 +133,37 @@ " device, the standard directory will be used.")) return super(CircuitPythonDevice, self).getWorkspace() + + def addDeviceMenuEntries(self, menu): + """ + Public method to add device specific entries to the given menu. + + @param menu reference to the context menu + @type QMenu + """ + connected = self.microPython.isConnected() + + act = menu.addAction(self.tr("Flash CircuitPython Firmware"), + self.__flashCircuitPython) + act.setEnabled(not connected) + + @pyqtSlot() + def __flashCircuitPython(self): + """ + Private slot to flash a CircuitPython firmware to the device. + """ + ok = E5MessageBox.information( + self.microPython, + self.tr("Flash CircuitPython Firmware"), + self.tr("Please reset the device to bootloader mode and confirm" + " when ready."), + E5MessageBox.StandardButtons( + E5MessageBox.Abort | + E5MessageBox.Ok)) + if ok: + from .CircuitPythonFirmwareSelectionDialog import ( + CircuitPythonFirmwareSelectionDialog) + dlg = CircuitPythonFirmwareSelectionDialog() + if dlg.exec_() == QDialog.Accepted: + cpyPath, devicePath = dlg.getData() + shutil.copy2(cpyPath, devicePath)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py Fri Aug 02 19:53:00 2019 +0200 @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter the firmware flashing data. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from E5Gui.E5PathPicker import E5PathPickerModes +from E5Gui import E5MessageBox + +from .Ui_CircuitPythonFirmwareSelectionDialog import ( + Ui_CircuitPythonFirmwareSelectionDialog +) + +import Utilities +import UI.PixmapCache + + +class CircuitPythonFirmwareSelectionDialog( + QDialog, Ui_CircuitPythonFirmwareSelectionDialog): + """ + Class implementing a dialog to enter the firmware flashing data. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super(CircuitPythonFirmwareSelectionDialog, self).__init__(parent) + self.setupUi(self) + + self.retestButton.setIcon(UI.PixmapCache.getIcon("rescan")) + + self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode) + self.firmwarePicker.setFilters( + self.tr("CircuitPython Firmware Files (*.uf2);;" + "All Files (*)")) + + self.bootPicker.setMode(E5PathPickerModes.DirectoryShowFilesMode) + + boards = ( + ("", ""), # indicator for no selection + + ("Circuit Playground Express", "CPLAYBOOT"), + ("Feather M0 Express", "FEATHERBOOT"), + ("Feather M4 Express", "FEATHERBOOT"), + ("Gemma M0", "GEMMABOOT"), + ("Grand Central M4 Express", "GCM4BOOT"), + ("ItsyBitsy M0 Express", "ITSYBOOT"), + ("ItsyBitsy M4 Express", "ITSYM4BOOT"), + ("Metro M0 Express", "METROBOOT"), + ("Metro M4 Express", "METROM4BOOT"), + ("NeoTrelis M4 Express", "TRELM4BOOT"), + ("Trinket M0", "TRINKETBOOT"), + + ("Manual Select", "<manual>"), + ) + for boardName, bootVolume in boards: + self.boardComboBox.addItem(boardName, bootVolume) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __updateOkButton(self): + """ + Private method to update the state of the OK button and the retest + button. + """ + firmwareFile = self.firmwarePicker.text() + self.retestButton.setEnabled(bool(firmwareFile) and + os.path.exists(firmwareFile)) + + if not bool(firmwareFile) or not os.path.exists(firmwareFile): + enable = False + else: + volumeName = self.boardComboBox.currentData() + if volumeName and volumeName != "<manual>": + # check if the user selected a board and the board is in + # bootloader mode + deviceDirectory = Utilities.findVolume(volumeName) + if deviceDirectory: + self.bootPicker.setText(deviceDirectory) + enable = True + else: + enable = False + E5MessageBox.warning( + self, + self.tr("Select Path to Device"), + self.tr("""<p>The device volume <b>{0}</b> could not""" + """ be found. Is the device in 'bootloader'""" + """ mode and mounted?</p> <p>Alternatively""" + """ select the Manual Select" entry and""" + """ enter the path to the device below.</p>""") + .format(volumeName) + ) + + elif volumeName == "<manual>": + # select the device path manually + deviceDirectory = self.bootPicker.text() + enable = (bool(deviceDirectory) and + os.path.exists(deviceDirectory)) + + else: + # illegal entry + enable = False + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable) + + @pyqtSlot(str) + def on_firmwarePicker_textChanged(self, firmware): + """ + Private slot handling a change of the firmware path. + + @param firmware path to the firmware + @type str + """ + self.__updateOkButton() + + @pyqtSlot(int) + def on_boardComboBox_currentIndexChanged(self, index): + """ + Private slot to handle the selection of a board type. + + @param index index of the selected board type + @type int + """ + if self.boardComboBox.itemData(index) == "<manual>": + self.bootPicker.clear() + self.bootPicker.setEnabled(True) + else: + self.bootPicker.setEnabled(False) + + self.__updateOkButton() + + @pyqtSlot() + def on_retestButton_clicked(self): + """ + Private slot to research for the selected volume. + """ + self.__updateOkButton() + + @pyqtSlot(str) + def on_bootPicker_textChanged(self, devicePath): + """ + Private slot handling a change of the device path. + + @param devicePath path to the device + @type str + """ + self.__updateOkButton() + + def getData(self): + """ + Public method to obtain the entered data. + + @return tuple containing the path to the CircuitPython firmware file + and the path to the device + @rtype tuple of (str, str) + """ + return self.firmwarePicker.text(), self.bootPicker.text()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui Fri Aug 02 19:53:00 2019 +0200 @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CircuitPythonFirmwareSelectionDialog</class> + <widget class="QDialog" name="CircuitPythonFirmwareSelectionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>124</height> + </rect> + </property> + <property name="windowTitle"> + <string>Flash CircuitPython Firmware</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1"> + <widget class="QComboBox" name="boardComboBox"> + <property name="toolTip"> + <string>Select the board type or 'Manual'</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="E5PathPicker" name="firmwarePicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::WheelFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path of the CircuitPython firmware file</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>339</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Firmware:</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Boot Path:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Board Type:</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="3"> + <widget class="E5PathPicker" name="bootPicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::WheelFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path to the device in bootloader mode</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="4"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QToolButton" name="retestButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Press to search the selected volume</string> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>firmwarePicker</tabstop> + <tabstop>boardComboBox</tabstop> + <tabstop>retestButton</tabstop> + <tabstop>bootPicker</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CircuitPythonFirmwareSelectionDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CircuitPythonFirmwareSelectionDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>