eric6/MicroPython/MicroPythonReplWidget.py

branch
micropython
changeset 7058
bdd583f96e96
parent 7054
fb84d8489bc1
child 7059
a8fad276cbd5
--- a/eric6/MicroPython/MicroPythonReplWidget.py	Sun Jul 07 18:48:17 2019 +0200
+++ b/eric6/MicroPython/MicroPythonReplWidget.py	Tue Jul 09 19:49:41 2019 +0200
@@ -11,17 +11,18 @@
 
 import re
 
-from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QEvent
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice
 from PyQt5.QtGui import QColor, QKeySequence, QTextCursor
 from PyQt5.QtWidgets import (
     QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy)
 try:
-    from PyQt5.QtSerialPort import QSerialPortInfo  # __IGNORE_WARNING__
+    from PyQt5.QtSerialPort import QSerialPort
     HAS_QTSERIALPORT = True
 except ImportError:
     HAS_QTSERIALPORT = False
 
 from E5Gui.E5ZoomWidget import E5ZoomWidget
+from E5Gui import E5MessageBox
 
 from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget
 
@@ -34,10 +35,18 @@
 class MicroPythonReplWidget(QWidget, Ui_MicroPythonReplWidget):
     """
     Class implementing the MicroPython REPL widget.
+    
+    @signal dataReceived(data) emitted to send data received via the serial
+        connection for further processing
     """
     ZoomMin = -10
     ZoomMax = 20
     
+    DeviceTypeRole = Qt.UserRole
+    DevicePortRole = Qt.UserRole + 1
+    
+    dataReceived = pyqtSignal(bytes)
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -80,6 +89,10 @@
         self.__device = None
         self.setConnected(False)
         
+        self.__replRunning = False
+        self.__plotterRunning = False
+        self.__fileManagerRunning = False
+        
         if not HAS_QTSERIALPORT:
             self.replEdit.setHtml(self.tr(
                 "<h3>The QtSerialPort package is not available.<br/>"
@@ -113,10 +126,17 @@
         if devices:
             self.deviceInfoLabel.setText(
                 self.tr("%n supported device(s) detected.", n=len(devices)))
+            
+            index = 0
             for device in sorted(devices):
+                index += 1
                 self.deviceTypeComboBox.addItem(
-                    self.tr("{0} at {1}".format(device[1], device[2])),
-                    device[0])
+                    self.tr("{0} at {1}".format(device[1], device[2])))
+                self.deviceTypeComboBox.setItemData(
+                    index, device[0], self.DeviceTypeRole)
+                self.deviceTypeComboBox.setItemData(
+                    index, device[2], self.DevicePortRole)
+                
         else:
             self.deviceInfoLabel.setText(
                 self.tr("No supported devices detected."))
@@ -131,16 +151,13 @@
         @param index index of the selected device
         @type int
         """
-        deviceType = self.deviceTypeComboBox.itemData(index)
+        deviceType = self.deviceTypeComboBox.itemData(
+            index, self.DeviceTypeRole)
         self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon(
             deviceType, False))
         
-        self.__device = MicroPythonDevices.getDevice(deviceType)
-        if self.__device:
-            actions = self.__device.supportedActions()
-        else:
-            actions = tuple()
-        self.__setActionButtons(actions)
+        self.__device = MicroPythonDevices.getDevice(deviceType, self)
+        self.__device.setButtons()
     
     @pyqtSlot()
     def on_checkButton_clicked(self):
@@ -149,18 +166,22 @@
         """
         self.__populateDeviceTypeComboBox()
     
-    def __setActionButtons(self, actions):
+    def setActionButtons(self, **kwargs):
         """
-        Private method to set the enabled state of the various action buttons.
+        Public method to set the enabled state of the various action buttons.
         
-        @param actions tuple of supported actions out of "repl", "run",
-            "files", "chart"
-        @type tuple of str
+        @param kwargs keyword arguments containg the enabled states (keys are
+            'run', 'repl', 'files', 'chart'
+        @type dict
         """
-        self.runButton.setEnabled("run" in actions)
-        self.replButton.setEnabled("repl" in actions)
-        self.filesButton.setEnabled("files" in actions)
-        self.chartButton.setEnabled("chart" in actions)
+        if "run" in kwargs:
+            self.runButton.setEnabled(kwargs["run"])
+        if "repl" in kwargs:
+            self.replButton.setEnabled(kwargs["repl"])
+        if "files" in kwargs:
+            self.filesButton.setEnabled(kwargs["files"])
+        if "chart" in kwargs:
+            self.chartButton.setEnabled(kwargs["chart"])
     
     @pyqtSlot(QPoint)
     def __showContextMenu(self, pos):
@@ -202,16 +223,60 @@
         """
         Private slot to connect to the selected device and start a REPL.
         """
-        # TODO: not implemented yet
-        raise NotImplementedError
+        if not self.__device:
+            E5MessageBox.critical(
+                self,
+                self.tr("No device attached"),
+                self.tr("""Please ensure the device is plugged inti your"""
+                        """ computer.\n\nIt must have a version of"""
+                        """ MicroPython (or CircuitPython) flashed onto it"""
+                        """ before the REPL will work.\n\nFinally press the"""
+                        """ device's reset button and wait a few seconds"""
+                        """ before trying again."""))
+            return
+        
+        if self.__replRunning:
+            self.dataReceived.disconnect(self.__processData)
+            self.on_disconnectButton_clicked()
+            self.__replRunning = False
+            self.__device.setRepl(False)
+        else:
+            ok, reason = self.__device.canStartRepl()
+            if not ok:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("Start REPL"),
+                    self.tr("""<p>The REPL cannot be started.</p><p>Reason:"""
+                            """ {0}</p>""").format(reason))
+                return
+                
+            if not self.__serial:
+                self.replEdit.clear()
+                self.dataReceived.connect(self.__processData)
+                self.__openSerialLink()
+                if self.__serial:
+                    if self.__device.forceInterrupt():
+                        # send a Ctrl-B (exit raw mode)
+                        self.__serial.write(b'\x02')
+                        # send Ctrl-C (keyboard interrupt)
+                        self.__serial.write(b'\x03')
     
     @pyqtSlot()
     def on_disconnectButton_clicked(self):
         """
         Private slot to disconnect from the currently connected device.
         """
-        # TODO: not implemented yet
-        raise NotImplementedError
+        if self.__replRunning:
+            self.on_replButton_clicked()
+        
+        # TODO: add more
+    
+    def __disconnect(self):
+        """
+        Private slot to disconnect the serial connection.
+        """
+        self.__closeSerialLink()
+        self.setConnected(False)
     
     def __activatePlotter(self):
         """
@@ -371,3 +436,61 @@
         elif value > self.__currentZoom:
             self.replEdit.zoomIn(value - self.__currentZoom)
         self.__currentZoom = value
+    
+    def __getCurrentPort(self):
+        """
+        Private method to determine the port path of the selected device.
+        
+        @return path of the port of the selected device
+        @rtype str
+        """
+        portName = self.deviceTypeComboBox.itemData(
+            self.deviceTypeComboBox.currentIndex(),
+            self.DevicePortRole)
+        
+        if Globals.isWindowsPlatform():
+            # return unchanged
+            return portName
+        else:
+            # return with device path prepended
+            return "/dev/{0}".format(portName)
+    
+    def __openSerialLink(self):
+        """
+        Private method to open a serial link to the selected device.
+        """
+        port = self.__getCurrentPort()
+        self.__serial = QSerialPort()
+        self.__serial.setPortName(port)
+        if self.__serial.open(QIODevice.ReadWrite):
+            self.__serial.setDataTerminalReady(True)
+            # 115.200 baud, 8N1
+            self.__serial.setBaudRate(115200)
+            self.__serial.setDataBits(QSerialPort.Data8)
+            self.__serial.setParity(QSerialPort.NoParity)
+            self.__serial.setStopBits(QSerialPort.OneStop)
+            self.__serial.readyRead.connect(self.__readSerial)
+        else:
+            E5MessageBox.warning(
+                self,
+                self.tr("Serial Device Connect"),
+                self.tr("""<p>Cannot connect to device at serial port"""
+                        """ <b>{0}</b>.</p>""").format(port))
+            self.__serial = None
+    
+    def __closeSerialLink(self):
+        """
+        Private method to close the open serial connection.
+        """
+        if self.__serial:
+            self.__serial.close()
+            self.__serial = None
+    
+    @pyqtSlot()
+    def __readSerial(self):
+        """
+        Private slot to read all available serial data and emit it with the
+        "dataReceived" signal for further processing.
+        """
+        data = bytes(self.__serial.readAll())
+        self.dataReceived.emit(data)

eric ide

mercurial