Implemented the support for the 'BBC micro:bit' device. micropython

Tue, 06 Aug 2019 17:45:38 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 06 Aug 2019 17:45:38 +0200
branch
micropython
changeset 7123
94948e2aa0a5
parent 7122
8e67ef7975de
child 7124
1965daf1a14b

Implemented the support for the 'BBC micro:bit' device.

eric6/MicroPython/MicroPythonDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonReplWidget.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicrobitDevices.py file | annotate | diff | comparison | revisions
--- a/eric6/MicroPython/MicroPythonDevices.py	Tue Aug 06 16:42:39 2019 +0200
+++ b/eric6/MicroPython/MicroPythonDevices.py	Tue Aug 06 17:45:38 2019 +0200
@@ -308,3 +308,14 @@
         @type QMenu
         """
         pass
+    
+    def hasTimeCommands(self):
+        """
+        Public method to check, if the device supports time commands.
+        
+        The default returns True.
+        
+        @return flag indicating support for time commands
+        @rtype bool
+        """
+        return True
--- a/eric6/MicroPython/MicroPythonReplWidget.py	Tue Aug 06 16:42:39 2019 +0200
+++ b/eric6/MicroPython/MicroPythonReplWidget.py	Tue Aug 06 17:45:38 2019 +0200
@@ -1050,6 +1050,10 @@
         Private slot to populate the Super Menu before showing it.
         """
         self.__superMenu.clear()
+        if self.__device:
+            hasTime = self.__device.hasTimeCommands()
+        else:
+            hasTime = False
         
         act = self.__superMenu.addAction(
             self.tr("Show Version"), self.__showDeviceVersion)
@@ -1058,13 +1062,14 @@
             self.tr("Show Implementation"), self.__showImplementation)
         act.setEnabled(self.__connected)
         self.__superMenu.addSeparator()
-        act = self.__superMenu.addAction(
-            self.tr("Synchronize Time"), self.__synchronizeTime)
-        act.setEnabled(self.__connected)
-        act = self.__superMenu.addAction(
-            self.tr("Show Time"), self.__showDeviceTime)
-        act.setEnabled(self.__connected)
-        self.__superMenu.addSeparator()
+        if hasTime:
+            act = self.__superMenu.addAction(
+                self.tr("Synchronize Time"), self.__synchronizeTime)
+            act.setEnabled(self.__connected)
+            act = self.__superMenu.addAction(
+                self.tr("Show Time"), self.__showDeviceTime)
+            act.setEnabled(self.__connected)
+            self.__superMenu.addSeparator()
         if self.__device:
             self.__device.addDeviceMenuEntries(self.__superMenu)
     
--- a/eric6/MicroPython/MicrobitDevices.py	Tue Aug 06 16:42:39 2019 +0200
+++ b/eric6/MicroPython/MicrobitDevices.py	Tue Aug 06 17:45:38 2019 +0200
@@ -9,9 +9,20 @@
 
 from __future__ import unicode_literals
 
+import sys
+import os
+
+from PyQt5.QtCore import pyqtSlot, QStandardPaths
+
 from .MicroPythonDevices import MicroPythonDevice
 from .MicroPythonReplWidget import HAS_QTCHART
 
+from E5Gui import E5MessageBox, E5FileDialog
+from E5Gui.E5Application import e5App
+from E5Gui.E5ProcessDialog import E5ProcessDialog
+
+import Utilities
+
 
 class MicrobitDevice(MicroPythonDevice):
     """
@@ -34,7 +45,7 @@
         """
         super(MicrobitDevice, self).setButtons()
         self.microPython.setActionButtons(
-            repl=True, files=True, chart=HAS_QTCHART)
+            repl=True, chart=HAS_QTCHART)
     
     def forceInterrupt(self):
         """
@@ -74,4 +85,216 @@
             File Manager and a reason why it cannot.
         @rtype tuple of (bool, str)
         """
-        return True, ""
+        return False, ""
+    
+    def getWorkspace(self):
+        """
+        Public method to get the workspace directory.
+        
+        @return workspace directory used for saving files
+        @rtype str
+        """
+        # Attempts to find the path on the filesystem that represents the
+        # plugged in MICROBIT board.
+        deviceDirectory = Utilities.findVolume("MICROBIT")
+        
+        if deviceDirectory:
+            return deviceDirectory
+        else:
+            # return the default workspace and give the user a warning
+            E5MessageBox.warning(
+                self.microPython,
+                self.tr("Workspace Directory"),
+                self.tr("Could not find an attached BBC micro:bit.\n\n"
+                        "Please make sure the device is plugged "
+                        "into this computer."))
+            
+            return super(MicrobitDevice, self).getWorkspace()
+    
+    def hasTimeCommands(self):
+        """
+        Public method to check, if the device supports time commands.
+        
+        The default returns True.
+        
+        @return flag indicating support for time commands
+        @rtype bool
+        """
+        return False
+    
+    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 Default MicroPython Firmware"),
+                             self.__flashMicroPython)
+        act.setEnabled(not connected)
+        act = menu.addAction(self.tr("Flash Custom MicroPython Firmware"),
+                             self.__flashCustomMicroPython)
+        act.setEnabled(not connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Flash Script"), self.__flashScript)
+        act.setToolTip(self.tr(
+            "Flash the current script to the selected device."))
+        act.setEnabled(not connected)
+        act = menu.addAction(self.tr("Save Script as 'main.py'"),
+                             self.__saveMain)
+        act.setToolTip(self.tr(
+            "Save the current script as 'main.py' on the connected device"))
+        act.setEnabled(connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Reset micro:bit"), self.__resetDevice)
+        act.setEnabled(connected)
+        menu.addSeparator()
+        menu.addAction(self.tr("Install 'uflash'"), self.__installUflashTool)
+    
+    @pyqtSlot()
+    def __flashMicroPython(self):
+        """
+        Private slot to flash the default MicroPython firmware to the device.
+        """
+        flashArgs = [
+            "-u",
+            "-m", "uflash",
+        ]
+        dlg = E5ProcessDialog(self.tr("'uflash' Output"),
+                              self.tr("Flash Default MicroPython Firmware"))
+        res = dlg.startProcess(sys.executable, flashArgs)
+        if res:
+            dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashCustomMicroPython(self):
+        """
+        Private slot to flash a custom MicroPython firmware to the device.
+        """
+        downloadsPath = QStandardPaths.standardLocations(
+            QStandardPaths.DownloadLocation)[0]
+        firmware = E5FileDialog.getOpenFileName(
+            None,
+            self.tr("Flash Custom MicroPython Firmware"),
+            downloadsPath,
+            self.tr("MicroPython Firmware Files (*.hex);;All Files (*)"))
+        if firmware and os.path.exists(firmware):
+            flashArgs = [
+                "-u",
+                "-m", "uflash",
+                "--runtime", firmware,
+            ]
+            dlg = E5ProcessDialog(
+                self.tr("'uflash' Output"),
+                self.tr("Flash Default MicroPython Firmware"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashScript(self):
+        """
+        Private slot to flash the current script onto the selected device.
+        """
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if not aw:
+            return
+        
+        if not (aw.isPy3File() or aw.isPy2File()):
+            yes = E5MessageBox.yesNo(
+                None,
+                self.tr("Flash Script"),
+                self.tr("""The current editor does not contain a Python"""
+                        """ script. Flash it anyway?"""))
+            if not yes:
+                return
+        
+        script = aw.text().strip()
+        if not script:
+            E5MessageBox.warning(
+                self,
+                self.tr("Flash Script"),
+                self.tr("""The script is empty. Aborting."""))
+            return
+        
+        if aw.checkDirty():
+            filename = aw.getFileName()
+            flashArgs = [
+                "-u",
+                "-m", "uflash",
+                filename,
+            ]
+            dlg = E5ProcessDialog(self.tr("'uflash' Output"),
+                                  self.tr("Flash Script"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __saveMain(self):
+        """
+        Private slot to copy the current script as 'main.py' onto the
+        connected device.
+        """
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if not aw:
+            return
+        
+        if not (aw.isPy3File() or aw.isPy2File()):
+            yes = E5MessageBox.yesNo(
+                None,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""The current editor does not contain a Python"""
+                        """ script. Write it anyway?"""))
+            if not yes:
+                return
+        
+        script = aw.text().strip()
+        if not script:
+            E5MessageBox.warning(
+                self,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""The script is empty. Aborting."""))
+            return
+        
+        commands = [
+            "fd = open('main.py', 'wb')",
+            "f = fd.write",
+        ]
+        for line in script.splitlines():
+            commands.append("f(" + repr(line + "\n") + ")")
+        commands.append("fd.close()")
+        out, err = self.microPython.commandsInterface().execute(commands)
+        if err:
+            E5MessageBox.critical(
+                self,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""<p>The script could not be saved to the"""
+                        """ device.</p><p>Reason: {0}</p>""")
+                .format(err.decode("utf-8")))
+        
+        # reset the device
+        self.microPython.commandsInterface().execute([
+            "import microbit",
+            "microbit.reset()",
+        ])
+    
+    @pyqtSlot()
+    def __resetDevice(self):
+        """
+        Private slot to reset the connected device.
+        """
+        self.microPython.commandsInterface().execute([
+            "import microbit",
+            "microbit.reset()",
+        ])
+    
+    @pyqtSlot()
+    def __installUflashTool(self):
+        """
+        Private slot to install the uflash package via pip.
+        """
+        pip = e5App().getObject("Pip")
+        pip.installPackages(["uflash"], interpreter=sys.executable)

eric ide

mercurial