CircuitPython: added code to flash a new CircuitPython firmware. micropython

Fri, 02 Aug 2019 19:53:00 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 02 Aug 2019 19:53:00 +0200
branch
micropython
changeset 7116
233b6e62ca2b
parent 7115
fe89c98430b6
child 7119
5f609e77de99

CircuitPython: added code to flash a new CircuitPython firmware.

eric6.e4p file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui file | annotate | diff | comparison | revisions
--- 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>

eric ide

mercurial