Merged with branch 'eric7' to get all the latest updates. mpy_network

Thu, 04 May 2023 17:31:13 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 04 May 2023 17:31:13 +0200
branch
mpy_network
changeset 10013
fbdc2a4f017c
parent 10012
d649d500a9a1 (diff)
parent 10007
f42cb90ea7dc (current diff)
child 10014
951a8d558e23

Merged with branch 'eric7' to get all the latest updates.

--- a/Dictionaries/words.dic	Tue May 02 11:39:09 2023 +0200
+++ b/Dictionaries/words.dic	Thu May 04 17:31:13 2023 +0200
@@ -8,3 +8,4 @@
 param
 keyparam
 rtype
+instantiation
--- a/eric7.epj	Tue May 02 11:39:09 2023 +0200
+++ b/eric7.epj	Thu May 04 17:31:13 2023 +0200
@@ -352,6 +352,9 @@
       "src/eric7/MicroPython/IgnoredDevicesDialog.ui",
       "src/eric7/MicroPython/MicroPythonFileManagerWidget.ui",
       "src/eric7/MicroPython/MicroPythonProgressInfoDialog.ui",
+      "src/eric7/MicroPython/MicroPythonWebreplConnectionDialog.ui",
+      "src/eric7/MicroPython/MicroPythonWebreplUrlAddEditDialog.ui",
+      "src/eric7/MicroPython/MicroPythonWebreplUrlsConfigDialog.ui",
       "src/eric7/MicroPython/MicroPythonWidget.ui",
       "src/eric7/MicroPython/MipPackageDialog.ui",
       "src/eric7/MicroPython/NtpParametersDialog.ui",
@@ -1347,7 +1350,14 @@
       "src/eric7/MicroPython/MicroPythonFileSystemUtilities.py",
       "src/eric7/MicroPython/MicroPythonGraphWidget.py",
       "src/eric7/MicroPython/MicroPythonProgressInfoDialog.py",
+      "src/eric7/MicroPython/MicroPythonReplWidget.py",
+      "src/eric7/MicroPython/MicroPythonSerialDeviceInterface.py",
       "src/eric7/MicroPython/MicroPythonSerialPort.py",
+      "src/eric7/MicroPython/MicroPythonWebreplConnectionDialog.py",
+      "src/eric7/MicroPython/MicroPythonWebreplDeviceInterface.py",
+      "src/eric7/MicroPython/MicroPythonWebreplSocket.py",
+      "src/eric7/MicroPython/MicroPythonWebreplUrlAddEditDialog.py",
+      "src/eric7/MicroPython/MicroPythonWebreplUrlsConfigDialog.py",
       "src/eric7/MicroPython/MicroPythonWidget.py",
       "src/eric7/MicroPython/MipLocalInstaller.py",
       "src/eric7/MicroPython/MipPackageDialog.py",
--- a/src/eric7/APIs/Python3/eric7.api	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/APIs/Python3/eric7.api	Thu May 04 17:31:13 2023 +0200
@@ -2692,6 +2692,7 @@
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.disconnectWifi?4()
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.downloadFirmware?4()
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.ensurePath?4(target)
+eric7.MicroPython.Devices.DeviceBase.BaseDevice.executeCommands?4(commands, *, mode="raw", timeout=0)
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.exists?4(pathname)
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.fileSystemInfo?4()
 eric7.MicroPython.Devices.DeviceBase.BaseDevice.forceInterrupt?4()
@@ -2954,15 +2955,12 @@
 eric7.MicroPython.EthernetDialogs.WiznetUtilities.mpyWiznetInit?4()
 eric7.MicroPython.IgnoredDevicesDialog.IgnoredDevicesDialog.getDevices?4()
 eric7.MicroPython.IgnoredDevicesDialog.IgnoredDevicesDialog?1(deviceList, parent=None)
-eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.PasteModePrompt?7
-eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.TracebackMarker?7
-eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.connectToDevice?4(port)
+eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.connectToDevice?4(connection)
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.dataReceived?7
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.disconnectFromDevice?4()
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.execute?4(commands, *, mode="raw", timeout=0)
-eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.executeAsync?4(commandsList)
+eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.executeAsync?4(commandsList, submitMode)
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.executeAsyncFinished?7
-eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.executeAsyncPaste?4(commandsList)
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.handlePreferencesChanged?4()
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.isConnected?4()
 eric7.MicroPython.MicroPythonDeviceInterface.MicroPythonDeviceInterface.probeDevice?4()
@@ -3037,6 +3035,17 @@
 eric7.MicroPython.MicroPythonGraphWidget.MicroPythonGraphWidget?1(parent=None)
 eric7.MicroPython.MicroPythonProgressInfoDialog.MicroPythonProgressInfoDialog.addMessage?4(message)
 eric7.MicroPython.MicroPythonProgressInfoDialog.MicroPythonProgressInfoDialog?1(parent=None)
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.PasteModePrompt?7
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.TracebackMarker?7
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.connectToDevice?4(connection)
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.disconnectFromDevice?4()
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.execute?4(commands, *, mode="raw", timeout=0)
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.executeAsync?4(commandsList, submitMode)
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.handlePreferencesChanged?4()
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.isConnected?4()
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.probeDevice?4()
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface.write?4(data)
+eric7.MicroPython.MicroPythonSerialDeviceInterface.MicroPythonSerialDeviceInterface?1(parent=None)
 eric7.MicroPython.MicroPythonSerialPort.MicroPythonSerialPort.closeSerialLink?4()
 eric7.MicroPython.MicroPythonSerialPort.MicroPythonSerialPort.hasTimedOut?4()
 eric7.MicroPython.MicroPythonSerialPort.MicroPythonSerialPort.isConnected?4()
@@ -3046,6 +3055,7 @@
 eric7.MicroPython.MicroPythonSerialPort.MicroPythonSerialPort?1(timeout=10000, parent=None)
 eric7.MicroPython.MicroPythonWidget.AnsiColorSchemes?7
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.DeviceBoardRole?7
+eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.DeviceInterfaceTypeRole?7
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.DevicePidRole?7
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.DevicePortRole?7
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.DeviceSerNoRole?7
@@ -3058,7 +3068,6 @@
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.deviceInterface?4()
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.deviceSupportsLocalFileAccess?4()
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.eventFilter?4(obj, evt)
-eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.getCurrentBoard?4()
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.getCurrentPort?4()
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.getDevice?4()
 eric7.MicroPython.MicroPythonWidget.MicroPythonWidget.getDeviceWorkspace?4()
@@ -7975,7 +7984,6 @@
 eric7.Project.CreateDialogCodeDialog.pyqtSignatureRole?7
 eric7.Project.CreateDialogCodeDialog.pythonSignatureRole?7
 eric7.Project.CreateDialogCodeDialog.returnTypeRole?7
-eric7.Project.CreateDialogCodeDialog.rubySignatureRole?7
 eric7.Project.DebuggerPropertiesDialog.DebuggerPropertiesDialog.on_debugClientClearHistoryButton_clicked?4()
 eric7.Project.DebuggerPropertiesDialog.DebuggerPropertiesDialog.on_debugClientPicker_aboutToShowPathPickerDialog?4()
 eric7.Project.DebuggerPropertiesDialog.DebuggerPropertiesDialog.storeData?4()
@@ -8329,7 +8337,6 @@
 eric7.Project.ProjectOthersBrowser.ProjectOthersBrowser.showMenu?7
 eric7.Project.ProjectOthersBrowser.ProjectOthersBrowser?1(project, projectBrowser, parent=None)
 eric7.Project.ProjectResourcesBrowser.ProjectResourcesBrowser.RCFilenameFormatPython?7
-eric7.Project.ProjectResourcesBrowser.ProjectResourcesBrowser.RCFilenameFormatRuby?7
 eric7.Project.ProjectResourcesBrowser.ProjectResourcesBrowser._contextMenuRequested?5(coord)
 eric7.Project.ProjectResourcesBrowser.ProjectResourcesBrowser._createPopupMenus?5()
 eric7.Project.ProjectResourcesBrowser.ProjectResourcesBrowser._initHookMethods?5()
@@ -8404,10 +8411,12 @@
 eric7.Project.TranslationPropertiesDialog.TranslationPropertiesDialog?1(project, new, parent)
 eric7.Project.UicCompilerOptionsDialog.UicCompilerOptionsDialog.getData?4()
 eric7.Project.UicCompilerOptionsDialog.UicCompilerOptionsDialog?1(compilerOptions, compiler, parent=None)
+eric7.Project.UicLoadUi5._printerr?5(dataString)
 eric7.Project.UicLoadUi5._printout?5(dataString)
 eric7.Project.UicLoadUi5.className?4(formFile, projectPath)
 eric7.Project.UicLoadUi5.objectName?4(formFile, projectPath)
 eric7.Project.UicLoadUi5.signatures?4(formFile, projectPath)
+eric7.Project.UicLoadUi6._printerr?5(dataString)
 eric7.Project.UicLoadUi6._printout?5(dataString)
 eric7.Project.UicLoadUi6.className?4(formFile, projectPath)
 eric7.Project.UicLoadUi6.objectName?4(formFile, projectPath)
--- a/src/eric7/APIs/Python3/eric7.bas	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/APIs/Python3/eric7.bas	Thu May 04 17:31:13 2023 +0200
@@ -664,6 +664,7 @@
 MicroPythonGraphWidget QWidget
 MicroPythonPage ConfigurationPageBase Ui_MicroPythonPage
 MicroPythonProgressInfoDialog QDialog Ui_MicroPythonProgressInfoDialog
+MicroPythonSerialDeviceInterface MicroPythonDeviceInterface
 MicroPythonSerialPort QSerialPort
 MicroPythonWidget QWidget Ui_MicroPythonWidget
 MicrobitDevice BaseDevice
Binary file src/eric7/Documentation/Help/source.qch has changed
--- a/src/eric7/Documentation/Help/source.qhp	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Help/source.qhp	Thu May 04 17:31:13 2023 +0200
@@ -327,6 +327,7 @@
             <section title="eric7.MicroPython.MicroPythonFileSystemUtilities" ref="eric7.MicroPython.MicroPythonFileSystemUtilities.html" />
             <section title="eric7.MicroPython.MicroPythonGraphWidget" ref="eric7.MicroPython.MicroPythonGraphWidget.html" />
             <section title="eric7.MicroPython.MicroPythonProgressInfoDialog" ref="eric7.MicroPython.MicroPythonProgressInfoDialog.html" />
+            <section title="eric7.MicroPython.MicroPythonSerialDeviceInterface" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html" />
             <section title="eric7.MicroPython.MicroPythonSerialPort" ref="eric7.MicroPython.MicroPythonSerialPort.html" />
             <section title="eric7.MicroPython.MicroPythonWidget" ref="eric7.MicroPython.MicroPythonWidget.html" />
             <section title="eric7.MicroPython.MipLocalInstaller" ref="eric7.MicroPython.MipLocalInstaller.html" />
@@ -2097,6 +2098,7 @@
       <keyword name="BaseDevice.disconnectWifi" id="BaseDevice.disconnectWifi" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.disconnectWifi" />
       <keyword name="BaseDevice.downloadFirmware" id="BaseDevice.downloadFirmware" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.downloadFirmware" />
       <keyword name="BaseDevice.ensurePath" id="BaseDevice.ensurePath" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.ensurePath" />
+      <keyword name="BaseDevice.executeCommands" id="BaseDevice.executeCommands" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.executeCommands" />
       <keyword name="BaseDevice.exists" id="BaseDevice.exists" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.exists" />
       <keyword name="BaseDevice.fileSystemInfo" id="BaseDevice.fileSystemInfo" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.fileSystemInfo" />
       <keyword name="BaseDevice.forceInterrupt" id="BaseDevice.forceInterrupt" ref="eric7.MicroPython.Devices.DeviceBase.html#BaseDevice.forceInterrupt" />
@@ -3642,7 +3644,6 @@
       <keyword name="CreateDialogCodeDialog (Module)" id="CreateDialogCodeDialog (Module)" ref="eric7.Project.CreateDialogCodeDialog.html" />
       <keyword name="CreateDialogCodeDialog.__className" id="CreateDialogCodeDialog.__className" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__className" />
       <keyword name="CreateDialogCodeDialog.__generateCode" id="CreateDialogCodeDialog.__generateCode" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__generateCode" />
-      <keyword name="CreateDialogCodeDialog.__generatePythonCode" id="CreateDialogCodeDialog.__generatePythonCode" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__generatePythonCode" />
       <keyword name="CreateDialogCodeDialog.__mapType" id="CreateDialogCodeDialog.__mapType" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__mapType" />
       <keyword name="CreateDialogCodeDialog.__objectName" id="CreateDialogCodeDialog.__objectName" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__objectName" />
       <keyword name="CreateDialogCodeDialog.__runUicLoadUi" id="CreateDialogCodeDialog.__runUicLoadUi" ref="eric7.Project.CreateDialogCodeDialog.html#CreateDialogCodeDialog.__runUicLoadUi" />
@@ -10793,18 +10794,10 @@
       <keyword name="MicroPythonDeviceInterface" id="MicroPythonDeviceInterface" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface" />
       <keyword name="MicroPythonDeviceInterface (Constructor)" id="MicroPythonDeviceInterface (Constructor)" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__init__" />
       <keyword name="MicroPythonDeviceInterface (Module)" id="MicroPythonDeviceInterface (Module)" ref="eric7.MicroPython.MicroPythonDeviceInterface.html" />
-      <keyword name="MicroPythonDeviceInterface.__execute_paste" id="MicroPythonDeviceInterface.__execute_paste" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__execute_paste" />
-      <keyword name="MicroPythonDeviceInterface.__execute_raw" id="MicroPythonDeviceInterface.__execute_raw" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__execute_raw" />
-      <keyword name="MicroPythonDeviceInterface.__pasteOff" id="MicroPythonDeviceInterface.__pasteOff" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__pasteOff" />
-      <keyword name="MicroPythonDeviceInterface.__pasteOn" id="MicroPythonDeviceInterface.__pasteOn" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__pasteOn" />
-      <keyword name="MicroPythonDeviceInterface.__rawOff" id="MicroPythonDeviceInterface.__rawOff" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__rawOff" />
-      <keyword name="MicroPythonDeviceInterface.__rawOn" id="MicroPythonDeviceInterface.__rawOn" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__rawOn" />
-      <keyword name="MicroPythonDeviceInterface.__readSerial" id="MicroPythonDeviceInterface.__readSerial" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.__readSerial" />
       <keyword name="MicroPythonDeviceInterface.connectToDevice" id="MicroPythonDeviceInterface.connectToDevice" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.connectToDevice" />
       <keyword name="MicroPythonDeviceInterface.disconnectFromDevice" id="MicroPythonDeviceInterface.disconnectFromDevice" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.disconnectFromDevice" />
       <keyword name="MicroPythonDeviceInterface.execute" id="MicroPythonDeviceInterface.execute" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.execute" />
       <keyword name="MicroPythonDeviceInterface.executeAsync" id="MicroPythonDeviceInterface.executeAsync" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.executeAsync" />
-      <keyword name="MicroPythonDeviceInterface.executeAsyncPaste" id="MicroPythonDeviceInterface.executeAsyncPaste" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.executeAsyncPaste" />
       <keyword name="MicroPythonDeviceInterface.handlePreferencesChanged" id="MicroPythonDeviceInterface.handlePreferencesChanged" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.handlePreferencesChanged" />
       <keyword name="MicroPythonDeviceInterface.isConnected" id="MicroPythonDeviceInterface.isConnected" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.isConnected" />
       <keyword name="MicroPythonDeviceInterface.probeDevice" id="MicroPythonDeviceInterface.probeDevice" ref="eric7.MicroPython.MicroPythonDeviceInterface.html#MicroPythonDeviceInterface.probeDevice" />
@@ -10902,6 +10895,26 @@
       <keyword name="MicroPythonProgressInfoDialog (Constructor)" id="MicroPythonProgressInfoDialog (Constructor)" ref="eric7.MicroPython.MicroPythonProgressInfoDialog.html#MicroPythonProgressInfoDialog.__init__" />
       <keyword name="MicroPythonProgressInfoDialog (Module)" id="MicroPythonProgressInfoDialog (Module)" ref="eric7.MicroPython.MicroPythonProgressInfoDialog.html" />
       <keyword name="MicroPythonProgressInfoDialog.addMessage" id="MicroPythonProgressInfoDialog.addMessage" ref="eric7.MicroPython.MicroPythonProgressInfoDialog.html#MicroPythonProgressInfoDialog.addMessage" />
+      <keyword name="MicroPythonSerialDeviceInterface" id="MicroPythonSerialDeviceInterface" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface" />
+      <keyword name="MicroPythonSerialDeviceInterface (Constructor)" id="MicroPythonSerialDeviceInterface (Constructor)" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__init__" />
+      <keyword name="MicroPythonSerialDeviceInterface (Module)" id="MicroPythonSerialDeviceInterface (Module)" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html" />
+      <keyword name="MicroPythonSerialDeviceInterface.__executeAsyncPaste" id="MicroPythonSerialDeviceInterface.__executeAsyncPaste" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__executeAsyncPaste" />
+      <keyword name="MicroPythonSerialDeviceInterface.__executeAsyncRaw" id="MicroPythonSerialDeviceInterface.__executeAsyncRaw" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__executeAsyncRaw" />
+      <keyword name="MicroPythonSerialDeviceInterface.__execute_paste" id="MicroPythonSerialDeviceInterface.__execute_paste" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__execute_paste" />
+      <keyword name="MicroPythonSerialDeviceInterface.__execute_raw" id="MicroPythonSerialDeviceInterface.__execute_raw" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__execute_raw" />
+      <keyword name="MicroPythonSerialDeviceInterface.__pasteOff" id="MicroPythonSerialDeviceInterface.__pasteOff" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__pasteOff" />
+      <keyword name="MicroPythonSerialDeviceInterface.__pasteOn" id="MicroPythonSerialDeviceInterface.__pasteOn" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__pasteOn" />
+      <keyword name="MicroPythonSerialDeviceInterface.__rawOff" id="MicroPythonSerialDeviceInterface.__rawOff" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__rawOff" />
+      <keyword name="MicroPythonSerialDeviceInterface.__rawOn" id="MicroPythonSerialDeviceInterface.__rawOn" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__rawOn" />
+      <keyword name="MicroPythonSerialDeviceInterface.__readSerial" id="MicroPythonSerialDeviceInterface.__readSerial" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.__readSerial" />
+      <keyword name="MicroPythonSerialDeviceInterface.connectToDevice" id="MicroPythonSerialDeviceInterface.connectToDevice" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.connectToDevice" />
+      <keyword name="MicroPythonSerialDeviceInterface.disconnectFromDevice" id="MicroPythonSerialDeviceInterface.disconnectFromDevice" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.disconnectFromDevice" />
+      <keyword name="MicroPythonSerialDeviceInterface.execute" id="MicroPythonSerialDeviceInterface.execute" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.execute" />
+      <keyword name="MicroPythonSerialDeviceInterface.executeAsync" id="MicroPythonSerialDeviceInterface.executeAsync" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.executeAsync" />
+      <keyword name="MicroPythonSerialDeviceInterface.handlePreferencesChanged" id="MicroPythonSerialDeviceInterface.handlePreferencesChanged" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.handlePreferencesChanged" />
+      <keyword name="MicroPythonSerialDeviceInterface.isConnected" id="MicroPythonSerialDeviceInterface.isConnected" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.isConnected" />
+      <keyword name="MicroPythonSerialDeviceInterface.probeDevice" id="MicroPythonSerialDeviceInterface.probeDevice" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.probeDevice" />
+      <keyword name="MicroPythonSerialDeviceInterface.write" id="MicroPythonSerialDeviceInterface.write" ref="eric7.MicroPython.MicroPythonSerialDeviceInterface.html#MicroPythonSerialDeviceInterface.write" />
       <keyword name="MicroPythonSerialPort" id="MicroPythonSerialPort" ref="eric7.MicroPython.MicroPythonSerialPort.html#MicroPythonSerialPort" />
       <keyword name="MicroPythonSerialPort (Constructor)" id="MicroPythonSerialPort (Constructor)" ref="eric7.MicroPython.MicroPythonSerialPort.html#MicroPythonSerialPort.__init__" />
       <keyword name="MicroPythonSerialPort (Module)" id="MicroPythonSerialPort (Module)" ref="eric7.MicroPython.MicroPythonSerialPort.html" />
@@ -10954,7 +10967,6 @@
       <keyword name="MicroPythonWidget.deviceInterface" id="MicroPythonWidget.deviceInterface" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.deviceInterface" />
       <keyword name="MicroPythonWidget.deviceSupportsLocalFileAccess" id="MicroPythonWidget.deviceSupportsLocalFileAccess" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.deviceSupportsLocalFileAccess" />
       <keyword name="MicroPythonWidget.eventFilter" id="MicroPythonWidget.eventFilter" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.eventFilter" />
-      <keyword name="MicroPythonWidget.getCurrentBoard" id="MicroPythonWidget.getCurrentBoard" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.getCurrentBoard" />
       <keyword name="MicroPythonWidget.getCurrentPort" id="MicroPythonWidget.getCurrentPort" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.getCurrentPort" />
       <keyword name="MicroPythonWidget.getDevice" id="MicroPythonWidget.getDevice" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.getDevice" />
       <keyword name="MicroPythonWidget.getDeviceWorkspace" id="MicroPythonWidget.getDeviceWorkspace" ref="eric7.MicroPython.MicroPythonWidget.html#MicroPythonWidget.getDeviceWorkspace" />
@@ -19188,6 +19200,8 @@
       <keyword name="_percentReplacementFunc" id="_percentReplacementFunc" ref="eric7.Utilities.__init__.html#_percentReplacementFunc" />
       <keyword name="_prettifyJSON" id="_prettifyJSON" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html#_prettifyJSON" />
       <keyword name="_prettifyXML" id="_prettifyXML" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html#_prettifyXML" />
+      <keyword name="_printerr" id="_printerr" ref="eric7.Project.UicLoadUi5.html#_printerr" />
+      <keyword name="_printerr" id="_printerr" ref="eric7.Project.UicLoadUi6.html#_printerr" />
       <keyword name="_printout" id="_printout" ref="eric7.Project.UicLoadUi5.html#_printout" />
       <keyword name="_printout" id="_printout" ref="eric7.Project.UicLoadUi6.html#_printout" />
       <keyword name="_shallPatch" id="_shallPatch" ref="eric7.DebugClients.Python.MultiProcessDebugExtension.html#_shallPatch" />
@@ -20566,6 +20580,7 @@
       <file>eric7.MicroPython.MicroPythonFileSystemUtilities.html</file>
       <file>eric7.MicroPython.MicroPythonGraphWidget.html</file>
       <file>eric7.MicroPython.MicroPythonProgressInfoDialog.html</file>
+      <file>eric7.MicroPython.MicroPythonSerialDeviceInterface.html</file>
       <file>eric7.MicroPython.MicroPythonSerialPort.html</file>
       <file>eric7.MicroPython.MicroPythonWidget.html</file>
       <file>eric7.MicroPython.MipLocalInstaller.html</file>
--- a/src/eric7/Documentation/Source/eric7.MicroPython.Devices.DeviceBase.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.Devices.DeviceBase.html	Thu May 04 17:31:13 2023 +0200
@@ -251,6 +251,10 @@
 <td>Public method to ensure, that the given target path exists.</td>
 </tr>
 <tr>
+<td><a href="#BaseDevice.executeCommands">executeCommands</a></td>
+<td>Public method to send commands to the connected device and return the result.</td>
+</tr>
+<tr>
 <td><a href="#BaseDevice.exists">exists</a></td>
 <td>Public method to check the existence of a file or directory.</td>
 </tr>
@@ -1070,6 +1074,46 @@
 target directory
 </dd>
 </dl>
+<a NAME="BaseDevice.executeCommands" ID="BaseDevice.executeCommands"></a>
+<h4>BaseDevice.executeCommands</h4>
+<b>executeCommands</b>(<i>commands, *, mode="raw", timeout=0</i>)
+
+<p>
+        Public method to send commands to the connected device and return the
+        result.
+</p>
+<p>
+        If no connected interface is available, empty results will be returned.
+</p>
+<dl>
+
+<dt><i>commands</i> (str or list of str)</dt>
+<dd>
+list of commands to be executed
+</dd>
+<dt><i>mode=</i> (str)</dt>
+<dd>
+submit mode to be used (one of 'raw' or 'paste') (defaults to
+            'raw')
+</dd>
+<dt><i>timeout=</i> (int (optional))</dt>
+<dd>
+per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing stdout and stderr output of the device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (bytes, bytes)
+</dd>
+</dl>
 <a NAME="BaseDevice.exists" ID="BaseDevice.exists"></a>
 <h4>BaseDevice.exists</h4>
 <b>exists</b>(<i>pathname</i>)
--- a/src/eric7/Documentation/Source/eric7.MicroPython.MicroPythonDeviceInterface.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.MicroPythonDeviceInterface.html	Thu May 04 17:31:13 2023 +0200
@@ -9,7 +9,7 @@
 <h1>eric7.MicroPython.MicroPythonDeviceInterface</h1>
 
 <p>
-Module implementing some file system commands for MicroPython.
+Module  implementing an interface base class to talk to a connected MicroPython device.
 </p>
 <h3>Global Attributes</h3>
 
@@ -43,8 +43,8 @@
 
 <dt>dataReceived(data)</dt>
 <dd>
-emitted to send data received via the serial
-        connection for further processing
+emitted to send data received via the connection
+        for further processing
 </dd>
 <dt>executeAsyncFinished()</dt>
 <dd>
@@ -57,7 +57,7 @@
 <h3>Class Attributes</h3>
 
 <table>
-<tr><td>PasteModePrompt</td></tr><tr><td>TracebackMarker</td></tr>
+<tr><td>None</td></tr>
 </table>
 <h3>Class Methods</h3>
 
@@ -73,40 +73,12 @@
 <td>Constructor</td>
 </tr>
 <tr>
-<td><a href="#MicroPythonDeviceInterface.__execute_paste">__execute_paste</a></td>
-<td>Private method to send commands to the connected device using 'paste' mode and return the result.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__execute_raw">__execute_raw</a></td>
-<td>Private method to send commands to the connected device using 'raw REPL' mode and return the result.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__pasteOff">__pasteOff</a></td>
-<td>Private method to switch 'paste' mode off.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__pasteOn">__pasteOn</a></td>
-<td>Private method to switch the connected device to 'paste' mode.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__rawOff">__rawOff</a></td>
-<td>Private method to switch 'raw' mode off.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__rawOn">__rawOn</a></td>
-<td>Private method to switch the connected device to 'raw' mode.</td>
-</tr>
-<tr>
-<td><a href="#MicroPythonDeviceInterface.__readSerial">__readSerial</a></td>
-<td>Private slot to read all available serial data and emit it with the "dataReceived" signal for further processing.</td>
-</tr>
-<tr>
 <td><a href="#MicroPythonDeviceInterface.connectToDevice">connectToDevice</a></td>
-<td>Public slot to start the manager.</td>
+<td>Public slot to connect to the device.</td>
 </tr>
 <tr>
 <td><a href="#MicroPythonDeviceInterface.disconnectFromDevice">disconnectFromDevice</a></td>
-<td>Public slot to stop the thread.</td>
+<td>Public slot to disconnect from the device.</td>
 </tr>
 <tr>
 <td><a href="#MicroPythonDeviceInterface.execute">execute</a></td>
@@ -117,10 +89,6 @@
 <td>Public method to execute a series of commands over a period of time without returning any result (asynchronous execution).</td>
 </tr>
 <tr>
-<td><a href="#MicroPythonDeviceInterface.executeAsyncPaste">executeAsyncPaste</a></td>
-<td>Public method to execute a series of commands over a period of time without returning any result (asynchronous execution).</td>
-</tr>
-<tr>
 <td><a href="#MicroPythonDeviceInterface.handlePreferencesChanged">handlePreferencesChanged</a></td>
 <td>Public slot to handle a change of the preferences.</td>
 </tr>
@@ -157,154 +125,18 @@
 reference to the parent object
 </dd>
 </dl>
-<a NAME="MicroPythonDeviceInterface.__execute_paste" ID="MicroPythonDeviceInterface.__execute_paste"></a>
-<h4>MicroPythonDeviceInterface.__execute_paste</h4>
-<b>__execute_paste</b>(<i>commands, timeout=0</i>)
+<a NAME="MicroPythonDeviceInterface.connectToDevice" ID="MicroPythonDeviceInterface.connectToDevice"></a>
+<h4>MicroPythonDeviceInterface.connectToDevice</h4>
+<b>connectToDevice</b>(<i>connection</i>)
 
 <p>
-        Private method to send commands to the connected device using 'paste' mode
-        and return the result.
-</p>
-<p>
-        If no serial connection is available, empty results will be returned.
-</p>
-<dl>
-
-<dt><i>commands</i> (str or list of str)</dt>
-<dd>
-list of commands to be executed
-</dd>
-<dt><i>timeout</i> (int (optional))</dt>
-<dd>
-per command timeout in milliseconds (0 for configured default)
-            (defaults to 0)
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing stdout and stderr output of the device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bytes, bytes)
-</dd>
-</dl>
-<a NAME="MicroPythonDeviceInterface.__execute_raw" ID="MicroPythonDeviceInterface.__execute_raw"></a>
-<h4>MicroPythonDeviceInterface.__execute_raw</h4>
-<b>__execute_raw</b>(<i>commands, timeout=0</i>)
-
-<p>
-        Private method to send commands to the connected device using 'raw REPL' mode
-        and return the result.
-</p>
-<p>
-        If no serial connection is available, empty results will be returned.
+        Public slot to connect to the device.
 </p>
 <dl>
 
-<dt><i>commands</i> (str or list of str)</dt>
-<dd>
-list of commands to be executed
-</dd>
-<dt><i>timeout</i> (int (optional))</dt>
-<dd>
-per command timeout in milliseconds (0 for configured default)
-            (defaults to 0)
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing stdout and stderr output of the device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bytes, bytes)
-</dd>
-</dl>
-<a NAME="MicroPythonDeviceInterface.__pasteOff" ID="MicroPythonDeviceInterface.__pasteOff"></a>
-<h4>MicroPythonDeviceInterface.__pasteOff</h4>
-<b>__pasteOff</b>(<i></i>)
-
-<p>
-        Private method to switch 'paste' mode off.
-</p>
-<a NAME="MicroPythonDeviceInterface.__pasteOn" ID="MicroPythonDeviceInterface.__pasteOn"></a>
-<h4>MicroPythonDeviceInterface.__pasteOn</h4>
-<b>__pasteOn</b>(<i></i>)
-
-<p>
-        Private method to switch the connected device to 'paste' mode.
-</p>
-<p>
-        Note: switching to paste mode is done with synchronous writes.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating success
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
+<dt><i>connection</i> (str)</dt>
 <dd>
-bool
-</dd>
-</dl>
-<a NAME="MicroPythonDeviceInterface.__rawOff" ID="MicroPythonDeviceInterface.__rawOff"></a>
-<h4>MicroPythonDeviceInterface.__rawOff</h4>
-<b>__rawOff</b>(<i></i>)
-
-<p>
-        Private method to switch 'raw' mode off.
-</p>
-<a NAME="MicroPythonDeviceInterface.__rawOn" ID="MicroPythonDeviceInterface.__rawOn"></a>
-<h4>MicroPythonDeviceInterface.__rawOn</h4>
-<b>__rawOn</b>(<i></i>)
-
-<p>
-        Private method to switch the connected device to 'raw' mode.
-</p>
-<p>
-        Note: switching to raw mode is done with synchronous writes.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating success
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-bool
-</dd>
-</dl>
-<a NAME="MicroPythonDeviceInterface.__readSerial" ID="MicroPythonDeviceInterface.__readSerial"></a>
-<h4>MicroPythonDeviceInterface.__readSerial</h4>
-<b>__readSerial</b>(<i></i>)
-
-<p>
-        Private slot to read all available serial data and emit it with the
-        "dataReceived" signal for further processing.
-</p>
-<a NAME="MicroPythonDeviceInterface.connectToDevice" ID="MicroPythonDeviceInterface.connectToDevice"></a>
-<h4>MicroPythonDeviceInterface.connectToDevice</h4>
-<b>connectToDevice</b>(<i>port</i>)
-
-<p>
-        Public slot to start the manager.
-</p>
-<dl>
-
-<dt><i>port</i> (str)</dt>
-<dd>
-name of the port to be used
+name of the connection to be used
 </dd>
 </dl>
 <dl>
@@ -319,13 +151,29 @@
 bool
 </dd>
 </dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+</dl>
 <a NAME="MicroPythonDeviceInterface.disconnectFromDevice" ID="MicroPythonDeviceInterface.disconnectFromDevice"></a>
 <h4>MicroPythonDeviceInterface.disconnectFromDevice</h4>
 <b>disconnectFromDevice</b>(<i></i>)
 
 <p>
-        Public slot to stop the thread.
+        Public slot to disconnect from the device.
 </p>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+</dl>
 <a NAME="MicroPythonDeviceInterface.execute" ID="MicroPythonDeviceInterface.execute"></a>
 <h4>MicroPythonDeviceInterface.execute</h4>
 <b>execute</b>(<i>commands, *, mode="raw", timeout=0</i>)
@@ -335,7 +183,7 @@
         result.
 </p>
 <p>
-        If no serial connection is available, empty results will be returned.
+        If no connection is available, empty results will be returned.
 </p>
 <dl>
 
@@ -368,6 +216,11 @@
 </dl>
 <dl>
 
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
 <dt>Raises <b>ValueError</b>:</dt>
 <dd>
 raised in case of an unsupported submit mode
@@ -375,7 +228,7 @@
 </dl>
 <a NAME="MicroPythonDeviceInterface.executeAsync" ID="MicroPythonDeviceInterface.executeAsync"></a>
 <h4>MicroPythonDeviceInterface.executeAsync</h4>
-<b>executeAsync</b>(<i>commandsList</i>)
+<b>executeAsync</b>(<i>commandsList, submitMode</i>)
 
 <p>
         Public method to execute a series of commands over a period of time
@@ -383,24 +236,26 @@
 </p>
 <dl>
 
-<dt><i>commandsList</i> (list of bytes)</dt>
+<dt><i>commandsList</i> (list of str)</dt>
 <dd>
 list of commands to be execute on the device
 </dd>
+<dt><i>submitMode</i> (str)</dt>
+<dd>
+mode to be used to submit the commands (one of 'raw'
+            or 'paste')
+</dd>
 </dl>
-<a NAME="MicroPythonDeviceInterface.executeAsyncPaste" ID="MicroPythonDeviceInterface.executeAsyncPaste"></a>
-<h4>MicroPythonDeviceInterface.executeAsyncPaste</h4>
-<b>executeAsyncPaste</b>(<i>commandsList</i>)
-
-<p>
-        Public method to execute a series of commands over a period of time
-        without returning any result (asynchronous execution).
-</p>
 <dl>
 
-<dt><i>commandsList</i> (list of bytes)</dt>
+<dt>Raises <b>NotImplementedError</b>:</dt>
 <dd>
-list of commands to be execute on the device
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+<dt>Raises <b>ValueError</b>:</dt>
+<dd>
+raised to indicate an unknown submit mode
 </dd>
 </dl>
 <a NAME="MicroPythonDeviceInterface.handlePreferencesChanged" ID="MicroPythonDeviceInterface.handlePreferencesChanged"></a>
@@ -429,6 +284,14 @@
 bool
 </dd>
 </dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+</dl>
 <a NAME="MicroPythonDeviceInterface.probeDevice" ID="MicroPythonDeviceInterface.probeDevice"></a>
 <h4>MicroPythonDeviceInterface.probeDevice</h4>
 <b>probeDevice</b>(<i></i>)
@@ -437,7 +300,7 @@
         Public method to check the device is responding.
 </p>
 <p>
-        If the device has not been flashed with a MicroPython formware, the
+        If the device has not been flashed with a MicroPython firmware, the
         probe will fail.
 </p>
 <dl>
@@ -452,6 +315,14 @@
 bool
 </dd>
 </dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+</dl>
 <a NAME="MicroPythonDeviceInterface.write" ID="MicroPythonDeviceInterface.write"></a>
 <h4>MicroPythonDeviceInterface.write</h4>
 <b>write</b>(<i>data</i>)
@@ -466,6 +337,14 @@
 data to be written
 </dd>
 </dl>
+<dl>
+
+<dt>Raises <b>NotImplementedError</b>:</dt>
+<dd>
+raised to indicate that this method needs to
+            be implemented in a derived class
+</dd>
+</dl>
 <div align="right"><a href="#top">Up</a></div>
 <hr />
 </body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.MicroPythonSerialDeviceInterface.html	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,489 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.MicroPythonSerialDeviceInterface</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.MicroPythonSerialDeviceInterface</h1>
+
+<p>
+Module  implementing an interface to talk to a connected MicroPython device via
+a serial link.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface">MicroPythonSerialDeviceInterface</a></td>
+<td>Class implementing an interface to talk to a connected MicroPython device via a serial link.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="MicroPythonSerialDeviceInterface" ID="MicroPythonSerialDeviceInterface"></a>
+<h2>MicroPythonSerialDeviceInterface</h2>
+
+<p>
+    Class implementing an interface to talk to a connected MicroPython device via
+    a serial link.
+</p>
+<h3>Derived from</h3>
+MicroPythonDeviceInterface
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>PasteModePrompt</td></tr><tr><td>TracebackMarker</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__init__">MicroPythonSerialDeviceInterface</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__executeAsyncPaste">__executeAsyncPaste</a></td>
+<td>Private method to execute a series of commands over a period of time without returning any result (asynchronous execution).</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__executeAsyncRaw">__executeAsyncRaw</a></td>
+<td>Private method to execute a series of commands over a period of time without returning any result (asynchronous execution).</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__execute_paste">__execute_paste</a></td>
+<td>Private method to send commands to the connected device using 'paste' mode and return the result.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__execute_raw">__execute_raw</a></td>
+<td>Private method to send commands to the connected device using 'raw REPL' mode and return the result.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__pasteOff">__pasteOff</a></td>
+<td>Private method to switch 'paste' mode off.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__pasteOn">__pasteOn</a></td>
+<td>Private method to switch the connected device to 'paste' mode.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__rawOff">__rawOff</a></td>
+<td>Private method to switch 'raw' mode off.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__rawOn">__rawOn</a></td>
+<td>Private method to switch the connected device to 'raw' mode.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.__readSerial">__readSerial</a></td>
+<td>Private slot to read all available serial data and emit it with the "dataReceived" signal for further processing.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.connectToDevice">connectToDevice</a></td>
+<td>Public slot to connect to the device.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.disconnectFromDevice">disconnectFromDevice</a></td>
+<td>Public slot to disconnect from the device.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.execute">execute</a></td>
+<td>Public method to send commands to the connected device and return the result.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.executeAsync">executeAsync</a></td>
+<td>Public method to execute a series of commands over a period of time without returning any result (asynchronous execution).</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.handlePreferencesChanged">handlePreferencesChanged</a></td>
+<td>Public slot to handle a change of the preferences.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.isConnected">isConnected</a></td>
+<td>Public method to get the connection status.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.probeDevice">probeDevice</a></td>
+<td>Public method to check the device is responding.</td>
+</tr>
+<tr>
+<td><a href="#MicroPythonSerialDeviceInterface.write">write</a></td>
+<td>Public method to write data to the connected device.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="MicroPythonSerialDeviceInterface.__init__" ID="MicroPythonSerialDeviceInterface.__init__"></a>
+<h4>MicroPythonSerialDeviceInterface (Constructor)</h4>
+<b>MicroPythonSerialDeviceInterface</b>(<i>parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>parent</i> (QObject)</dt>
+<dd>
+reference to the parent object
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__executeAsyncPaste" ID="MicroPythonSerialDeviceInterface.__executeAsyncPaste"></a>
+<h4>MicroPythonSerialDeviceInterface.__executeAsyncPaste</h4>
+<b>__executeAsyncPaste</b>(<i>commandsList</i>)
+
+<p>
+        Private method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+</p>
+<dl>
+
+<dt><i>commandsList</i> (list of str)</dt>
+<dd>
+list of commands to be execute on the device
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__executeAsyncRaw" ID="MicroPythonSerialDeviceInterface.__executeAsyncRaw"></a>
+<h4>MicroPythonSerialDeviceInterface.__executeAsyncRaw</h4>
+<b>__executeAsyncRaw</b>(<i>commandsList</i>)
+
+<p>
+        Private method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+</p>
+<dl>
+
+<dt><i>commandsList</i> (list of bytes)</dt>
+<dd>
+list of commands to be execute on the device
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__execute_paste" ID="MicroPythonSerialDeviceInterface.__execute_paste"></a>
+<h4>MicroPythonSerialDeviceInterface.__execute_paste</h4>
+<b>__execute_paste</b>(<i>commands, timeout=0</i>)
+
+<p>
+        Private method to send commands to the connected device using 'paste' mode
+        and return the result.
+</p>
+<p>
+        If no serial connection is available, empty results will be returned.
+</p>
+<dl>
+
+<dt><i>commands</i> (str or list of str)</dt>
+<dd>
+list of commands to be executed
+</dd>
+<dt><i>timeout</i> (int (optional))</dt>
+<dd>
+per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing stdout and stderr output of the device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (bytes, bytes)
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__execute_raw" ID="MicroPythonSerialDeviceInterface.__execute_raw"></a>
+<h4>MicroPythonSerialDeviceInterface.__execute_raw</h4>
+<b>__execute_raw</b>(<i>commands, timeout=0</i>)
+
+<p>
+        Private method to send commands to the connected device using 'raw REPL' mode
+        and return the result.
+</p>
+<p>
+        If no serial connection is available, empty results will be returned.
+</p>
+<dl>
+
+<dt><i>commands</i> (str or list of str)</dt>
+<dd>
+list of commands to be executed
+</dd>
+<dt><i>timeout</i> (int (optional))</dt>
+<dd>
+per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing stdout and stderr output of the device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (bytes, bytes)
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__pasteOff" ID="MicroPythonSerialDeviceInterface.__pasteOff"></a>
+<h4>MicroPythonSerialDeviceInterface.__pasteOff</h4>
+<b>__pasteOff</b>(<i></i>)
+
+<p>
+        Private method to switch 'paste' mode off.
+</p>
+<a NAME="MicroPythonSerialDeviceInterface.__pasteOn" ID="MicroPythonSerialDeviceInterface.__pasteOn"></a>
+<h4>MicroPythonSerialDeviceInterface.__pasteOn</h4>
+<b>__pasteOn</b>(<i></i>)
+
+<p>
+        Private method to switch the connected device to 'paste' mode.
+</p>
+<p>
+        Note: switching to paste mode is done with synchronous writes.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating success
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__rawOff" ID="MicroPythonSerialDeviceInterface.__rawOff"></a>
+<h4>MicroPythonSerialDeviceInterface.__rawOff</h4>
+<b>__rawOff</b>(<i></i>)
+
+<p>
+        Private method to switch 'raw' mode off.
+</p>
+<a NAME="MicroPythonSerialDeviceInterface.__rawOn" ID="MicroPythonSerialDeviceInterface.__rawOn"></a>
+<h4>MicroPythonSerialDeviceInterface.__rawOn</h4>
+<b>__rawOn</b>(<i></i>)
+
+<p>
+        Private method to switch the connected device to 'raw' mode.
+</p>
+<p>
+        Note: switching to raw mode is done with synchronous writes.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating success
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.__readSerial" ID="MicroPythonSerialDeviceInterface.__readSerial"></a>
+<h4>MicroPythonSerialDeviceInterface.__readSerial</h4>
+<b>__readSerial</b>(<i></i>)
+
+<p>
+        Private slot to read all available serial data and emit it with the
+        "dataReceived" signal for further processing.
+</p>
+<a NAME="MicroPythonSerialDeviceInterface.connectToDevice" ID="MicroPythonSerialDeviceInterface.connectToDevice"></a>
+<h4>MicroPythonSerialDeviceInterface.connectToDevice</h4>
+<b>connectToDevice</b>(<i>connection</i>)
+
+<p>
+        Public slot to connect to the device.
+</p>
+<dl>
+
+<dt><i>connection</i> (str)</dt>
+<dd>
+name of the connection to be used
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating success
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.disconnectFromDevice" ID="MicroPythonSerialDeviceInterface.disconnectFromDevice"></a>
+<h4>MicroPythonSerialDeviceInterface.disconnectFromDevice</h4>
+<b>disconnectFromDevice</b>(<i></i>)
+
+<p>
+        Public slot to disconnect from the device.
+</p>
+<a NAME="MicroPythonSerialDeviceInterface.execute" ID="MicroPythonSerialDeviceInterface.execute"></a>
+<h4>MicroPythonSerialDeviceInterface.execute</h4>
+<b>execute</b>(<i>commands, *, mode="raw", timeout=0</i>)
+
+<p>
+        Public method to send commands to the connected device and return the
+        result.
+</p>
+<p>
+        If no serial connection is available, empty results will be returned.
+</p>
+<dl>
+
+<dt><i>commands</i> (str or list of str)</dt>
+<dd>
+list of commands to be executed
+</dd>
+<dt><i>mode=</i> (str)</dt>
+<dd>
+submit mode to be used (one of 'raw' or 'paste') (defaults to
+            'raw')
+</dd>
+<dt><i>timeout=</i> (int (optional))</dt>
+<dd>
+per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing stdout and stderr output of the device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (bytes, bytes)
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>ValueError</b>:</dt>
+<dd>
+raised in case of an unsupported submit mode
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.executeAsync" ID="MicroPythonSerialDeviceInterface.executeAsync"></a>
+<h4>MicroPythonSerialDeviceInterface.executeAsync</h4>
+<b>executeAsync</b>(<i>commandsList, submitMode</i>)
+
+<p>
+        Public method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+</p>
+<dl>
+
+<dt><i>commandsList</i> (list of str)</dt>
+<dd>
+list of commands to be execute on the device
+</dd>
+<dt><i>submitMode</i> (str (one of 'raw' or 'paste'))</dt>
+<dd>
+mode to be used to submit the commands
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>ValueError</b>:</dt>
+<dd>
+raised to indicate an unknown submit mode
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.handlePreferencesChanged" ID="MicroPythonSerialDeviceInterface.handlePreferencesChanged"></a>
+<h4>MicroPythonSerialDeviceInterface.handlePreferencesChanged</h4>
+<b>handlePreferencesChanged</b>(<i></i>)
+
+<p>
+        Public slot to handle a change of the preferences.
+</p>
+<a NAME="MicroPythonSerialDeviceInterface.isConnected" ID="MicroPythonSerialDeviceInterface.isConnected"></a>
+<h4>MicroPythonSerialDeviceInterface.isConnected</h4>
+<b>isConnected</b>(<i></i>)
+
+<p>
+        Public method to get the connection status.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the connection status
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.probeDevice" ID="MicroPythonSerialDeviceInterface.probeDevice"></a>
+<h4>MicroPythonSerialDeviceInterface.probeDevice</h4>
+<b>probeDevice</b>(<i></i>)
+
+<p>
+        Public method to check the device is responding.
+</p>
+<p>
+        If the device has not been flashed with a MicroPython firmware, the
+        probe will fail.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating a communicating MicroPython device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<a NAME="MicroPythonSerialDeviceInterface.write" ID="MicroPythonSerialDeviceInterface.write"></a>
+<h4>MicroPythonSerialDeviceInterface.write</h4>
+<b>write</b>(<i>data</i>)
+
+<p>
+        Public method to write data to the connected device.
+</p>
+<dl>
+
+<dt><i>data</i> (bytes or bytearray)</dt>
+<dd>
+data to be written
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/src/eric7/Documentation/Source/eric7.MicroPython.MicroPythonWidget.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.MicroPythonWidget.html	Thu May 04 17:31:13 2023 +0200
@@ -52,7 +52,7 @@
 <h3>Class Attributes</h3>
 
 <table>
-<tr><td>DeviceBoardRole</td></tr><tr><td>DevicePidRole</td></tr><tr><td>DevicePortRole</td></tr><tr><td>DeviceSerNoRole</td></tr><tr><td>DeviceTypeRole</td></tr><tr><td>DeviceVidRole</td></tr><tr><td>ManualMarker</td></tr><tr><td>ZoomMax</td></tr><tr><td>ZoomMin</td></tr>
+<tr><td>DeviceBoardRole</td></tr><tr><td>DeviceInterfaceTypeRole</td></tr><tr><td>DevicePidRole</td></tr><tr><td>DevicePortRole</td></tr><tr><td>DeviceSerNoRole</td></tr><tr><td>DeviceTypeRole</td></tr><tr><td>DeviceVidRole</td></tr><tr><td>ManualMarker</td></tr><tr><td>ZoomMax</td></tr><tr><td>ZoomMin</td></tr>
 </table>
 <h3>Class Methods</h3>
 
@@ -228,10 +228,6 @@
 <td>Public method to process events for the REPL pane.</td>
 </tr>
 <tr>
-<td><a href="#MicroPythonWidget.getCurrentBoard">getCurrentBoard</a></td>
-<td>Public method to get the board name of the selected device.</td>
-</tr>
-<tr>
 <td><a href="#MicroPythonWidget.getCurrentPort">getCurrentPort</a></td>
 <td>Public method to determine the port path of the selected device.</td>
 </tr>
@@ -388,6 +384,13 @@
             automatically
 </dd>
 </dl>
+<dl>
+
+<dt>Raises <b>ValueError</b>:</dt>
+<dd>
+raised to indicate an unsupported interface type
+</dd>
+</dl>
 <a NAME="MicroPythonWidget.__convertToUF2" ID="MicroPythonWidget.__convertToUF2"></a>
 <h4>MicroPythonWidget.__convertToUF2</h4>
 <b>__convertToUF2</b>(<i></i>)
@@ -847,25 +850,6 @@
 bool
 </dd>
 </dl>
-<a NAME="MicroPythonWidget.getCurrentBoard" ID="MicroPythonWidget.getCurrentBoard"></a>
-<h4>MicroPythonWidget.getCurrentBoard</h4>
-<b>getCurrentBoard</b>(<i></i>)
-
-<p>
-        Public method to get the board name of the selected device.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-board name of the selected device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-str
-</dd>
-</dl>
 <a NAME="MicroPythonWidget.getCurrentPort" ID="MicroPythonWidget.getCurrentPort"></a>
 <h4>MicroPythonWidget.getCurrentPort</h4>
 <b>getCurrentPort</b>(<i></i>)
--- a/src/eric7/Documentation/Source/eric7.Project.CreateDialogCodeDialog.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.Project.CreateDialogCodeDialog.html	Thu May 04 17:31:13 2023 +0200
@@ -14,7 +14,7 @@
 <h3>Global Attributes</h3>
 
 <table>
-<tr><td>parameterNamesListRole</td></tr><tr><td>parameterTypesListRole</td></tr><tr><td>pyqtSignatureRole</td></tr><tr><td>pythonSignatureRole</td></tr><tr><td>returnTypeRole</td></tr><tr><td>rubySignatureRole</td></tr>
+<tr><td>parameterNamesListRole</td></tr><tr><td>parameterTypesListRole</td></tr><tr><td>pyqtSignatureRole</td></tr><tr><td>pythonSignatureRole</td></tr><tr><td>returnTypeRole</td></tr>
 </table>
 <h3>Classes</h3>
 
@@ -64,10 +64,6 @@
 </tr>
 <tr>
 <td><a href="#CreateDialogCodeDialog.__generateCode">__generateCode</a></td>
-<td>Private slot to generate the code as requested by the user.</td>
-</tr>
-<tr>
-<td><a href="#CreateDialogCodeDialog.__generatePythonCode">__generatePythonCode</a></td>
 <td>Private slot to generate Python code as requested by the user.</td>
 </tr>
 <tr>
@@ -163,13 +159,6 @@
 <b>__generateCode</b>(<i></i>)
 
 <p>
-        Private slot to generate the code as requested by the user.
-</p>
-<a NAME="CreateDialogCodeDialog.__generatePythonCode" ID="CreateDialogCodeDialog.__generatePythonCode"></a>
-<h4>CreateDialogCodeDialog.__generatePythonCode</h4>
-<b>__generatePythonCode</b>(<i></i>)
-
-<p>
         Private slot to generate Python code as requested by the user.
 </p>
 <a NAME="CreateDialogCodeDialog.__mapType" ID="CreateDialogCodeDialog.__mapType"></a>
--- a/src/eric7/Documentation/Source/eric7.Project.ProjectResourcesBrowser.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.Project.ProjectResourcesBrowser.html	Thu May 04 17:31:13 2023 +0200
@@ -57,7 +57,7 @@
 <h3>Class Attributes</h3>
 
 <table>
-<tr><td>RCFilenameFormatPython</td></tr><tr><td>RCFilenameFormatRuby</td></tr>
+<tr><td>RCFilenameFormatPython</td></tr>
 </table>
 <h3>Class Methods</h3>
 
--- a/src/eric7/Documentation/Source/eric7.Project.UicLoadUi5.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.Project.UicLoadUi5.html	Thu May 04 17:31:13 2023 +0200
@@ -30,8 +30,12 @@
 <td>Private function to map a type as reported by Qt's meta object to the correct Python type.</td>
 </tr>
 <tr>
+<td><a href="#_printerr">_printerr</a></td>
+<td>Function to print the given string as error to sys.stdoerr with a guard string.</td>
+</tr>
+<tr>
 <td><a href="#_printout">_printout</a></td>
-<td>Function to print the given string to sys.stdout with a guard string.</td>
+<td>Function to print the given string as output to sys.stderr with a guard string.</td>
 </tr>
 <tr>
 <td><a href="#className">className</a></td>
@@ -78,12 +82,29 @@
 <div align="right"><a href="#top">Up</a></div>
 <hr />
 <hr />
+<a NAME="_printerr" ID="_printerr"></a>
+<h2>_printerr</h2>
+<b>_printerr</b>(<i>dataString</i>)
+
+<p>
+    Function to print the given string as error to sys.stdoerr with a guard string.
+</p>
+<dl>
+
+<dt><i>dataString</i> (str)</dt>
+<dd>
+string to be printed
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
 <a NAME="_printout" ID="_printout"></a>
 <h2>_printout</h2>
 <b>_printout</b>(<i>dataString</i>)
 
 <p>
-    Function to print the given string to sys.stdout with a guard string.
+    Function to print the given string as output to sys.stderr with a guard string.
 </p>
 <dl>
 
--- a/src/eric7/Documentation/Source/eric7.Project.UicLoadUi6.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.Project.UicLoadUi6.html	Thu May 04 17:31:13 2023 +0200
@@ -30,8 +30,12 @@
 <td>Private function to map a type as reported by Qt's meta object to the correct Python type.</td>
 </tr>
 <tr>
+<td><a href="#_printerr">_printerr</a></td>
+<td>Function to print the given string as error to sys.stdoerr with a guard string.</td>
+</tr>
+<tr>
 <td><a href="#_printout">_printout</a></td>
-<td>Function to print the given string to sys.stdout with a guard string.</td>
+<td>Function to print the given string as output to sys.stderr with a guard string.</td>
 </tr>
 <tr>
 <td><a href="#className">className</a></td>
@@ -78,12 +82,29 @@
 <div align="right"><a href="#top">Up</a></div>
 <hr />
 <hr />
+<a NAME="_printerr" ID="_printerr"></a>
+<h2>_printerr</h2>
+<b>_printerr</b>(<i>dataString</i>)
+
+<p>
+    Function to print the given string as error to sys.stdoerr with a guard string.
+</p>
+<dl>
+
+<dt><i>dataString</i> (str)</dt>
+<dd>
+string to be printed
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
 <a NAME="_printout" ID="_printout"></a>
 <h2>_printout</h2>
 <b>_printout</b>(<i>dataString</i>)
 
 <p>
-    Function to print the given string to sys.stdout with a guard string.
+    Function to print the given string as output to sys.stderr with a guard string.
 </p>
 <dl>
 
--- a/src/eric7/Documentation/Source/index-eric7.MicroPython.html	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Documentation/Source/index-eric7.MicroPython.html	Thu May 04 17:31:13 2023 +0200
@@ -61,7 +61,7 @@
 </tr>
 <tr>
 <td><a href="eric7.MicroPython.MicroPythonDeviceInterface.html">MicroPythonDeviceInterface</a></td>
-<td>Module implementing some file system commands for MicroPython.</td>
+<td>Module  implementing an interface base class to talk to a connected MicroPython device.</td>
 </tr>
 <tr>
 <td><a href="eric7.MicroPython.MicroPythonFileManager.html">MicroPythonFileManager</a></td>
@@ -84,6 +84,10 @@
 <td>Module implementing a dialog to show progress messages.</td>
 </tr>
 <tr>
+<td><a href="eric7.MicroPython.MicroPythonSerialDeviceInterface.html">MicroPythonSerialDeviceInterface</a></td>
+<td>Module  implementing an interface to talk to a connected MicroPython device via a serial link.</td>
+</tr>
+<tr>
 <td><a href="eric7.MicroPython.MicroPythonSerialPort.html">MicroPythonSerialPort</a></td>
 <td>Module implementing a QSerialPort with additional functionality for MicroPython devices.</td>
 </tr>
--- a/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/CircuitPythonDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -469,7 +469,7 @@
         Private slot to reset the connected device.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 "import microcontroller as mc\n"
                 "mc.on_next_reset(mc.RunMode.NORMAL)"
                 "mc.reset()\n",
@@ -501,7 +501,7 @@
         Private slot to switch the board into 'bootloader' mode.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 "import microcontroller as mc\n"
                 "mc.on_next_reset(mc.RunMode.BOOTLOADER)\n"
                 "mc.reset()\n",
@@ -514,7 +514,7 @@
         Private slot to switch the board into 'UF2 Boot' mode.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 "import microcontroller as mc\n"
                 "mc.on_next_reset(mc.RunMode.UF2)\n"
                 "mc.reset()\n",
@@ -764,7 +764,7 @@
 print(has_wifi())
 del has_wifi
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return ast.literal_eval(out.decode("utf-8"))
@@ -848,7 +848,7 @@
 del wifi_status
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -902,7 +902,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -938,7 +938,7 @@
 del disconnect_wifi
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -1070,7 +1070,7 @@
 del check_internet
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -1107,9 +1107,7 @@
 del scan_networks
 """
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return [], err
 
@@ -1162,7 +1160,7 @@
 del deactivate
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
         else:
@@ -1214,9 +1212,7 @@
             repr(ssid), repr(password), authmode
         )
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return False, err
         elif out and out.startswith(b"Error:"):
@@ -1245,7 +1241,7 @@
 del stop_ap
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
         elif out and out.startswith(b"Error:"):
@@ -1294,9 +1290,7 @@
 del has_eth
 """
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1339,9 +1333,7 @@
             WiznetUtilities.cpyWiznetInit()
         )
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1396,7 +1388,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -1428,7 +1420,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -1465,9 +1457,7 @@
             WiznetUtilities.cpyWiznetInit(),
         )
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return False, err
 
@@ -1621,9 +1611,7 @@
 print(has_bt())
 del has_bt
 """
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -1666,7 +1654,7 @@
 ble_status()
 del ble_status
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1703,7 +1691,7 @@
 activate_ble()
 del activate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1729,7 +1717,7 @@
 deactivate_ble()
 del deactivate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1790,7 +1778,7 @@
 """.format(
             timeout
         )
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 5) * 1000
         )
         if err:
@@ -1845,7 +1833,7 @@
 print(has_ntp())
 del has_ntp
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -1937,7 +1925,7 @@
                 repr(server), tzOffset, timeout
             )
 
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 2) * 1000
         )
         if err:
--- a/src/eric7/MicroPython/Devices/DeviceBase.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/DeviceBase.py	Thu May 04 17:31:13 2023 +0200
@@ -118,7 +118,7 @@
         super().__init__(parent)
 
         self._deviceType = deviceType
-        self._interface = microPythonWidget.deviceInterface()
+        self._interface = None
         self.microPython = microPythonWidget
         self._deviceData = {}  # dictionary with essential device data
 
@@ -137,6 +137,7 @@
         self._deviceData = {}
 
         if connected:
+            self._interface = self.microPython.deviceInterface()
             with contextlib.suppress(OSError):
                 self._deviceData = self.__getDeviceData()
                 self._deviceData["local_mip"] = (
@@ -149,6 +150,8 @@
                     self._deviceData["ethernet_type"],
                 ) = self.hasEthernet()
                 self._deviceData["ntp"] = self.hasNetworkTime()
+        else:
+            self._interface = None
 
     def getDeviceType(self):
         """
@@ -381,6 +384,29 @@
             # user cancelled
             return ""
 
+    def executeCommands(self, commands, *, mode="raw", timeout=0):
+        """
+        Public method to send commands to the connected device and return the
+        result.
+
+        If no connected interface is available, empty results will be returned.
+
+        @param commands list of commands to be executed
+        @type str or list of str
+        @keyparam mode submit mode to be used (one of 'raw' or 'paste') (defaults to
+            'raw')
+        @type str
+        @keyparam timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        """
+        if self._interface is None:
+            return b"", b""
+
+        return self._interface.execute(commands, mode=mode, timeout=timeout)
+
     def sendCommands(self, commandsList):
         """
         Public method to send a list of commands to the device.
@@ -388,25 +414,8 @@
         @param commandsList list of commands to be sent to the device
         @type list of str
         """
-        if self._submitMode == "raw":
-            rawOn = [  # sequence of commands to enter raw mode
-                b"\x02",  # Ctrl-B: exit raw repl (just in case)
-                b"\r\x03\x03\x03",  # Ctrl-C three times: interrupt any running program
-                b"\r\x01",  # Ctrl-A: enter raw REPL
-            ]
-            newLine = [
-                b'print("\\n")\r',
-            ]
-            commands = [c.encode("utf-8)") + b"\r" for c in commandsList]
-            commands.append(b"\r")
-            commands.append(b"\x04")
-            rawOff = [b"\x02", b"\x02"]
-            commandSequence = rawOn + newLine + commands + rawOff
-            self._interface.executeAsync(commandSequence)
-        elif self._submitMode == "paste":
-            commands = b"\n".join([c.encode("utf-8)") for c in commandsList])
-            commandSequence = ["@PasteOn@", commands]
-            self._interface.executeAsyncPaste(commandSequence)
+        if self._interface is not None:
+            self._interface.executeAsync(commandsList, self._submitMode)
 
     @pyqtSlot()
     def handleDataFlood(self):
@@ -543,7 +552,7 @@
 """.format(
             repr(pathname)
         )
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -565,7 +574,7 @@
 """.format(
             dirname
         )
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return ast.literal_eval(out.decode("utf-8"))
@@ -617,7 +626,7 @@
 """.format(
             dirname, showHidden
         )
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         fileslist = ast.literal_eval(out.decode("utf-8"))
@@ -645,7 +654,7 @@
 """.format(
                 dirname
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
 
@@ -662,7 +671,7 @@
 print(__os_.getcwd())
 del __os_
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return out.decode("utf-8").strip()
@@ -687,7 +696,7 @@
 """.format(
                 filename
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
 
@@ -734,7 +743,7 @@
 """.format(
                 name, recursive, force
             )
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=20000
             )
             if err:
@@ -759,7 +768,7 @@
 """.format(
                 dirname
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
 
@@ -786,7 +795,7 @@
 """.format(
                 dirname
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
 
@@ -848,7 +857,7 @@
         )
         command = "\n".join(commands)
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return True
@@ -917,7 +926,7 @@
 """.format(
             deviceFileName
         )
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -962,7 +971,7 @@
 print(fsinfo())
 del __os_, fsinfo
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         infodict = ast.literal_eval(out.decode("utf-8"))
@@ -1084,7 +1093,7 @@
 print(get_device_data())
 del get_device_data
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return ast.literal_eval(out.decode("utf-8"))
@@ -1198,7 +1207,7 @@
 print(get_board_info())
 del get_board_info
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return ast.literal_eval(out.decode("utf-8"))
@@ -1212,7 +1221,7 @@
         @exception OSError raised to indicate an issue with the device
         """
         commands = ["help('modules')"]
-        out, err = self._interface.execute(commands, mode=self._submitMode)
+        out, err = self.executeCommands(commands, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1255,7 +1264,7 @@
 get_time()
 del get_time
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             if b"NotImplementedError" in err:
                 return "&lt;unsupported&gt; &lt;unsupported&gt;"
@@ -1327,7 +1336,7 @@
                     now.tm_isdst,
                 ),
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
 
@@ -1354,7 +1363,7 @@
 """.format(
             repr(packages)
         )
-        return self._interface.execute(command, mode=self._submitMode, timeout=60000)
+        return self.executeCommands(command, mode=self._submitMode, timeout=60000)
 
     def mipInstall(self, package, index=None, target=None, version=None, mpy=True):
         """
@@ -1393,7 +1402,7 @@
 """.format(
             parameterStr
         )
-        return self._interface.execute(command, mode=self._submitMode, timeout=60000)
+        return self.executeCommands(command, mode=self._submitMode, timeout=60000)
 
     def getLibPaths(self):
         """
@@ -1411,7 +1420,7 @@
 lib_paths()
 del lib_paths
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
--- a/src/eric7/MicroPython/Devices/EspDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/EspDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -508,7 +508,7 @@
         Private slot to reset the connected device.
         """
         if self.microPython.isConnected() and not self.hasCircuitPython():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 "import machine\nmachine.reset()\n", mode=self._submitMode
             )
         else:
@@ -688,7 +688,7 @@
 del wifi_status
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -745,7 +745,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -787,7 +787,7 @@
 del disconnect_wifi
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -842,7 +842,7 @@
 del modify_boot
 """
 
-        out, err = self._interface.execute(nvsCommand, mode=self._submitMode)
+        out, err = self.executeCommands(nvsCommand, mode=self._submitMode)
         if err:
             return False, self.tr("Error saving credentials: {0}").format(err)
 
@@ -857,7 +857,7 @@
         except OSError as err:
             return False, self.tr("Error saving auto-connect script: {0}").format(err)
 
-        out, err = self._interface.execute(bootCommand, mode=self._submitMode)
+        out, err = self.executeCommands(bootCommand, mode=self._submitMode)
         if err:
             return False, self.tr("Error modifying 'boot.py': {0}").format(err)
 
@@ -889,7 +889,7 @@
 del delete_wifi_creds
 """
 
-        out, err = self._interface.execute(nvsCommand, mode=self._submitMode)
+        out, err = self.executeCommands(nvsCommand, mode=self._submitMode)
         if err:
             return False, self.tr("Error deleting credentials: {0}").format(err)
 
@@ -926,7 +926,7 @@
 del check_internet
 """
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -960,9 +960,7 @@
 del scan_networks
 """
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return [], err
 
@@ -1017,7 +1015,7 @@
             interface
         )
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
         else:
@@ -1070,9 +1068,7 @@
             repr(ssid), security, repr(password), ifconfig
         )
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return False, err
         else:
@@ -1113,9 +1109,7 @@
 del get_stations
 """
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             return [], err
 
@@ -1152,9 +1146,7 @@
 print(has_bt())
 del has_bt
 """
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -1200,7 +1192,7 @@
 ble_status()
 del ble_status
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1245,7 +1237,7 @@
 activate_ble()
 del activate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1274,7 +1266,7 @@
 deactivate_ble()
 del deactivate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1334,7 +1326,7 @@
 """.format(
             timeout
         )
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 5) * 1000
         )
         if err:
@@ -1379,7 +1371,7 @@
 print(has_ntp())
 del has_ntp
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -1439,7 +1431,7 @@
 """.format(
             repr(server), tzOffset, timeout
         )
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 2) * 1000
         )
         if err:
--- a/src/eric7/MicroPython/Devices/GenericMicroPythonDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/GenericMicroPythonDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -249,7 +249,7 @@
         Private slot to switch the board into 'bootloader' mode.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 "import machine\nmachine.bootloader()\n", mode=self._submitMode
             )
             # simulate pressing the disconnect button
@@ -334,11 +334,12 @@
         """
         Private slot to reset the connected device.
         """
-        self.microPython.deviceInterface().execute(
-            "import machine\nmachine.reset()\n", mode=self._submitMode
-        )
-        # simulate pressing the disconnect button
-        self.microPython.on_connectButton_clicked()
+        if self.microPython.isConnected():
+            self.executeCommands(
+                "import machine\nmachine.reset()\n", mode=self._submitMode
+            )
+            # simulate pressing the disconnect button
+            self.microPython.on_connectButton_clicked()
 
     def getDocumentationUrl(self):
         """
--- a/src/eric7/MicroPython/Devices/MicrobitDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/MicrobitDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -526,16 +526,18 @@
         """
         Private slot to reset the connected device.
         """
-        if self.getDeviceType() == "bbc_microbit":
-            # BBC micro:bit
-            self.microPython.deviceInterface().execute(
-                "import microbit\nmicrobit.reset()\n", mode=self._submitMode
-            )
-        else:
-            # Calliope mini
-            self.microPython.deviceInterface().execute(
-                "import calliope_mini\ncalliope_mini.reset()\n", mode=self._submitMode
-            )
+        if self.microPython.isConnected():
+            if self.getDeviceType() == "bbc_microbit":
+                # BBC micro:bit
+                self.executeCommands(
+                    "import microbit\nmicrobit.reset()\n", mode=self._submitMode
+                )
+            else:
+                # Calliope mini
+                self.executeCommands(
+                    "import calliope_mini\ncalliope_mini.reset()\n",
+                    mode=self._submitMode,
+                )
 
     def getDocumentationUrl(self):
         """
@@ -626,7 +628,7 @@
 print(__os_.listdir())
 del __os_
 """
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
             return ast.literal_eval(out.decode("utf-8"))
@@ -674,7 +676,7 @@
 """.format(
                 showHidden
             )
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 raise OSError(self._shortError(err))
             fileslist = ast.literal_eval(out.decode("utf-8"))
@@ -765,9 +767,7 @@
 print(has_bt())
 del has_bt
 """
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -811,7 +811,7 @@
 ble_status()
 del ble_status
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -848,7 +848,7 @@
 activate_ble()
 del activate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -874,7 +874,7 @@
 deactivate_ble()
 del deactivate_ble
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -935,7 +935,7 @@
 """.format(
             timeout
         )
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 5) * 1000
         )
         if err:
--- a/src/eric7/MicroPython/Devices/PyBoardDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/PyBoardDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -500,7 +500,7 @@
         Private slot to activate the bootloader and disconnect.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 [
                     "import pyb",
                     "pyb.bootloader()",
@@ -515,9 +515,10 @@
         """
         Private slot to reset the connected device.
         """
-        self.microPython.deviceInterface().execute(
-            "import machine\nmachine.reset()\n", mode=self._submitMode
-        )
+        if self.microPython.isConnected():
+            self.executeCommands(
+                "import machine\nmachine.reset()\n", mode=self._submitMode
+            )
 
     ##################################################################
     ## time related methods below
--- a/src/eric7/MicroPython/Devices/RP2040Devices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/RP2040Devices.py	Thu May 04 17:31:13 2023 +0200
@@ -239,7 +239,7 @@
         Private slot to switch the board into 'bootloader' mode.
         """
         if self.microPython.isConnected():
-            self.microPython.deviceInterface().execute(
+            self.executeCommands(
                 [
                     "import machine",
                     "machine.bootloader()",
@@ -338,9 +338,10 @@
         """
         Private slot to reset the connected device.
         """
-        self.microPython.deviceInterface().execute(
-            "import machine\nmachine.reset()\n", mode=self._submitMode
-        )
+        if self.microPython.isConnected():
+            self.executeCommands(
+                "import machine\nmachine.reset()\n", mode=self._submitMode
+            )
 
     def getDocumentationUrl(self):
         """
@@ -463,9 +464,7 @@
 print(has_wifi())
 del has_wifi
 """
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             if not err.startswith(b"Timeout "):
                 raise OSError(self._shortError(err))
@@ -584,7 +583,7 @@
         else:
             return super().getWifiData()
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -698,7 +697,7 @@
             return super().connectWifi(ssid, password)
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -756,7 +755,7 @@
         else:
             return super().disconnectWifi()
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -823,7 +822,7 @@
             return False, str(err)
 
         # modify boot.py
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -888,9 +887,7 @@
         else:
             return super().checkInternet()
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             return False, err
 
@@ -953,9 +950,7 @@
         else:
             return super().scanNetworks()
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return [], err
 
@@ -1031,7 +1026,7 @@
         else:
             return super().deactivateInterface(interface)
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
         else:
@@ -1122,9 +1117,7 @@
         else:
             return super().startAccessPoint(ssid, security=security, password=password)
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=15000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=15000)
         if err:
             return False, err
         else:
@@ -1172,9 +1165,7 @@
         else:
             return super().checkInternet()
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             return [], err
 
@@ -1209,9 +1200,7 @@
 del has_eth
 """
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1250,7 +1239,7 @@
             WiznetUtilities.mpyWiznetInit()
         )
 
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
 
@@ -1307,7 +1296,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -1339,7 +1328,7 @@
         )
 
         with EricOverrideCursor():
-            out, err = self._interface.execute(
+            out, err = self.executeCommands(
                 command, mode=self._submitMode, timeout=15000
             )
         if err:
@@ -1378,9 +1367,7 @@
             WiznetUtilities.mpyWiznetInit(),
         )
 
-        out, err = self._interface.execute(
-            command, mode=self._submitMode, timeout=10000
-        )
+        out, err = self.executeCommands(command, mode=self._submitMode, timeout=10000)
         if err:
             return False, err
 
@@ -1445,7 +1432,7 @@
             return False, str(err)
 
         # modify boot.py
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             return False, err
 
@@ -1490,7 +1477,7 @@
 print(has_ntp())
 del has_ntp
 """
-        out, err = self._interface.execute(command, mode=self._submitMode)
+        out, err = self.executeCommands(command, mode=self._submitMode)
         if err:
             raise OSError(self._shortError(err))
         return out.strip() == b"True"
@@ -1551,7 +1538,7 @@
 """.format(
             repr(server), tzOffset, timeout
         )
-        out, err = self._interface.execute(
+        out, err = self.executeCommands(
             command, mode=self._submitMode, timeout=(timeout + 2) * 1000
         )
         if err:
@@ -1586,7 +1573,7 @@
                 repr(country)
             )
 
-            out, err = self._interface.execute(command, mode=self._submitMode)
+            out, err = self.executeCommands(command, mode=self._submitMode)
             if err:
                 self.microPython.showError("rp2.country()", err)
 
--- a/src/eric7/MicroPython/Devices/STLinkDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/STLinkDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -468,9 +468,10 @@
         """
         Private slot to reset the connected device.
         """
-        self.microPython.deviceInterface().execute(
-            "import machine\nmachine.reset()\n", mode=self._submitMode
-        )
+        if self.microPython.isConnected():
+            self.executeCommands(
+                "import machine\nmachine.reset()\n", mode=self._submitMode
+            )
 
 
 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
--- a/src/eric7/MicroPython/Devices/TeensyDevices.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/Devices/TeensyDevices.py	Thu May 04 17:31:13 2023 +0200
@@ -243,9 +243,10 @@
         """
         Private slot to reset the connected device.
         """
-        self.microPython.deviceInterface().execute(
-            "import machine\nmachine.reset()\n", mode=self._submitMode
-        )
+        if self.microPython.isConnected():
+            self.executeCommands(
+                "import machine\nmachine.reset()\n", mode=self._submitMode
+            )
 
     ##################################################################
     ## time related methods below
--- a/src/eric7/MicroPython/MicroPythonDeviceInterface.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/MicroPythonDeviceInterface.py	Thu May 04 17:31:13 2023 +0200
@@ -1,25 +1,13 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
 """
-Module implementing some file system commands for MicroPython.
+Module  implementing an interface base class to talk to a connected MicroPython device.
 """
 
-from PyQt6.QtCore import (
-    QCoreApplication,
-    QEventLoop,
-    QObject,
-    QThread,
-    QTimer,
-    pyqtSignal,
-    pyqtSlot,
-)
-
-from eric7 import Preferences
-
-from .MicroPythonSerialPort import MicroPythonSerialPort
+from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
 
 
 class MicroPythonDeviceInterface(QObject):
@@ -28,12 +16,14 @@
 
     @signal executeAsyncFinished() emitted to indicate the end of an
         asynchronously executed list of commands (e.g. a script)
-    @signal dataReceived(data) emitted to send data received via the serial
-        connection for further processing
+    @signal dataReceived(data) emitted to send data received via the connection
+        for further processing
+    @signal osdInfo(str) emitted when some OSD data was received from the device
     """
 
     executeAsyncFinished = pyqtSignal()
     dataReceived = pyqtSignal(bytes)
+    osdInfo = pyqtSignal(str)
 
     PasteModePrompt = b"=== "
     TracebackMarker = b"Traceback (most recent call last):"
@@ -47,43 +37,35 @@
         """
         super().__init__(parent)
 
-        self.__repl = parent
-
-        self.__blockReadyRead = False
-
-        self.__serial = MicroPythonSerialPort(
-            timeout=Preferences.getMicroPython("SerialTimeout"), parent=self
-        )
-        self.__serial.readyRead.connect(self.__readSerial)
-
     @pyqtSlot()
-    def __readSerial(self):
+    def connectToDevice(self, connection):
         """
-        Private slot to read all available serial data and emit it with the
-        "dataReceived" signal for further processing.
-        """
-        if not self.__blockReadyRead:
-            data = bytes(self.__serial.readAll())
-            self.dataReceived.emit(data)
+        Public slot to connect to the device.
 
-    @pyqtSlot()
-    def connectToDevice(self, port):
-        """
-        Public slot to start the manager.
-
-        @param port name of the port to be used
+        @param connection name of the connection to be used
         @type str
         @return flag indicating success
         @rtype bool
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         """
-        return self.__serial.openSerialLink(port)
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
+
+        return False
 
     @pyqtSlot()
     def disconnectFromDevice(self):
         """
-        Public slot to stop the thread.
+        Public slot to disconnect from the device.
+
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         """
-        self.__serial.closeSerialLink()
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
 
     def isConnected(self):
         """
@@ -91,15 +73,21 @@
 
         @return flag indicating the connection status
         @rtype bool
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         """
-        return self.__serial.isConnected()
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
+
+        return False
 
     @pyqtSlot()
     def handlePreferencesChanged(self):
         """
         Public slot to handle a change of the preferences.
         """
-        self.__serial.setTimeout(Preferences.getMicroPython("SerialTimeout"))
+        pass
 
     def write(self, data):
         """
@@ -107,149 +95,37 @@
 
         @param data data to be written
         @type bytes or bytearray
-        """
-        self.__serial.isConnected() and self.__serial.write(data)
-
-    def __pasteOn(self):
-        """
-        Private method to switch the connected device to 'paste' mode.
-
-        Note: switching to paste mode is done with synchronous writes.
-
-        @return flag indicating success
-        @rtype bool
-        """
-        if not self.__serial:
-            return False
-
-        pasteMessage = b"paste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "
-
-        self.__serial.clear()  # clear any buffered output before entering paste mode
-        self.__serial.write(b"\x02")  # end raw mode if required
-        written = self.__serial.waitForBytesWritten(500)
-        # time out after 500ms if device is not responding
-        if not written:
-            return False
-        for _i in range(3):
-            # CTRL-C three times to break out of loops
-            self.__serial.write(b"\r\x03")
-            written = self.__serial.waitForBytesWritten(500)
-            # time out after 500ms if device is not responding
-            if not written:
-                return False
-            QThread.msleep(10)
-        self.__serial.readAll()  # read all data and discard it
-        self.__serial.write(b"\r\x05")  # send CTRL-E to enter paste mode
-        self.__serial.readUntil(pasteMessage)
-
-        if self.__serial.hasTimedOut():
-            # it timed out; try it again and than fail
-            self.__serial.write(b"\r\x05")  # send CTRL-E again
-            self.__serial.readUntil(pasteMessage)
-            if self.__serial.hasTimedOut():
-                return False
-
-        QCoreApplication.processEvents(
-            QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
-        )
-        self.__serial.readAll()  # read all data and discard it
-        return True
-
-    def __pasteOff(self):
-        """
-        Private method to switch 'paste' mode off.
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         """
-        if self.__serial:
-            self.__serial.write(b"\x04")  # send CTRL-D to cancel paste mode
-
-    def __rawOn(self):
-        """
-        Private method to switch the connected device to 'raw' mode.
-
-        Note: switching to raw mode is done with synchronous writes.
-
-        @return flag indicating success
-        @rtype bool
-        """
-        if not self.__serial:
-            return False
-
-        rawReplMessage = b"raw REPL; CTRL-B to exit\r\n>"
-
-        self.__serial.write(b"\x02")  # end raw mode if required
-        written = self.__serial.waitForBytesWritten(500)
-        # time out after 500ms if device is not responding
-        if not written:
-            return False
-        for _i in range(3):
-            # CTRL-C three times to break out of loops
-            self.__serial.write(b"\r\x03")
-            written = self.__serial.waitForBytesWritten(500)
-            # time out after 500ms if device is not responding
-            if not written:
-                return False
-            QThread.msleep(10)
-        self.__serial.readAll()  # read all data and discard it
-        self.__serial.write(b"\r\x01")  # send CTRL-A to enter raw mode
-        self.__serial.readUntil(rawReplMessage)
-        if self.__serial.hasTimedOut():
-            # it timed out; try it again and than fail
-            self.__serial.write(b"\r\x01")  # send CTRL-A again
-            self.__serial.readUntil(rawReplMessage)
-            if self.__serial.hasTimedOut():
-                return False
-
-        QCoreApplication.processEvents(
-            QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
         )
-        self.__serial.readAll()  # read all data and discard it
-        return True
-
-    def __rawOff(self):
-        """
-        Private method to switch 'raw' mode off.
-        """
-        if self.__serial:
-            self.__serial.write(b"\x02")  # send CTRL-B to cancel raw mode
-            self.__serial.readUntil(b">>> ")  # read until Python prompt
-            self.__serial.readAll()  # read all data and discard it
 
     def probeDevice(self):
         """
         Public method to check the device is responding.
 
-        If the device has not been flashed with a MicroPython formware, the
+        If the device has not been flashed with a MicroPython firmware, the
         probe will fail.
 
         @return flag indicating a communicating MicroPython device
         @rtype bool
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         """
-        if not self.__serial:
-            return False
-
-        if not self.__serial.isConnected():
-            return False
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
 
-        # switch on raw mode
-        self.__blockReadyRead = True
-        ok = self.__pasteOn()
-        if not ok:
-            self.__blockReadyRead = False
-            return False
-
-        # switch off raw mode
-        QThread.msleep(10)
-        self.__pasteOff()
-        self.__blockReadyRead = False
-
-        return True
+        return False
 
     def execute(self, commands, *, mode="raw", timeout=0):
         """
         Public method to send commands to the connected device and return the
         result.
 
-        If no serial connection is available, empty results will be returned.
+        If no connection is available, empty results will be returned.
 
         @param commands list of commands to be executed
         @type str or list of str
@@ -261,205 +137,38 @@
         @type int (optional)
         @return tuple containing stdout and stderr output of the device
         @rtype tuple of (bytes, bytes)
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
         @exception ValueError raised in case of an unsupported submit mode
         """
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
+
         if mode not in ("paste", "raw"):
             raise ValueError("Unsupported submit mode given ('{0}').".format(mode))
 
-        if mode == "raw":
-            return self.__execute_raw(commands, timeout=timeout)
-        elif mode == "paste":
-            return self.__execute_paste(commands, timeout=timeout)
-        else:
-            # just in case
-            return b"", b""
-
-    def __execute_raw(self, commands, timeout=0):
-        """
-        Private method to send commands to the connected device using 'raw REPL' mode
-        and return the result.
-
-        If no serial connection is available, empty results will be returned.
-
-        @param commands list of commands to be executed
-        @type str or list of str
-        @param timeout per command timeout in milliseconds (0 for configured default)
-            (defaults to 0)
-        @type int (optional)
-        @return tuple containing stdout and stderr output of the device
-        @rtype tuple of (bytes, bytes)
-        """
-        if not self.__serial:
-            return b"", b""
-
-        if not self.__serial.isConnected():
-            return b"", b"Device not connected or not switched on."
-
-        result = bytearray()
-        err = b""
-
-        if isinstance(commands, str):
-            commands = [commands]
-
-        # switch on raw mode
-        self.__blockReadyRead = True
-        ok = self.__rawOn()
-        if not ok:
-            self.__blockReadyRead = False
-            return (b"", b"Could not switch to raw mode. Is the device switched on?")
-
-        # send commands
-        QThread.msleep(10)
-        for command in commands:
-            if command:
-                commandBytes = command.encode("utf-8")
-                self.__serial.write(commandBytes + b"\x04")
-                QCoreApplication.processEvents(
-                    QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
-                )
-                ok = self.__serial.readUntil(b"OK")
-                if ok != b"OK":
-                    self.__blockReadyRead = False
-                    return (
-                        b"",
-                        "Expected 'OK', got '{0}', followed by '{1}'".format(
-                            ok, self.__serial.readAll()
-                        ).encode("utf-8"),
-                    )
-
-                # read until prompt
-                response = self.__serial.readUntil(b"\x04>", timeout=timeout)
-                if self.__serial.hasTimedOut():
-                    self.__blockReadyRead = False
-                    return b"", b"Timeout while processing commands."
-                if b"\x04" in response[:-2]:
-                    # split stdout, stderr
-                    out, err = response[:-2].split(b"\x04")
-                    result += out
-                else:
-                    err = b"invalid response received: " + response
-                if err:
-                    result = b""
-                    break
-
-        # switch off raw mode
-        QThread.msleep(10)
-        self.__rawOff()
-        self.__blockReadyRead = False
+        return b"", b""
 
-        return bytes(result), err
-
-    def __execute_paste(self, commands, timeout=0):
-        """
-        Private method to send commands to the connected device using 'paste' mode
-        and return the result.
-
-        If no serial connection is available, empty results will be returned.
-
-        @param commands list of commands to be executed
-        @type str or list of str
-        @param timeout per command timeout in milliseconds (0 for configured default)
-            (defaults to 0)
-        @type int (optional)
-        @return tuple containing stdout and stderr output of the device
-        @rtype tuple of (bytes, bytes)
-        """
-        if not self.__serial:
-            return b"", b""
-
-        if not self.__serial.isConnected():
-            return b"", b"Device not connected or not switched on."
-
-        if isinstance(commands, list):
-            commands = "\n".join(commands)
-
-        # switch on paste mode
-        self.__blockReadyRead = True
-        ok = self.__pasteOn()
-        if not ok:
-            self.__blockReadyRead = False
-            return (b"", b"Could not switch to paste mode. Is the device switched on?")
-
-        # send commands
-        QThread.msleep(10)
-        for command in commands.splitlines(keepends=True):
-            # send the data as single lines
-            commandBytes = command.encode("utf-8")
-            self.__serial.write(commandBytes)
-            QCoreApplication.processEvents(
-                QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
-            )
-            QThread.msleep(10)
-            ok = self.__serial.readUntil(commandBytes)
-            if ok != commandBytes:
-                self.__blockReadyRead = False
-                return (
-                    b"",
-                    "Expected '{0}', got '{1}', followed by '{2}'".format(
-                        commandBytes, ok, self.__serial.readAll()
-                    ).encode("utf-8"),
-                )
-
-        # switch off paste mode causing the commands to be executed
-        self.__pasteOff()
-        QThread.msleep(10)
-        # read until Python prompt
-        result = (
-            self.__serial.readUntil(b">>> ", timeout=timeout)
-            .replace(b">>> ", b"")
-            .strip()
-        )
-        if self.__serial.hasTimedOut():
-            self.__blockReadyRead = False
-            return b"", b"Timeout while processing commands."
-
-        # get rid of any OSD string
-        if result.startswith(b"\x1b]0;"):
-            result = result.split(b"\x1b\\")[-1]
-
-        if self.TracebackMarker in result:
-            errorIndex = result.find(self.TracebackMarker)
-            out, err = result[:errorIndex], result[errorIndex:]
-        else:
-            out = result
-            err = b""
-
-        self.__blockReadyRead = False
-        return out, err
-
-    def executeAsync(self, commandsList):
+    def executeAsync(self, commandsList, submitMode):
         """
         Public method to execute a series of commands over a period of time
         without returning any result (asynchronous execution).
 
         @param commandsList list of commands to be execute on the device
-        @type list of bytes
+        @type list of str
+        @param submitMode mode to be used to submit the commands (one of 'raw'
+            or 'paste')
+        @type str
+        @exception NotImplementedError raised to indicate that this method needs to
+            be implemented in a derived class
+        @exception ValueError raised to indicate an unknown submit mode
         """
-        if commandsList:
-            command = commandsList.pop(0)
-            self.__serial.write(command)
-            QTimer.singleShot(2, lambda: self.executeAsync(commandsList))
-        else:
-            self.executeAsyncFinished.emit()
-
-    def executeAsyncPaste(self, commandsList):
-        """
-        Public method to execute a series of commands over a period of time
-        without returning any result (asynchronous execution).
+        raise NotImplementedError(
+            "This method needs to be implemented in a derived class."
+        )
 
-        @param commandsList list of commands to be execute on the device
-        @type list of bytes
-        """
-        if commandsList:
-            self.__blockReadyRead = True
-            command = commandsList.pop(0)
-            if command == "@PasteOn@":
-                self.__pasteOn()
-            else:
-                self.__serial.write(command)
-                self.__serial.readUntil(command)
-            QTimer.singleShot(2, lambda: self.executeAsyncPaste(commandsList))
-        else:
-            self.__blockReadyRead = False
-            self.__pasteOff()
-            self.executeAsyncFinished.emit()
+        if submitMode not in ("raw", "paste"):
+            raise ValueError(
+                "Unsupported submit mode given ('{0}').".format(submitMode)
+            )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonReplWidget.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,703 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the MicroPython REPL widget.
+"""
+
+import re
+
+from PyQt6.QtCore import QPoint, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtGui import (
+    QBrush,
+    QClipboard,
+    QColor,
+    QGuiApplication,
+    QKeySequence,
+    QTextCursor,
+)
+from PyQt6.QtWidgets import (
+    QHBoxLayout,
+    QLabel,
+    QMenu,
+    QSizePolicy,
+    QTextEdit,
+    QVBoxLayout,
+    QWidget,
+)
+
+from eric7 import Preferences
+from eric7.EricGui import EricPixmapCache
+from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
+from eric7.SystemUtilities import OSUtilities
+
+AnsiColorSchemes = {
+    "Windows 7": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(128, 0, 0)),
+        2: QBrush(QColor(0, 128, 0)),
+        3: QBrush(QColor(128, 128, 0)),
+        4: QBrush(QColor(0, 0, 128)),
+        5: QBrush(QColor(128, 0, 128)),
+        6: QBrush(QColor(0, 128, 128)),
+        7: QBrush(QColor(192, 192, 192)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Windows 10": {
+        0: QBrush(QColor(12, 12, 12)),
+        1: QBrush(QColor(197, 15, 31)),
+        2: QBrush(QColor(19, 161, 14)),
+        3: QBrush(QColor(193, 156, 0)),
+        4: QBrush(QColor(0, 55, 218)),
+        5: QBrush(QColor(136, 23, 152)),
+        6: QBrush(QColor(58, 150, 221)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(118, 118, 118)),
+        11: QBrush(QColor(231, 72, 86)),
+        12: QBrush(QColor(22, 198, 12)),
+        13: QBrush(QColor(249, 241, 165)),
+        14: QBrush(QColor(59, 12, 255)),
+        15: QBrush(QColor(180, 0, 158)),
+        16: QBrush(QColor(97, 214, 214)),
+        17: QBrush(QColor(242, 242, 242)),
+    },
+    "PuTTY": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(187, 0, 0)),
+        2: QBrush(QColor(0, 187, 0)),
+        3: QBrush(QColor(187, 187, 0)),
+        4: QBrush(QColor(0, 0, 187)),
+        5: QBrush(QColor(187, 0, 187)),
+        6: QBrush(QColor(0, 187, 187)),
+        7: QBrush(QColor(187, 187, 187)),
+        10: QBrush(QColor(85, 85, 85)),
+        11: QBrush(QColor(255, 85, 85)),
+        12: QBrush(QColor(85, 255, 85)),
+        13: QBrush(QColor(255, 255, 85)),
+        14: QBrush(QColor(85, 85, 255)),
+        15: QBrush(QColor(255, 85, 255)),
+        16: QBrush(QColor(85, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "xterm": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(205, 0, 0)),
+        2: QBrush(QColor(0, 205, 0)),
+        3: QBrush(QColor(205, 205, 0)),
+        4: QBrush(QColor(0, 0, 238)),
+        5: QBrush(QColor(205, 0, 205)),
+        6: QBrush(QColor(0, 205, 205)),
+        7: QBrush(QColor(229, 229, 229)),
+        10: QBrush(QColor(127, 127, 127)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Ubuntu": {
+        0: QBrush(QColor(1, 1, 1)),
+        1: QBrush(QColor(222, 56, 43)),
+        2: QBrush(QColor(57, 181, 74)),
+        3: QBrush(QColor(255, 199, 6)),
+        4: QBrush(QColor(0, 11, 184)),
+        5: QBrush(QColor(118, 38, 113)),
+        6: QBrush(QColor(44, 181, 233)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Ubuntu (dark)": {
+        0: QBrush(QColor(96, 96, 96)),
+        1: QBrush(QColor(235, 58, 45)),
+        2: QBrush(QColor(57, 181, 74)),
+        3: QBrush(QColor(255, 199, 29)),
+        4: QBrush(QColor(25, 56, 230)),
+        5: QBrush(QColor(200, 64, 193)),
+        6: QBrush(QColor(48, 200, 255)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Breeze (dark)": {
+        0: QBrush(QColor(35, 38, 39)),
+        1: QBrush(QColor(237, 21, 21)),
+        2: QBrush(QColor(17, 209, 22)),
+        3: QBrush(QColor(246, 116, 0)),
+        4: QBrush(QColor(29, 153, 243)),
+        5: QBrush(QColor(155, 89, 182)),
+        6: QBrush(QColor(26, 188, 156)),
+        7: QBrush(QColor(252, 252, 252)),
+        10: QBrush(QColor(127, 140, 141)),
+        11: QBrush(QColor(192, 57, 43)),
+        12: QBrush(QColor(28, 220, 154)),
+        13: QBrush(QColor(253, 188, 75)),
+        14: QBrush(QColor(61, 174, 233)),
+        15: QBrush(QColor(142, 68, 173)),
+        16: QBrush(QColor(22, 160, 133)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+}
+
+
+class MicroPythonReplWidget(QWidget):
+    """
+    Class implementing the MicroPython REPL widget.
+    """
+
+    ZoomMin = -10
+    ZoomMax = 20
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent=parent)
+
+        self.__layout = QVBoxLayout(self)
+        self.__layout.setContentsMargins(0, 0, 0, 0)
+
+        self.__zoomLayout = QHBoxLayout()
+        self.__osdLabel = QLabel()
+        self.__osdLabel.setSizePolicy(
+            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
+        )
+        self.__zoomLayout.addWidget(self.__osdLabel)
+
+        self.__zoomWidget = EricZoomWidget(
+            EricPixmapCache.getPixmap("zoomOut"),
+            EricPixmapCache.getPixmap("zoomIn"),
+            EricPixmapCache.getPixmap("zoomReset"),
+            self,
+        )
+        self.__zoomWidget.setMinimum(self.ZoomMin)
+        self.__zoomWidget.setMaximum(self.ZoomMax)
+        self.__zoomLayout.addWidget(self.__zoomWidget)
+        self.__layout.addLayout(self.__zoomLayout)
+
+        self.__replEdit = MicroPythonReplEdit(self)
+        self.__layout.addWidget(self.__replEdit)
+
+        self.setLayout(self.__layout)
+
+        self.__zoomWidget.valueChanged.connect(self.__replEdit.doZoom)
+        self.__replEdit.osdInfo.connect(self.setOSDInfo)
+
+    @pyqtSlot(str)
+    def setOSDInfo(self, infoStr):
+        """
+        Public slot to set the OSD information.
+
+        @param infoStr string to be shown
+        @type str
+        """
+        self.__osdLabel.setText(infoStr)
+
+    @pyqtSlot()
+    def clearOSD(self):
+        """
+        Public slot to clear the OSD info.
+        """
+        self.__osdLabel.clear()
+
+    def replEdit(self):
+        """
+        Public method to get a reference to the REPL edit.
+
+        @return reference to the REPL edit
+        @rtype MicroPythonReplEdit
+        """
+        return self.__replEdit
+
+
+class MicroPythonReplEdit(QTextEdit):
+    """
+    Class implementing the REPL edit pane.
+
+    @signal osdInfo(str) emitted when some OSD data was received from the device
+    """
+
+    osdInfo = pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent=parent)
+
+        self.setAcceptRichText(False)
+        self.setUndoRedoEnabled(False)
+        self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
+        self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
+
+        self.__currentZoom = 0
+
+        self.__replBuffer = b""
+
+        self.__vt100Re = re.compile(
+            r"(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])"
+        )
+
+        self.customContextMenuRequested.connect(self.__showContextMenu)
+
+        charFormat = self.currentCharFormat()
+        self.DefaultForeground = charFormat.foreground()
+        self.DefaultBackground = charFormat.background()
+
+        self.__interface = None
+
+    def setInterface(self, deviceInterface):
+        """
+        Public method to set the reference to the device interface object.
+
+        @param deviceInterface reference to the device interface object
+        @type MicroPythonDeviceInterface
+        """
+        self.__interface = deviceInterface
+
+    @pyqtSlot(int)
+    def doZoom(self, value):
+        """
+        Public slot to zoom in or out.
+
+        @param value zoom value
+        @type int
+        """
+        if value < self.__currentZoom:
+            self.zoomOut(self.__currentZoom - value)
+        elif value > self.__currentZoom:
+            self.zoomIn(value - self.__currentZoom)
+        self.__currentZoom = value
+
+    @pyqtSlot(QPoint)
+    def __showContextMenu(self, pos):
+        """
+        Private slot to show the REPL context menu.
+
+        @param pos position to show the menu at
+        @type QPoint
+        """
+        connected = bool(self.__interface) and self.__interface.isConnected()
+
+        if OSUtilities.isMacPlatform():
+            copyKeys = QKeySequence("Ctrl+C")
+            pasteKeys = QKeySequence("Ctrl+V")
+            selectAllKeys = QKeySequence("Ctrl+A")
+        else:
+            copyKeys = QKeySequence("Ctrl+Shift+C")
+            pasteKeys = QKeySequence("Ctrl+Shift+V")
+            selectAllKeys = QKeySequence("Ctrl+Shift+A")
+
+        menu = QMenu(self)
+        menu.addAction(
+            EricPixmapCache.getIcon("editDelete"), self.tr("Clear"), self.__clear
+        ).setEnabled(bool(self.toPlainText()))
+        menu.addSeparator()
+        menu.addAction(
+            EricPixmapCache.getIcon("editCopy"),
+            self.tr("Copy"),
+            copyKeys,
+            self.copy,
+        ).setEnabled(self.textCursor().hasSelection())
+        menu.addAction(
+            EricPixmapCache.getIcon("editPaste"),
+            self.tr("Paste"),
+            pasteKeys,
+            self.__paste,
+        ).setEnabled(self.canPaste() and connected)
+        menu.addSeparator()
+        menu.addAction(
+            EricPixmapCache.getIcon("editSelectAll"),
+            self.tr("Select All"),
+            selectAllKeys,
+            self.selectAll,
+        ).setEnabled(bool(self.toPlainText()))
+
+        menu.exec(self.mapToGlobal(pos))
+
+    @pyqtSlot()
+    def handlePreferencesChanged(self):
+        """
+        Public slot to handle a change in preferences.
+        """
+        self.__colorScheme = Preferences.getMicroPython("ColorScheme")
+
+        self.__font = Preferences.getEditorOtherFonts("MonospacedFont")
+        self.setFontFamily(self.__font.family())
+        self.setFontPointSize(self.__font.pointSize())
+
+        if Preferences.getMicroPython("ReplLineWrap"):
+            self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
+        else:
+            self.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
+
+    @pyqtSlot()
+    def __clear(self):
+        """
+        Private slot to clear the REPL pane.
+        """
+        self.clear()
+        if bool(self.__interface) and self.__interface.isConnected():
+            self.__interface.write(b"\r")
+
+    @pyqtSlot()
+    def __paste(self, mode=QClipboard.Mode.Clipboard):
+        """
+        Private slot to perform a paste operation.
+
+        @param mode paste mode (defaults to QClipboard.Mode.Clipboard)
+        @type QClipboard.Mode (optional)
+        """
+        # add support for paste by mouse middle button
+        clipboard = QGuiApplication.clipboard()
+        if clipboard:
+            pasteText = clipboard.text(mode=mode)
+            if pasteText:
+                pasteText = pasteText.replace("\n\r", "\r")
+                pasteText = pasteText.replace("\n", "\r")
+                if bool(self.__interface) and self.__interface.isConnected():
+                    self.__interface.write(b"\x05")
+                    self.__interface.write(pasteText.encode("utf-8"))
+                    self.__interface.write(b"\x04")
+
+    def keyPressEvent(self, evt):
+        """
+        Protected method to handle key press events.
+
+        @param evt reference to the key press event
+        @type QKeyEvent
+        """
+        key = evt.key()
+        msg = bytes(evt.text(), "utf8")
+        if key == Qt.Key.Key_Backspace:
+            msg = b"\b"
+        elif key == Qt.Key.Key_Delete:
+            msg = b"\x1B[\x33\x7E"
+        elif key == Qt.Key.Key_Up:
+            msg = b"\x1B[A"
+        elif key == Qt.Key.Key_Down:
+            msg = b"\x1B[B"
+        elif key == Qt.Key.Key_Right:
+            msg = b"\x1B[C"
+        elif key == Qt.Key.Key_Left:
+            msg = b"\x1B[D"
+        elif key == Qt.Key.Key_Home:
+            msg = b"\x1B[H"
+        elif key == Qt.Key.Key_End:
+            msg = b"\x1B[F"
+        elif (
+            OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.MetaModifier
+        ) or (
+            not OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
+        ):
+            if Qt.Key.Key_A <= key <= Qt.Key.Key_Z:
+                # devices treat an input of \x01 as Ctrl+A, etc.
+                msg = bytes([1 + key - Qt.Key.Key_A])
+        elif evt.modifiers() == (
+            Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
+        ) or (
+            OSUtilities.isMacPlatform()
+            and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
+        ):
+            if key == Qt.Key.Key_C:
+                self.copy()
+                msg = b""
+            elif key == Qt.Key.Key_V:
+                self.__paste()
+                msg = b""
+            elif key == Qt.Key.Key_A:
+                self.selectAll()
+                msg = b""
+        elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
+            tc = self.textCursor()
+            tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
+            self.setTextCursor(tc)
+        if bool(self.__interface) and self.__interface.isConnected():
+            self.__interface.write(msg)
+
+        evt.accept()
+
+    def mouseReleaseEvent(self, evt):
+        """
+        Protected method to handle mouse release events.
+
+        @param evt reference to the event object
+        @type QMouseEvent
+        """
+        if evt.button() == Qt.MouseButton.MiddleButton:
+            self.__paste(mode=QClipboard.Mode.Selection)
+            msg = b""
+            if bool(self.__interface) and self.__interface.isConnected():
+                self.__interface.write(msg)
+            evt.accept()
+        else:
+            super().mouseReleaseEvent(evt)
+
+    @pyqtSlot(bytes)
+    def processData(self, data):
+        """
+        Public slot to process the data received from the device.
+
+        @param data data received from the device
+        @type bytes
+        """
+        tc = self.textCursor()
+        # the text cursor must be on the last line
+        while tc.movePosition(QTextCursor.MoveOperation.Down):
+            pass
+
+        # reset the font
+        charFormat = tc.charFormat()
+        self.__setCharFormat(None, tc)
+        tc.setCharFormat(charFormat)
+
+        # add received data to the buffered one
+        data = self.__replBuffer + data
+
+        index = 0
+        while index < len(data):
+            if data[index] == 8:  # \b
+                tc.movePosition(QTextCursor.MoveOperation.Left)
+                self.setTextCursor(tc)
+            elif data[index] in (4, 13):  # EOT, \r
+                pass
+            elif len(data) > index + 1 and data[index] == 27 and data[index + 1] == 91:
+                # VT100 cursor command detected: <Esc>[
+                index += 2  # move index to after the [
+                match = self.__vt100Re.search(
+                    data[index:].decode("utf-8", errors="replace")
+                )
+                if match:
+                    # move to last position in control sequence
+                    # ++ will be done at end of loop
+                    index += match.end() - 1
+
+                    action = match.group("action")
+                    if action in "ABCD":
+                        if match.group("count") == "":
+                            count = 1
+                        else:
+                            count = int(match.group("count"))
+
+                        if action == "A":  # up
+                            tc.movePosition(QTextCursor.MoveOperation.Up, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "B":  # down
+                            tc.movePosition(QTextCursor.MoveOperation.Down, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "C":  # right
+                            tc.movePosition(QTextCursor.MoveOperation.Right, n=count)
+                            self.setTextCursor(tc)
+                        elif action == "D":  # left
+                            tc.movePosition(QTextCursor.MoveOperation.Left, n=count)
+                            self.setTextCursor(tc)
+                    elif action == "K":  # delete things
+                        if match.group("count") in ("", "0"):
+                            # delete to end of line
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.EndOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                        elif match.group("count") == "1":
+                            # delete to beginning of line
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.StartOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                        elif match.group("count") == "2":
+                            # delete whole line
+                            tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
+                            tc.movePosition(
+                                QTextCursor.MoveOperation.StartOfLine,
+                                mode=QTextCursor.MoveMode.KeepAnchor,
+                            )
+                            tc.removeSelectedText()
+                            self.setTextCursor(tc)
+                    elif action == "m":
+                        self.__setCharFormat(match.group(0)[:-1].split(";"), tc)
+            elif (
+                len(data) > index + 1
+                and data[index] == 27
+                and data[index + 1 : index + 4] == b"]0;"
+            ):
+                if b"\x1b\\" in data[index + 4 :]:
+                    # 'set window title' command detected: <Esc>]0;...<Esc>\
+                    # __IGNORE_WARNING_M891__
+                    titleData = data[index + 4 :].split(b"\x1b\\")[0]
+                    title = titleData.decode("utf-8")
+                    index += len(titleData) + 5  # one more is done at the end
+                    self.osdInfo.emit(title)
+                else:
+                    # data is incomplete; buffer and stop processing
+                    self.__replBuffer = data[index:]
+                    return
+            else:
+                tc.deleteChar()
+                self.setTextCursor(tc)
+                self.insertPlainText(chr(data[index]))
+
+            index += 1
+
+        self.ensureCursorVisible()
+        self.__replBuffer = b""
+
+    def __setCharFormat(self, formatCodes, textCursor):
+        """
+        Private method setting the current text format of the REPL pane based
+        on the passed ANSI codes.
+
+        Following codes are used:
+        <ul>
+        <li>0: Reset</li>
+        <li>1: Bold font (weight 75)</li>
+        <li>2: Light font (weight 25)</li>
+        <li>3: Italic font</li>
+        <li>4: Underlined font</li>
+        <li>9: Strikeout font</li>
+        <li>21: Bold off (weight 50)</li>
+        <li>22: Light off (weight 50)</li>
+        <li>23: Italic off</li>
+        <li>24: Underline off</li>
+        <li>29: Strikeout off</li>
+        <li>30: foreground Black</li>
+        <li>31: foreground Dark Red</li>
+        <li>32: foreground Dark Green</li>
+        <li>33: foreground Dark Yellow</li>
+        <li>34: foreground Dark Blue</li>
+        <li>35: foreground Dark Magenta</li>
+        <li>36: foreground Dark Cyan</li>
+        <li>37: foreground Light Gray</li>
+        <li>39: reset foreground to default</li>
+        <li>40: background Black</li>
+        <li>41: background Dark Red</li>
+        <li>42: background Dark Green</li>
+        <li>43: background Dark Yellow</li>
+        <li>44: background Dark Blue</li>
+        <li>45: background Dark Magenta</li>
+        <li>46: background Dark Cyan</li>
+        <li>47: background Light Gray</li>
+        <li>49: reset background to default</li>
+        <li>53: Overlined font</li>
+        <li>55: Overline off</li>
+        <li>90: bright foreground Dark Gray</li>
+        <li>91: bright foreground Red</li>
+        <li>92: bright foreground Green</li>
+        <li>93: bright foreground Yellow</li>
+        <li>94: bright foreground Blue</li>
+        <li>95: bright foreground Magenta</li>
+        <li>96: bright foreground Cyan</li>
+        <li>97: bright foreground White</li>
+        <li>100: bright background Dark Gray</li>
+        <li>101: bright background Red</li>
+        <li>102: bright background Green</li>
+        <li>103: bright background Yellow</li>
+        <li>104: bright background Blue</li>
+        <li>105: bright background Magenta</li>
+        <li>106: bright background Cyan</li>
+        <li>107: bright background White</li>
+        </ul>
+
+        @param formatCodes list of format codes
+        @type list of str
+        @param textCursor reference to the text cursor
+        @type QTextCursor
+        """
+        if not formatCodes:
+            # empty format codes list is treated as a reset
+            formatCodes = ["0"]
+
+        charFormat = textCursor.charFormat()
+        for formatCode in formatCodes:
+            try:
+                formatCode = int(formatCode)
+            except ValueError:
+                # ignore non digit values
+                continue
+
+            if formatCode == 0:
+                charFormat.setFontWeight(50)
+                charFormat.setFontItalic(False)
+                charFormat.setFontUnderline(False)
+                charFormat.setFontStrikeOut(False)
+                charFormat.setFontOverline(False)
+                charFormat.setForeground(self.DefaultForeground)
+                charFormat.setBackground(self.DefaultBackground)
+            elif formatCode == 1:
+                charFormat.setFontWeight(75)
+            elif formatCode == 2:
+                charFormat.setFontWeight(25)
+            elif formatCode == 3:
+                charFormat.setFontItalic(True)
+            elif formatCode == 4:
+                charFormat.setFontUnderline(True)
+            elif formatCode == 9:
+                charFormat.setFontStrikeOut(True)
+            elif formatCode in (21, 22):
+                charFormat.setFontWeight(50)
+            elif formatCode == 23:
+                charFormat.setFontItalic(False)
+            elif formatCode == 24:
+                charFormat.setFontUnderline(False)
+            elif formatCode == 29:
+                charFormat.setFontStrikeOut(False)
+            elif formatCode == 53:
+                charFormat.setFontOverline(True)
+            elif formatCode == 55:
+                charFormat.setFontOverline(False)
+            elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 30]
+                )
+            elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 40]
+                )
+            elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 80]
+                )
+            elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 90]
+                )
+            elif formatCode == 39:
+                charFormat.setForeground(self.DefaultForeground)
+            elif formatCode == 49:
+                charFormat.setBackground(self.DefaultBackground)
+
+        textCursor.setCharFormat(charFormat)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonSerialDeviceInterface.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,477 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module  implementing an interface to talk to a connected MicroPython device via
+a serial link.
+"""
+
+from PyQt6.QtCore import QCoreApplication, QEventLoop, QThread, QTimer, pyqtSlot
+
+from eric7 import Preferences
+
+from .MicroPythonDeviceInterface import MicroPythonDeviceInterface
+from .MicroPythonSerialPort import MicroPythonSerialPort
+
+
+class MicroPythonSerialDeviceInterface(MicroPythonDeviceInterface):
+    """
+    Class implementing an interface to talk to a connected MicroPython device via
+    a serial link.
+    """
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super().__init__(parent)
+
+        self.__blockReadyRead = False
+
+        self.__serial = MicroPythonSerialPort(
+            timeout=Preferences.getMicroPython("SerialTimeout"), parent=self
+        )
+        self.__serial.readyRead.connect(self.__readSerial)
+
+    @pyqtSlot()
+    def __readSerial(self):
+        """
+        Private slot to read all available serial data and emit it with the
+        "dataReceived" signal for further processing.
+        """
+        if not self.__blockReadyRead:
+            data = bytes(self.__serial.readAll())
+            self.dataReceived.emit(data)
+
+    @pyqtSlot()
+    def connectToDevice(self, connection):
+        """
+        Public slot to connect to the device.
+
+        @param connection name of the connection to be used
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        return self.__serial.openSerialLink(connection)
+
+    @pyqtSlot()
+    def disconnectFromDevice(self):
+        """
+        Public slot to disconnect from the device.
+        """
+        self.__serial.closeSerialLink()
+
+    def isConnected(self):
+        """
+        Public method to get the connection status.
+
+        @return flag indicating the connection status
+        @rtype bool
+        """
+        return self.__serial.isConnected()
+
+    @pyqtSlot()
+    def handlePreferencesChanged(self):
+        """
+        Public slot to handle a change of the preferences.
+        """
+        self.__serial.setTimeout(Preferences.getMicroPython("SerialTimeout"))
+
+    def write(self, data):
+        """
+        Public method to write data to the connected device.
+
+        @param data data to be written
+        @type bytes or bytearray
+        """
+        self.__serial.isConnected() and self.__serial.write(data)
+
+    def __pasteOn(self):
+        """
+        Private method to switch the connected device to 'paste' mode.
+
+        Note: switching to paste mode is done with synchronous writes.
+
+        @return flag indicating success
+        @rtype bool
+        """
+        if not self.__serial:
+            return False
+
+        pasteMessage = b"paste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "
+
+        self.__serial.clear()  # clear any buffered output before entering paste mode
+        self.__serial.write(b"\x02")  # end raw mode if required
+        written = self.__serial.waitForBytesWritten(500)
+        # time out after 500ms if device is not responding
+        if not written:
+            return False
+        for _i in range(3):
+            # CTRL-C three times to break out of loops
+            self.__serial.write(b"\r\x03")
+            written = self.__serial.waitForBytesWritten(500)
+            # time out after 500ms if device is not responding
+            if not written:
+                return False
+            QThread.msleep(10)
+        self.__serial.readAll()  # read all data and discard it
+        self.__serial.write(b"\r\x05")  # send CTRL-E to enter paste mode
+        self.__serial.readUntil(pasteMessage)
+
+        if self.__serial.hasTimedOut():
+            # it timed out; try it again and than fail
+            self.__serial.write(b"\r\x05")  # send CTRL-E again
+            self.__serial.readUntil(pasteMessage)
+            if self.__serial.hasTimedOut():
+                return False
+
+        QCoreApplication.processEvents(
+            QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+        )
+        self.__serial.readAll()  # read all data and discard it
+        return True
+
+    def __pasteOff(self):
+        """
+        Private method to switch 'paste' mode off.
+        """
+        if self.__serial:
+            self.__serial.write(b"\x04")  # send CTRL-D to cancel paste mode
+
+    def __rawOn(self):
+        """
+        Private method to switch the connected device to 'raw' mode.
+
+        Note: switching to raw mode is done with synchronous writes.
+
+        @return flag indicating success
+        @rtype bool
+        """
+        if not self.__serial:
+            return False
+
+        rawReplMessage = b"raw REPL; CTRL-B to exit\r\n>"
+
+        self.__serial.write(b"\x02")  # end raw mode if required
+        written = self.__serial.waitForBytesWritten(500)
+        # time out after 500ms if device is not responding
+        if not written:
+            return False
+        for _i in range(3):
+            # CTRL-C three times to break out of loops
+            self.__serial.write(b"\r\x03")
+            written = self.__serial.waitForBytesWritten(500)
+            # time out after 500ms if device is not responding
+            if not written:
+                return False
+            QThread.msleep(10)
+        self.__serial.readAll()  # read all data and discard it
+        self.__serial.write(b"\r\x01")  # send CTRL-A to enter raw mode
+        self.__serial.readUntil(rawReplMessage)
+        if self.__serial.hasTimedOut():
+            # it timed out; try it again and than fail
+            self.__serial.write(b"\r\x01")  # send CTRL-A again
+            self.__serial.readUntil(rawReplMessage)
+            if self.__serial.hasTimedOut():
+                return False
+
+        QCoreApplication.processEvents(
+            QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+        )
+        self.__serial.readAll()  # read all data and discard it
+        return True
+
+    def __rawOff(self):
+        """
+        Private method to switch 'raw' mode off.
+        """
+        if self.__serial:
+            self.__serial.write(b"\x02")  # send CTRL-B to cancel raw mode
+            self.__serial.readUntil(b">>> ")  # read until Python prompt
+            self.__serial.readAll()  # read all data and discard it
+
+    def probeDevice(self):
+        """
+        Public method to check the device is responding.
+
+        If the device has not been flashed with a MicroPython firmware, the
+        probe will fail.
+
+        @return flag indicating a communicating MicroPython device
+        @rtype bool
+        """
+        if not self.__serial:
+            return False
+
+        if not self.__serial.isConnected():
+            return False
+
+        # switch on paste mode
+        self.__blockReadyRead = True
+        ok = self.__pasteOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return False
+
+        # switch off paste mode
+        QThread.msleep(10)
+        self.__pasteOff()
+        self.__blockReadyRead = False
+
+        return True
+
+    def execute(self, commands, *, mode="raw", timeout=0):
+        """
+        Public method to send commands to the connected device and return the
+        result.
+
+        If no serial connection is available, empty results will be returned.
+
+        @param commands list of commands to be executed
+        @type str or list of str
+        @keyparam mode submit mode to be used (one of 'raw' or 'paste') (defaults to
+            'raw')
+        @type str
+        @keyparam timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        @exception ValueError raised in case of an unsupported submit mode
+        """
+        if mode not in ("paste", "raw"):
+            raise ValueError("Unsupported submit mode given ('{0}').".format(mode))
+
+        if mode == "raw":
+            return self.__execute_raw(commands, timeout=timeout)
+        elif mode == "paste":
+            return self.__execute_paste(commands, timeout=timeout)
+        else:
+            # just in case
+            return b"", b""
+
+    def __execute_raw(self, commands, timeout=0):
+        """
+        Private method to send commands to the connected device using 'raw REPL' mode
+        and return the result.
+
+        If no serial connection is available, empty results will be returned.
+
+        @param commands list of commands to be executed
+        @type str or list of str
+        @param timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        """
+        if not self.__serial:
+            return b"", b""
+
+        if not self.__serial.isConnected():
+            return b"", b"Device not connected or not switched on."
+
+        result = bytearray()
+        err = b""
+
+        if isinstance(commands, str):
+            commands = [commands]
+
+        # switch on raw mode
+        self.__blockReadyRead = True
+        ok = self.__rawOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return (b"", b"Could not switch to raw mode. Is the device switched on?")
+
+        # send commands
+        QThread.msleep(10)
+        for command in commands:
+            if command:
+                commandBytes = command.encode("utf-8")
+                self.__serial.write(commandBytes + b"\x04")
+                QCoreApplication.processEvents(
+                    QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+                )
+                ok = self.__serial.readUntil(b"OK")
+                if ok != b"OK":
+                    self.__blockReadyRead = False
+                    return (
+                        b"",
+                        "Expected 'OK', got '{0}', followed by '{1}'".format(
+                            ok, self.__serial.readAll()
+                        ).encode("utf-8"),
+                    )
+
+                # read until prompt
+                response = self.__serial.readUntil(b"\x04>", timeout=timeout)
+                if self.__serial.hasTimedOut():
+                    self.__blockReadyRead = False
+                    return b"", b"Timeout while processing commands."
+                if b"\x04" in response[:-2]:
+                    # split stdout, stderr
+                    out, err = response[:-2].split(b"\x04")
+                    result += out
+                else:
+                    err = b"invalid response received: " + response
+                if err:
+                    result = b""
+                    break
+
+        # switch off raw mode
+        QThread.msleep(10)
+        self.__rawOff()
+        self.__blockReadyRead = False
+
+        return bytes(result), err
+
+    def __execute_paste(self, commands, timeout=0):
+        """
+        Private method to send commands to the connected device using 'paste' mode
+        and return the result.
+
+        If no serial connection is available, empty results will be returned.
+
+        @param commands list of commands to be executed
+        @type str or list of str
+        @param timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        """
+        if not self.__serial:
+            return b"", b""
+
+        if not self.__serial.isConnected():
+            return b"", b"Device is not connected or not switched on."
+
+        if isinstance(commands, list):
+            commands = "\n".join(commands)
+
+        # switch on paste mode
+        self.__blockReadyRead = True
+        ok = self.__pasteOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return (b"", b"Could not switch to paste mode. Is the device switched on?")
+
+        # send commands
+        QThread.msleep(10)
+        for command in commands.splitlines(keepends=True):
+            # send the data as single lines
+            commandBytes = command.encode("utf-8")
+            self.__serial.write(commandBytes)
+            QCoreApplication.processEvents(
+                QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+            )
+            QThread.msleep(10)
+            ok = self.__serial.readUntil(commandBytes)
+            if ok != commandBytes:
+                self.__blockReadyRead = False
+                return (
+                    b"",
+                    "Expected '{0}', got '{1}', followed by '{2}'".format(
+                        commandBytes, ok, self.__serial.readAll()
+                    ).encode("utf-8"),
+                )
+
+        # switch off paste mode causing the commands to be executed
+        self.__pasteOff()
+        QThread.msleep(10)
+        # read until Python prompt
+        result = (
+            self.__serial.readUntil(b">>> ", timeout=timeout)
+            .replace(b">>> ", b"")
+            .strip()
+        )
+        if self.__serial.hasTimedOut():
+            self.__blockReadyRead = False
+            return b"", b"Timeout while processing commands."
+
+        # get rid of any OSD string and send it
+        if result.startswith(b"\x1b]0;"):
+            osd, result = result.split(b"\x1b\\", 1)
+            self.osdInfo.emit(osd[4:].decode("utf-8"))
+
+        if self.TracebackMarker in result:
+            errorIndex = result.find(self.TracebackMarker)
+            out, err = result[:errorIndex], result[errorIndex:]
+        else:
+            out = result
+            err = b""
+
+        self.__blockReadyRead = False
+        return out, err
+
+    def executeAsync(self, commandsList, submitMode):
+        """
+        Public method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+
+        @param commandsList list of commands to be execute on the device
+        @type list of str
+        @param submitMode mode to be used to submit the commands
+        @type str (one of 'raw' or 'paste')
+        @exception ValueError raised to indicate an unknown submit mode
+        """
+        if submitMode not in ("raw", "paste"):
+            raise ValueError("Illegal submit mode given ({0})".format(submitMode))
+
+        if submitMode == "raw":
+            startSequence = [  # sequence of commands to enter raw mode
+                b"\x02",  # Ctrl-B: exit raw repl (just in case)
+                b"\r\x03\x03\x03",  # Ctrl-C three times: interrupt any running program
+                b"\r\x01",  # Ctrl-A: enter raw REPL
+                b'print("\\n")\r',
+            ]
+            endSequence = [
+                b"\r",
+                b"\x04",
+            ]
+            self.__executeAsyncRaw(
+                startSequence
+                + [c.encode("utf-8") + b"\r" for c in commandsList]
+                + endSequence
+            )
+        elif submitMode == "paste":
+            self.__executeAsyncPaste(commandsList)
+
+    def __executeAsyncRaw(self, commandsList):
+        """
+        Private method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+
+        @param commandsList list of commands to be execute on the device
+        @type list of bytes
+        """
+        if commandsList:
+            command = commandsList.pop(0)
+            self.__serial.write(command)
+            QTimer.singleShot(2, lambda: self.__executeAsyncRaw(commandsList))
+        else:
+            self.__rawOff()
+            self.executeAsyncFinished.emit()
+
+    def __executeAsyncPaste(self, commandsList):
+        """
+        Private method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+
+        @param commandsList list of commands to be execute on the device
+        @type list of str
+        """
+        self.__blockReadyRead = True
+        self.__pasteOn()
+        command = b"\n".join(c.encode("utf-8)") for c in commandsList)
+        self.__serial.write(command)
+        self.__serial.readUntil(command)
+        self.__blockReadyRead = False
+        self.__pasteOff()
+        self.executeAsyncFinished.emit()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplConnectionDialog.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the WebREPL connection parameters.
+"""
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QLineEdit
+
+from eric7.EricGui import EricPixmapCache
+
+from .Devices import getSupportedDevices
+from .Ui_MicroPythonWebreplConnectionDialog import Ui_MicroPythonWebreplConnectionDialog
+
+
+class MicroPythonWebreplConnectionDialog(
+    QDialog, Ui_MicroPythonWebreplConnectionDialog
+):
+    """
+    Class implementing a dialog to enter the WebREPL connection parameters.
+    """
+
+    def __init__(self, currentWebreplUrl, currentType, parent=None):
+        """
+        Constructor
+
+        @param currentWebreplUrl WebREPL URL most recently configured
+        @type str
+        @param currentType device type most recently selected
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.deviceTypeComboBox.addItem("", "")
+        for board, description in sorted(getSupportedDevices(), key=lambda x: x[1]):
+            self.deviceTypeComboBox.addItem(description, board)
+
+        self.showPasswordButton.setIcon(EricPixmapCache.getIcon("showPassword"))
+
+        self.hostEdit.textChanged.connect(self.__updateOkButton)
+        self.portEdit.textChanged.connect(self.__updateOkButton)
+        self.deviceTypeComboBox.currentIndexChanged.connect(self.__updateOkButton)
+
+        if currentWebreplUrl:
+            url = currentWebreplUrl.replace("ws://", "")
+            password, hostPort = url.split("@", 1) if "@" in url else ("", url)
+            host, port = hostPort.split(":", 1) if ":" in hostPort else (hostPort, "")
+            self.hostEdit.setText(host)
+            self.portEdit.setText(port)
+            self.passwordEdit.setText(password)
+
+            typeIndex = self.deviceTypeComboBox.findData(currentType)
+            self.deviceTypeComboBox.setCurrentIndex(typeIndex)
+        else:
+            self.__updateOkButton()
+
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+
+    @pyqtSlot()
+    def __updateOkButton(self):
+        """
+        Private slot to update the enabled state of the OK button.
+        """
+        port = self.portEdit.text()
+        if port == "":
+            portOk = True
+        else:
+            try:
+                portNo = int(port)
+                portOk = 1024 < portNo <= 65535
+            except ValueError:
+                portOk = False
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            bool(self.hostEdit.text())
+            and portOk
+            and bool(self.deviceTypeComboBox.currentData())
+        )
+
+    @pyqtSlot(bool)
+    def on_showPasswordButton_clicked(self, checked):
+        """
+        Private slot to show or hide the password.
+
+        @param checked state of the button
+        @type bool
+        """
+        if checked:
+            self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Normal)
+            self.showPasswordButton.setIcon(EricPixmapCache.getIcon("hidePassword"))
+            self.showPasswordButton.setToolTip(self.tr("Press to hide the password."))
+        else:
+            self.passwordEdit.setEchoMode(QLineEdit.EchoMode.Password)
+            self.showPasswordButton.setIcon(EricPixmapCache.getIcon("showPassword"))
+            self.showPasswordButton.setToolTip(self.tr("Press to show the password."))
+
+    def getWebreplConnectionParameters(self):
+        """
+        Public method to retrieve the entered WebREPL connection data.
+
+        @return tuple containing the URL and device type for the WebREPL connection
+        @rtype tuple of (str, str)
+        """
+        password = self.passwordEdit.text()
+        host = self.hostEdit.text()
+        port = self.portEdit.text()
+
+        if password and port:
+            url = f"ws://{password}@{host}:{port}"
+        elif password:
+            url = f"ws://{password}@{host}"
+        elif port:
+            url = f"ws://{host}:{port}"
+        else:
+            url = f"ws://{host}"
+
+        return (url, self.deviceTypeComboBox.currentData())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplConnectionDialog.ui	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonWebreplConnectionDialog</class>
+ <widget class="QDialog" name="MicroPythonWebreplConnectionDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>172</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WebREPL Connection</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Host:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1" colspan="2">
+    <widget class="QLineEdit" name="hostEdit">
+     <property name="toolTip">
+      <string>Enter the host name or IPv4 address of the device.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>Port:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1" colspan="2">
+    <widget class="QLineEdit" name="portEdit">
+     <property name="toolTip">
+      <string>Enter the port of the WebREPL (empty for default port 8266).</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Password:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QLineEdit" name="passwordEdit">
+     <property name="toolTip">
+      <string>Enter the password for this device connection.</string>
+     </property>
+     <property name="echoMode">
+      <enum>QLineEdit::Password</enum>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="2">
+    <widget class="QToolButton" name="showPasswordButton">
+     <property name="toolTip">
+      <string>Press to show the password.</string>
+     </property>
+     <property name="checkable">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="label_6">
+     <property name="text">
+      <string>Device Type:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1" colspan="2">
+    <widget class="QComboBox" name="deviceTypeComboBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="toolTip">
+      <string>Select the device type</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0" colspan="3">
+    <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>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>hostEdit</tabstop>
+  <tabstop>portEdit</tabstop>
+  <tabstop>passwordEdit</tabstop>
+  <tabstop>deviceTypeComboBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MicroPythonWebreplConnectionDialog</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>MicroPythonWebreplConnectionDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplDeviceInterface.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,299 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module  implementing an interface to talk to a connected MicroPython device via
+a webrepl connection.
+"""
+
+from PyQt6.QtCore import QThread, pyqtSlot
+from PyQt6.QtWidgets import QInputDialog, QLineEdit
+
+from eric7 import Preferences
+from eric7.EricWidgets import EricMessageBox
+
+from .MicroPythonDeviceInterface import MicroPythonDeviceInterface
+from .MicroPythonWebreplSocket import MicroPythonWebreplSocket
+
+
+class MicroPythonWebreplDeviceInterface(MicroPythonDeviceInterface):
+    """
+    Class implementing an interface to talk to a connected MicroPython device via
+    a webrepl connection.
+    """
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super().__init__(parent)
+
+        self.__blockReadyRead = False
+
+        self.__socket = MicroPythonWebreplSocket(
+            timeout=Preferences.getMicroPython("WebreplTimeout"), parent=self
+        )
+        self.__connected = False
+        self.__socket.readyRead.connect(self.__readSocket)
+
+    @pyqtSlot()
+    def __readSocket(self):
+        """
+        Private slot to read all available data and emit it with the
+        "dataReceived" signal for further processing.
+        """
+        if not self.__blockReadyRead:
+            data = bytes(self.__socket.readAll())
+            self.dataReceived.emit(data)
+
+    def __readAll(self):
+        """
+        Private method to read all data and emit it for further processing.
+        """
+        data = self.__socket.readAll()
+        self.dataReceived.emit(data)
+
+    @pyqtSlot()
+    def connectToDevice(self, connection):
+        """
+        Public slot to connect to the device.
+
+        @param connection name of the connection to be used in the form of an URL string
+            (ws://password@host:port)
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        connection = connection.replace("ws://", "")
+        try:
+            password, hostPort = connection.split("@", 1)
+        except ValueError:
+            password, hostPort = None, connection
+        if password is None:
+            password, ok = QInputDialog.getText(
+                None,
+                self.tr("WebRepl Password"),
+                self.tr("Enter the WebRepl password:"),
+                QLineEdit.EchoMode.Password,
+            )
+            if not ok:
+                return False
+
+        try:
+            host, port = hostPort.split(":", 1)
+            port = int(port)
+        except ValueError:
+            host, port = hostPort, 8266  # default port is 8266
+
+        self.__blockReadyRead = True
+        ok = self.__socket.connectToDevice(host, port)
+        if ok:
+            ok = self.__socket.login(password)
+            if not ok:
+                EricMessageBox.warning(
+                    None,
+                    self.tr("WebRepl Login"),
+                    self.tr(
+                        "The login to the selected device 'webrepl' failed. The given"
+                        " password may be incorrect."
+                    ),
+                )
+
+        self.__connected = ok
+        self.__blockReadyRead = False
+
+        return self.__connected
+
+    @pyqtSlot()
+    def disconnectFromDevice(self):
+        """
+        Public slot to disconnect from the device.
+        """
+        self.__socket.disconnect()
+        self.__connected = False
+
+    def isConnected(self):
+        """
+        Public method to get the connection status.
+
+        @return flag indicating the connection status
+        @rtype bool
+        """
+        return self.__connected
+
+    @pyqtSlot()
+    def handlePreferencesChanged(self):
+        """
+        Public slot to handle a change of the preferences.
+        """
+        self.__socket.setTimeout(Preferences.getMicroPython("WebreplTimeout"))
+
+    def write(self, data):
+        """
+        Public method to write data to the connected device.
+
+        @param data data to be written
+        @type bytes or bytearray
+        """
+        self.__connected and self.__socket.writeTextMessage(data)
+
+    def probeDevice(self):
+        """
+        Public method to check the device is responding.
+
+        If the device has not been flashed with a MicroPython firmware, the
+        probe will fail.
+
+        @return flag indicating a communicating MicroPython device
+        @rtype bool
+        """
+        if not self.__connected:
+            return False
+
+        # switch on paste mode
+        self.__blockReadyRead = True
+        ok = self.__pasteOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return False
+
+        # switch off raw mode
+        QThread.msleep(10)
+        self.__pasteOff()
+        self.__blockReadyRead = False
+
+        return True
+
+    def execute(self, commands, *, mode="raw", timeout=0):
+        """
+        Public method to send commands to the connected device and return the
+        result.
+
+        @param commands list of commands to be executed
+        @type str or list of str
+        @keyparam mode submit mode to be used (one of 'raw' or 'paste') (defaults to
+            'raw'). This is ignored because webrepl always uses 'paste' mode.
+        @type str
+        @keyparam timeout per command timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        """
+        if not self.__connected:
+            return b"", b"Device is not connected."
+
+        if isinstance(commands, list):
+            commands = "\n".join(commands)
+
+        # switch on paste mode
+        self.__blockReadyRead = True
+        ok = self.__pasteOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return (b"", b"Could not switch to paste mode. Is the device switched on?")
+
+        # send commands
+        commandBytes = commands.encode("utf-8")
+        self.__socket.writeTextMessage(commandBytes)
+        ok = self.__socket.readUntil(commandBytes)
+        if ok != commandBytes:
+            self.__blockReadyRead = False
+            return (
+                b"",
+                "Expected '{0}', got '{1}', followed by '{2}'".format(
+                    commandBytes, ok, self.__socket.readAll()
+                ).encode("utf-8"),
+            )
+
+        # switch off paste mode causing the commands to be executed
+        self.__pasteOff()
+
+        # read until Python prompt
+        result = (
+            self.__socket.readUntil(b">>> ", timeout=timeout)
+            .replace(b">>> ", b"")
+            .strip()
+        )
+        if self.__socket.hasTimedOut():
+            self.__blockReadyRead = False
+            return b"", b"Timeout while processing commands."
+
+        # get rid of any OSD string and send it
+        if result.startswith(b"\x1b]0;"):
+            osd, result = result.split(b"\x1b\\", 1)
+            self.osdInfo.emit(osd[4:].decode("utf-8"))
+
+        if self.TracebackMarker in result:
+            errorIndex = result.find(self.TracebackMarker)
+            out, err = result[:errorIndex], result[errorIndex:].replace(">>> ", "")
+        else:
+            out = result
+            err = b""
+
+        self.__blockReadyRead = False
+        return out, err
+
+    def executeAsync(self, commandsList, submitMode):
+        """
+        Public method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+
+        @param commandsList list of commands to be execute on the device
+        @type list of str
+        @param submitMode mode to be used to submit the commands
+        @type str (one of 'raw' or 'paste')
+        """
+        self.__blockReadyRead = True
+        self.__pasteOn()
+        command = b"\n".join(c.encode("utf-8)") for c in commandsList)
+        self.__socket.writeTextMessage(command)
+        self.__socket.readUntil(command)
+        self.__blockReadyRead = False
+        self.__pasteOff()
+        self.executeAsyncFinished.emit()
+
+    def __pasteOn(self):
+        """
+        Private method to switch the connected device to 'paste' mode.
+
+        Note: switching to paste mode is done with synchronous writes.
+
+        @return flag indicating success
+        @rtype bool
+        """
+        if not self.__connected:
+            return False
+
+        pasteMessage = b"paste mode; Ctrl-C to cancel, Ctrl-D to finish\r\n=== "
+
+        self.__socket.writeTextMessage(b"\x02")  # end raw mode if required
+        for _i in range(3):
+            # CTRL-C three times to break out of loops
+            self.__socket.writeTextMessage(b"\r\x03")
+            # time out after 500ms if device is not responding
+        self.__socket.readAll()  # read all data and discard it
+        self.__socket.writeTextMessage(b"\r\x05")  # send CTRL-E to enter paste mode
+        self.__socket.readUntil(pasteMessage)
+
+        if self.__socket.hasTimedOut():
+            # it timed out; try it again and than fail
+            self.__socket.writeTextMessage(b"\r\x05")  # send CTRL-E again
+            self.__socket.readUntil(pasteMessage)
+            if self.__socket.hasTimedOut():
+                return False
+
+        self.__socket.readAll()  # read all data and discard it
+        return True
+
+    def __pasteOff(self):
+        """
+        Private method to switch 'paste' mode off.
+        """
+        if self.__connected:
+            self.__socket.writeTextMessage(b"\x04")  # send CTRL-D to cancel paste mode
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplSocket.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,251 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a websocket class to be connect to the MicroPython webrepl
+interface.
+"""
+
+from PyQt6.QtCore import (
+    QCoreApplication, QEventLoop, QMutex, QTime, QTimer, QUrl, pyqtSignal, pyqtSlot
+)
+from PyQt6.QtNetwork import QAbstractSocket
+from PyQt6.QtWebSockets import QWebSocket
+
+from eric7.EricUtilities.EricMutexLocker import EricMutexLocker
+
+
+class MicroPythonWebreplSocket(QWebSocket):
+    """
+    Class implementing a websocket client to be connected to the MicroPython webrepl
+    interface.
+
+    @signal readyRead() emitted to signal the availability of data
+    """
+
+    readyRead = pyqtSignal()
+
+    def __init__(self, timeout=10000, parent=None):
+        """
+        Constructor
+
+        @param timeout timout in milliseconds to be set
+        @type int
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super().__init__(parent=parent)
+
+        self.__connected = False
+        self.__timeout = timeout  # 10s default timeout
+        self.__timedOut = False
+
+        self.__mutex = QMutex()
+        self.__buffer = b""
+        self.textMessageReceived.connect(self.__textDataReceived)
+
+    @pyqtSlot(str)
+    def __textDataReceived(self, strMessage):
+        """
+        Private slot handling a received text message.
+
+        @param strMessage received text message
+        @type str
+        """
+        with EricMutexLocker(self.__mutex):
+            self.__buffer += strMessage.encode("utf-8")
+
+        self.readyRead.emit()
+
+    def setTimeout(self, timeout):
+        """
+        Public method to set the socket timeout value.
+
+        @param timeout timout in milliseconds to be set
+        @type int
+        """
+        self.__timeout = timeout
+
+    def waitForConnected(self):
+        """
+        Public method to wait for the websocket being connected.
+
+        @return flag indicating the connect result
+        @rtype bool
+        """
+        loop = QEventLoop()
+        self.connected.connect(loop.quit)
+        self.errorOccurred.connect(loop.quit)
+
+        def timeout():
+            loop.quit()
+            self.__timedOut = True
+
+        self.__timedOut = False
+        timer = QTimer()
+        timer.setSingleShot(True)
+        timer.timeout.connect(timeout)
+        timer.start(self.__timeout)
+        
+        loop.exec()
+        timer.stop()
+        if self.state() == QAbstractSocket.SocketState.ConnectedState:
+            self.__connected = True
+            return True
+        else:
+            self.__connected = False
+            return False
+
+    def connectToDevice(self, host, port):
+        """
+        Public method to connect to the given host and port.
+
+        @param host host name or IP address
+        @type str
+        @param port port number
+        @type int
+        @return flag indicating success
+        @rtype bool
+        """
+        if self.__connected:
+            self.disconnectFromDevice()
+
+        url = QUrl(f"ws://{host}:{port}")
+        self.open(url)
+        ok = self.waitForConnected()
+        if not ok:
+            return False
+
+        self.__connected = True
+        return True
+
+    def disconnect(self):
+        """
+        Public method to disconnect the websocket.
+        """
+        if self.__connected:
+            self.close()
+            self.__connected = False
+
+    def isConnected(self):
+        """
+        Public method to check the connected state of the websocket.
+
+        @return flag indicating the connected state
+        @rtype bool
+        """
+        return self.__connected
+
+    def hasTimedOut(self):
+        """
+        Public method to check, if the last 'readUntil()' has timed out.
+
+        @return flag indicating a timeout
+        @@rtype bool
+        """
+        return self.__timedOut
+
+    def login(self, password):
+        """
+        Public method to login to the webrepl console of the device.
+
+        @param password password
+        @type str
+        @return flag indicating a successful login
+        @rtype bool
+        """
+        self.readUntil(expected=b": ")
+        self.writeTextMessage(password.encode("utf-8") + b"\r")
+        data = self.readUntil([b">>> ", b"denied\r\n"])
+
+        return not data.endswith(b"denied\r\n")
+
+    def writeTextMessage(self, data):
+        """
+        Public method write some text data to the webrepl server of the connected
+        device.
+
+        @param data text data to be sent
+        @type bytes
+        """
+        self.sendTextMessage(data.decode("utf-8"))
+        self.flush()
+
+    def readAll(self, timeout=0):
+        """
+        Public method to read all available data.
+
+        @param timeout timeout in milliseconds (0 for no timeout)
+            (defaults to 0)
+        @type int (optional)
+        @return received data
+        @rtype bytes
+        """
+        QCoreApplication.processEvents(
+            QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents
+        )
+        if timeout > 0:
+            # receive data for 'timeout' milliseconds
+            loop = QEventLoop()
+            QTimer.singleShot(timeout, loop.quit)
+            loop.exec()
+
+        # return all buffered data
+        with EricMutexLocker(self.__mutex):
+            data = self.__buffer
+            self.__buffer = b""
+
+        return data
+
+    def readUntil(self, expected=b"\n", size=None, timeout=0):
+        r"""
+        Public method to read data until an expected sequence is found
+        (default: \n) or a specific size is exceeded.
+
+        @param expected expected bytes sequence
+        @type bytes
+        @param size maximum data to be read (defaults to None)
+        @type int (optional)
+        @param timeout timeout in milliseconds (0 for configured default)
+            (defaults to 0)
+        @type int (optional)
+        @return bytes read from the device including the expected sequence
+        @rtype bytes
+        """
+        data = b""
+        self.__timedOut = False
+
+        if timeout == 0:
+            timeout = self.__timeout
+
+        if not isinstance(expected, list):
+            expected = [expected]
+
+        t = QTime.currentTime()
+        while True:
+            QCoreApplication.processEvents(
+                QEventLoop.ProcessEventsFlag.ExcludeUserInputEvents, 500
+            )
+            with EricMutexLocker(self.__mutex):
+                if any(e in self.__buffer for e in expected):
+                    for e in expected:
+                        index = self.__buffer.find(e)
+                        if index >= 0:
+                            endIndex = index + len(e)
+                            data = self.__buffer[:endIndex]
+                            self.__buffer = self.__buffer[endIndex:]
+                            break
+                    break
+                if size is not None and len(self.__buffer) >= size:
+                    data = self.__buffer[:size]
+                    self.__buffer = self.__buffer[size:]
+                    break
+                if t.msecsTo(QTime.currentTime()) > timeout:
+                    self.__timedOut = True
+                    data = self.__buffer
+                    self.__buffer = b""
+                    break
+
+        return data
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplUrlAddEditDialog.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to edit the parameters for a WebREPL connection.
+"""
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox
+
+from .Devices import getSupportedDevices
+from .Ui_MicroPythonWebreplUrlAddEditDialog import Ui_MicroPythonWebreplUrlAddEditDialog
+
+
+class MicroPythonWebreplUrlAddEditDialog(
+    QDialog, Ui_MicroPythonWebreplUrlAddEditDialog
+):
+    """
+    Class implementing a dialog to edit the parameters for a WebREPL connection.
+    """
+
+    def __init__(self, definedNames, connectionParams=None, parent=None):
+        """
+        Constructor
+
+        @param definedNames list of already define WebREPL connection names
+        @type list of str
+        @param connectionParams parameters for the WebREPL connection to be edited
+            (default to None)
+        @type tuple of (str, str, str) (optional)
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.__definedNames = definedNames[:]
+
+        self.deviceTypeComboBox.addItem("", "")
+        for board, description in sorted(getSupportedDevices(), key=lambda x: x[1]):
+            self.deviceTypeComboBox.addItem(description, board)
+
+        self.nameEdit.textChanged.connect(self.__updateOkButton)
+        self.descriptionEdit.textChanged.connect(self.__updateOkButton)
+        self.hostEdit.textChanged.connect(self.__updateOkButton)
+        self.portEdit.textChanged.connect(self.__updateOkButton)
+        self.deviceTypeComboBox.currentIndexChanged.connect(self.__updateOkButton)
+
+        if connectionParams:
+            self.__editName = connectionParams[0]
+            self.__populateFields(connectionParams)
+        else:
+            self.__editName = ""
+            self.__updateOkButton()
+
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+
+    def __populateFields(self, params):
+        """
+        Private method to populate the various dialog fields with the given parameters.
+
+        @param params arameters for the WebREPL connection to be edited
+        @type tuple of (str, str, str)
+        """
+        self.nameEdit.setText(params[0])
+        self.descriptionEdit.setText(params[1])
+
+        url = params[2].replace("ws://", "")
+        password, hostPort = url.split("@", 1) if "@" in url else ("", url)
+        host, port = hostPort.split(":", 1) if ":" in hostPort else (hostPort, "")
+        self.hostEdit.setText(host)
+        self.portEdit.setText(port)
+        self.passwordEdit.setText(password)
+
+        typeIndex = self.deviceTypeComboBox.findData(params["device_type"])
+        self.deviceTypeComboBox.setCurrentIndex(typeIndex)
+
+    @pyqtSlot()
+    def __updateOkButton(self):
+        """
+        Private slot to update the enabled state of the OK button.
+        """
+        port = self.portEdit.text()
+        if port == "":
+            portOk = True
+        else:
+            try:
+                portNo = int(port)
+                portOk = 1024 < portNo <= 65535
+            except ValueError:
+                portOk = False
+
+        name = self.nameEdit.text()
+        nameOk = bool(name) and (
+            name == self.__editName or name not in self.__definedNames
+        )
+
+        self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+            nameOk
+            and bool(self.descriptionEdit.text())
+            and bool(self.hostEdit.text())
+            and portOk
+            and bool(self.deviceTypeComboBox.currentData())
+        )
+
+    def getWebreplUrl(self):
+        """
+        Public method to retrieve the entered WebREPL connection data.
+
+        @return tuple containing the name, description, URL and device type for
+            the WebREPL connection
+        @rtype tuple of (str, str, str, str)
+        """
+        password = self.passwordEdit.text()
+        host = self.hostEdit.text()
+        port = self.portEdit.text()
+
+        if password and port:
+            url = f"ws://{password}@{host}:{port}"
+        elif password:
+            url = f"ws://{password}@{host}"
+        elif port:
+            url = f"ws://{host}:{port}"
+        else:
+            url = f"ws://{host}"
+
+        return (
+            self.nameEdit.text(),
+            self.descriptionEdit.text(),
+            url,
+            self.deviceTypeComboBox.currentData(),
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplUrlAddEditDialog.ui	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,172 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonWebreplUrlAddEditDialog</class>
+ <widget class="QDialog" name="MicroPythonWebreplUrlAddEditDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>236</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WebREPL URL</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Name:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <widget class="QLineEdit" name="nameEdit">
+     <property name="toolTip">
+      <string>Enter a unique name for the WebREPL connection.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Description:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLineEdit" name="descriptionEdit">
+     <property name="toolTip">
+      <string>Enter a short description to be shown in the selector.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Host:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QLineEdit" name="hostEdit">
+     <property name="toolTip">
+      <string>Enter the host name or IPv4 address of the device.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>Port:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QLineEdit" name="portEdit">
+     <property name="toolTip">
+      <string>Enter the port of the WebREPL (empty for default port 8266).</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0">
+    <widget class="QLabel" name="label_5">
+     <property name="text">
+      <string>Password:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="1">
+    <widget class="QLineEdit" name="passwordEdit">
+     <property name="toolTip">
+      <string>Enter the password for this device connection.</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="0">
+    <widget class="QLabel" name="label_6">
+     <property name="text">
+      <string>Device Type:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="1">
+    <widget class="QComboBox" name="deviceTypeComboBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="toolTip">
+      <string>Select the device type</string>
+     </property>
+    </widget>
+   </item>
+   <item row="6" column="0" colspan="2">
+    <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>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MicroPythonWebreplUrlAddEditDialog</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>MicroPythonWebreplUrlAddEditDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplUrlsConfigDialog.py	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,172 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to edit the list of configured WebREPL URLs.
+"""
+
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import QDialog, QTreeWidgetItem
+
+from eric7.EricWidgets import EricMessageBox
+
+from .MicroPythonWebreplUrlAddEditDialog import MicroPythonWebreplUrlAddEditDialog
+from .Ui_MicroPythonWebreplUrlsConfigDialog import Ui_MicroPythonWebreplUrlsConfigDialog
+
+
+class MicroPythonWebreplUrlsConfigDialog(
+    QDialog, Ui_MicroPythonWebreplUrlsConfigDialog
+):
+    """
+    Class implementing a dialog to edit the list of configured WebREPL URLs.
+    """
+
+    def __init__(self, webreplDict, parent=None):
+        """
+        Constructor
+
+        @param webreplDict dictionary containing the configured WebREPL URLs
+        @type dict
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        for name, data in webreplDict.items():
+            itm = QTreeWidgetItem(
+                self.webreplUrlsList,
+                [name, data["description"], data["url"]],
+            )
+            itm.setData(0, Qt.ItemDataRole.UserRole, data["device_type"])
+
+        self.__sortItems()
+        self.__resizeColumns()
+        self.__updateActionButtons()
+
+        self.webreplUrlsList.itemSelectionChanged.connect(self.__updateActionButtons)
+
+    @pyqtSlot()
+    def __sortItems(self):
+        """
+        Private slot to sort the list by name column (i.e. column 0).
+        """
+        self.webreplUrlsList.sortItems(0, Qt.SortOrder.AscendingOrder)
+
+    @pyqtSlot()
+    def __resizeColumns(self):
+        """
+        Private slot to resize the columns to their contents.
+        """
+        for column in range(self.webreplUrlsList.columnCount()):
+            self.webreplUrlsList.resizeColumnToContents(column)
+
+    @pyqtSlot()
+    def __updateActionButtons(self):
+        """
+        Private slot to change the enabled state of the action buttons.
+        """
+        selectedItemsCount = len(self.webreplUrlsList.selectedItems())
+        self.editButton.setEnabled(selectedItemsCount == 1)
+        self.removeButton.setEnabled(selectedItemsCount > 0)
+
+        self.removeAllButton.setEnabled(self.webreplUrlsList.topLevelItemCount() > 0)
+
+    def __definedNames(self):
+        """
+        Private method to get a list of defined connection names.
+
+        @return list of defined connection names
+        @rtype list of str
+        """
+        return [
+            self.webreplUrlsList.topLevelItem(row).text(0)
+            for row in range(self.webreplUrlsList.topLevelItemCount())
+        ]
+
+    @pyqtSlot()
+    def on_addButton_clicked(self):
+        """
+        Private slot to add a new WebREPL connection.
+        """
+        dlg = MicroPythonWebreplUrlAddEditDialog(self.__definedNames(), parent=self)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            name, description, url, deviceType = dlg.getWebreplUrl()
+            itm = QTreeWidgetItem(self.webreplUrlsList, [name, description, url])
+            itm.setData(0, Qt.ItemDataRole.UserRole, deviceType)
+
+            self.__sortItems()
+            self.__resizeColumns()
+            self.__updateActionButtons()
+
+    @pyqtSlot()
+    def on_editButton_clicked(self):
+        """
+        Private slot to edit the selected WebREPL connection.
+        """
+        itm = self.webreplUrlsList.selectedItems()[0]
+        dlg = MicroPythonWebreplUrlAddEditDialog(
+            self.__definedNames(),
+            connectionParams=(itm.text(0), itm.text(1), itm.text(2)),
+            parent=self,
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            name, description, url, deviceType = dlg.getWebreplUrl()
+            itm.setText(0, name)
+            itm.setText(1, description)
+            itm.setText(2, url)
+            itm.setData(0, Qt.ItemDataRole.UserRole, deviceType)
+
+            self.__sortItems()
+            self.__resizeColumns()
+            self.__updateActionButtons()
+
+    @pyqtSlot()
+    def on_removeButton_clicked(self):
+        """
+        Private slot to remove the selected entries.
+        """
+        ok = EricMessageBox.yesNo(
+            self,
+            self.tr("Remove Selected WebREPL URLs"),
+            self.tr("""Shall the selected WebREPL URLs really be removed?"""),
+        )
+        if ok:
+            for itm in self.webreplUrlsList.selectedItems():
+                self.webreplUrlsList.takeTopLevelItem(
+                    self.webreplUrlsList.indexOfTopLevelItem(itm)
+                )
+                del itm
+
+    @pyqtSlot()
+    def on_removeAllButton_clicked(self):
+        """
+        Private slot to remove all entries.
+        """
+        ok = EricMessageBox.yesNo(
+            self,
+            self.tr("Remove All WebREPL URLs"),
+            self.tr("""Shall all WebREPL URLs really be removed?"""),
+        )
+        if ok:
+            self.webreplUrlsList.clear()
+
+    def getWebreplDict(self):
+        """
+        Public method to retrieve a dictionary containing the configured WebREPL URLs.
+
+        @return dictionary containing the configured WebREPL URLs
+        @rtype dict
+        """
+        webreplDict = {}
+        for row in range(self.webreplUrlsList.topLevelItemCount()):
+            itm = self.webreplUrlsList.topLevelItem(row)
+            webreplDict[itm.text(0)] = {
+                "description": itm.text(1),
+                "url": itm.text(2),
+                "device_type": itm.data(0, Qt.ItemDataRole.UserRole),
+            }
+
+        return webreplDict
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/MicroPythonWebreplUrlsConfigDialog.ui	Thu May 04 17:31:13 2023 +0200
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonWebreplUrlsConfigDialog</class>
+ <widget class="QDialog" name="MicroPythonWebreplUrlsConfigDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>713</width>
+    <height>516</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>WebREPL URLs</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QTreeWidget" name="webreplUrlsList">
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::ExtendedSelection</enum>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Description</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>URL</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item row="0" column="1">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QPushButton" name="addButton">
+       <property name="toolTip">
+        <string>Press to add a new entry.</string>
+       </property>
+       <property name="text">
+        <string>Add...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="editButton">
+       <property name="toolTip">
+        <string>Press to edit the selected entry.</string>
+       </property>
+       <property name="text">
+        <string>Edit...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="toolTip">
+        <string>Press to remove the selected entries.</string>
+       </property>
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeAllButton">
+       <property name="toolTip">
+        <string>Press to remove all entries.</string>
+       </property>
+       <property name="text">
+        <string>Remove All</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="2" column="0" colspan="2">
+    <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="0" colspan="2">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>&lt;b&gt;Note:&lt;/b&gt; The name of an entry must be unique amongst the list.</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MicroPythonWebreplUrlsConfigDialog</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>MicroPythonWebreplUrlsConfigDialog</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>
--- a/src/eric7/MicroPython/MicroPythonWidget.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/MicroPythonWidget.py	Thu May 04 17:31:13 2023 +0200
@@ -10,21 +10,14 @@
 import contextlib
 import functools
 import os
-import re
 import time
 
-from PyQt6.QtCore import QEvent, QPoint, Qt, pyqtSignal, pyqtSlot
-from PyQt6.QtGui import QBrush, QClipboard, QColor, QKeySequence, QTextCursor
+from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
 from PyQt6.QtWidgets import (
-    QApplication,
     QDialog,
-    QHBoxLayout,
     QInputDialog,
-    QLabel,
     QLineEdit,
     QMenu,
-    QSizePolicy,
-    QTextEdit,
     QToolButton,
     QWidget,
 )
@@ -37,7 +30,6 @@
 from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
 from eric7.EricWidgets.EricPlainTextDialog import EricPlainTextDialog
 from eric7.EricWidgets.EricProcessDialog import EricProcessDialog
-from eric7.EricWidgets.EricZoomWidget import EricZoomWidget
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities
 from eric7.UI.Info import BugAddress
 
@@ -46,6 +38,7 @@
 from .EthernetDialogs.EthernetController import EthernetController
 from .MicroPythonFileManager import MicroPythonFileManager
 from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget
+from .MicroPythonWebreplDeviceInterface import MicroPythonWebreplDeviceInterface
 from .Ui_MicroPythonWidget import Ui_MicroPythonWidget
 from .WifiDialogs.WifiController import WifiController
 
@@ -57,142 +50,12 @@
     HAS_QTCHART = False
 
 try:
-    from .MicroPythonDeviceInterface import MicroPythonDeviceInterface
+    from .MicroPythonSerialDeviceInterface import MicroPythonSerialDeviceInterface
 
     HAS_QTSERIALPORT = True
 except ImportError:
     HAS_QTSERIALPORT = False
 
-# ANSI Colors (see https://en.wikipedia.org/wiki/ANSI_escape_code)
-AnsiColorSchemes = {
-    "Windows 7": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(128, 0, 0)),
-        2: QBrush(QColor(0, 128, 0)),
-        3: QBrush(QColor(128, 128, 0)),
-        4: QBrush(QColor(0, 0, 128)),
-        5: QBrush(QColor(128, 0, 128)),
-        6: QBrush(QColor(0, 128, 128)),
-        7: QBrush(QColor(192, 192, 192)),
-        10: QBrush(QColor(128, 128, 128)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Windows 10": {
-        0: QBrush(QColor(12, 12, 12)),
-        1: QBrush(QColor(197, 15, 31)),
-        2: QBrush(QColor(19, 161, 14)),
-        3: QBrush(QColor(193, 156, 0)),
-        4: QBrush(QColor(0, 55, 218)),
-        5: QBrush(QColor(136, 23, 152)),
-        6: QBrush(QColor(58, 150, 221)),
-        7: QBrush(QColor(204, 204, 204)),
-        10: QBrush(QColor(118, 118, 118)),
-        11: QBrush(QColor(231, 72, 86)),
-        12: QBrush(QColor(22, 198, 12)),
-        13: QBrush(QColor(249, 241, 165)),
-        14: QBrush(QColor(59, 12, 255)),
-        15: QBrush(QColor(180, 0, 158)),
-        16: QBrush(QColor(97, 214, 214)),
-        17: QBrush(QColor(242, 242, 242)),
-    },
-    "PuTTY": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(187, 0, 0)),
-        2: QBrush(QColor(0, 187, 0)),
-        3: QBrush(QColor(187, 187, 0)),
-        4: QBrush(QColor(0, 0, 187)),
-        5: QBrush(QColor(187, 0, 187)),
-        6: QBrush(QColor(0, 187, 187)),
-        7: QBrush(QColor(187, 187, 187)),
-        10: QBrush(QColor(85, 85, 85)),
-        11: QBrush(QColor(255, 85, 85)),
-        12: QBrush(QColor(85, 255, 85)),
-        13: QBrush(QColor(255, 255, 85)),
-        14: QBrush(QColor(85, 85, 255)),
-        15: QBrush(QColor(255, 85, 255)),
-        16: QBrush(QColor(85, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "xterm": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(205, 0, 0)),
-        2: QBrush(QColor(0, 205, 0)),
-        3: QBrush(QColor(205, 205, 0)),
-        4: QBrush(QColor(0, 0, 238)),
-        5: QBrush(QColor(205, 0, 205)),
-        6: QBrush(QColor(0, 205, 205)),
-        7: QBrush(QColor(229, 229, 229)),
-        10: QBrush(QColor(127, 127, 127)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Ubuntu": {
-        0: QBrush(QColor(1, 1, 1)),
-        1: QBrush(QColor(222, 56, 43)),
-        2: QBrush(QColor(57, 181, 74)),
-        3: QBrush(QColor(255, 199, 6)),
-        4: QBrush(QColor(0, 11, 184)),
-        5: QBrush(QColor(118, 38, 113)),
-        6: QBrush(QColor(44, 181, 233)),
-        7: QBrush(QColor(204, 204, 204)),
-        10: QBrush(QColor(128, 128, 128)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Ubuntu (dark)": {
-        0: QBrush(QColor(96, 96, 96)),
-        1: QBrush(QColor(235, 58, 45)),
-        2: QBrush(QColor(57, 181, 74)),
-        3: QBrush(QColor(255, 199, 29)),
-        4: QBrush(QColor(25, 56, 230)),
-        5: QBrush(QColor(200, 64, 193)),
-        6: QBrush(QColor(48, 200, 255)),
-        7: QBrush(QColor(204, 204, 204)),
-        10: QBrush(QColor(128, 128, 128)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Breeze (dark)": {
-        0: QBrush(QColor(35, 38, 39)),
-        1: QBrush(QColor(237, 21, 21)),
-        2: QBrush(QColor(17, 209, 22)),
-        3: QBrush(QColor(246, 116, 0)),
-        4: QBrush(QColor(29, 153, 243)),
-        5: QBrush(QColor(155, 89, 182)),
-        6: QBrush(QColor(26, 188, 156)),
-        7: QBrush(QColor(252, 252, 252)),
-        10: QBrush(QColor(127, 140, 141)),
-        11: QBrush(QColor(192, 57, 43)),
-        12: QBrush(QColor(28, 220, 154)),
-        13: QBrush(QColor(253, 188, 75)),
-        14: QBrush(QColor(61, 174, 233)),
-        15: QBrush(QColor(142, 68, 173)),
-        16: QBrush(QColor(22, 160, 133)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-}
-
 
 class MicroPythonWidget(QWidget, Ui_MicroPythonWidget):
     """
@@ -202,15 +65,14 @@
         connection for further processing
     """
 
-    ZoomMin = -10
-    ZoomMax = 20
-
     DeviceTypeRole = Qt.ItemDataRole.UserRole
     DeviceBoardRole = Qt.ItemDataRole.UserRole + 1
     DevicePortRole = Qt.ItemDataRole.UserRole + 2
     DeviceVidRole = Qt.ItemDataRole.UserRole + 3
     DevicePidRole = Qt.ItemDataRole.UserRole + 4
     DeviceSerNoRole = Qt.ItemDataRole.UserRole + 5
+    DeviceInterfaceTypeRole = Qt.ItemDataRole.UserRole + 6
+    DeviceWebreplUrlRole = Qt.ItemDataRole.UserRole + 7
 
     dataReceived = pyqtSignal(bytes)
 
@@ -254,34 +116,14 @@
 
         self.deviceIconLabel.setPixmap(Devices.getDeviceIcon("", False))
 
-        self.checkButton.setIcon(EricPixmapCache.getIcon("question"))
+        self.repopulateButton.setIcon(EricPixmapCache.getIcon("question"))
+        self.webreplConfigButton.setIcon(EricPixmapCache.getIcon("edit"))
         self.runButton.setIcon(EricPixmapCache.getIcon("start"))
         self.replButton.setIcon(EricPixmapCache.getIcon("terminal"))
         self.filesButton.setIcon(EricPixmapCache.getIcon("filemanager"))
         self.chartButton.setIcon(EricPixmapCache.getIcon("chart"))
         self.connectButton.setIcon(EricPixmapCache.getIcon("linkConnect"))
 
-        self.__zoomLayout = QHBoxLayout()
-        self.__osdLabel = QLabel()
-        self.__osdLabel.setSizePolicy(
-            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred
-        )
-        self.__zoomLayout.addWidget(self.__osdLabel)
-
-        self.__zoom0 = self.replEdit.fontPointSize()
-        self.__zoomWidget = EricZoomWidget(
-            EricPixmapCache.getPixmap("zoomOut"),
-            EricPixmapCache.getPixmap("zoomIn"),
-            EricPixmapCache.getPixmap("zoomReset"),
-            self,
-        )
-        self.__zoomLayout.addWidget(self.__zoomWidget)
-        self.layout().insertLayout(self.layout().count() - 1, self.__zoomLayout)
-        self.__zoomWidget.setMinimum(self.ZoomMin)
-        self.__zoomWidget.setMaximum(self.ZoomMax)
-        self.__zoomWidget.valueChanged.connect(self.__doZoom)
-        self.__currentZoom = 0
-
         self.__fileManager = None
         self.__fileManagerWidget = None
         self.__chartWidget = None
@@ -290,19 +132,16 @@
         self.__lastPort = None
         self.__lastDeviceType = None
 
-        if HAS_QTSERIALPORT:
-            self.__interface = MicroPythonDeviceInterface(self)
-        else:
-            self.__interface = None
+        self.__lastWebreplUrl = None
+
+        self.__interface = None
         self.__device = None
         self.__connected = False
         self.__linkConnected = False
         self.__setConnected(False)
 
-        self.__replBuffer = b""
-
         if not HAS_QTSERIALPORT:
-            self.replEdit.setHtml(
+            self.replWidget.replEdit().setHtml(
                 self.tr(
                     "<h3>The QtSerialPort package is not available.<br/>"
                     "MicroPython support is deactivated.</h3>"
@@ -311,27 +150,14 @@
             self.setEnabled(False)
             return
 
-        self.__vt100Re = re.compile(
-            r"(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])"
-        )
-
         self.__populateDeviceTypeComboBox()
 
-        self.replEdit.installEventFilter(self)
-        # Hack to intercept middle button paste
-        self.__origReplEditMouseReleaseEvent = self.replEdit.mouseReleaseEvent
-        self.replEdit.mouseReleaseEvent = self.__replEditMouseReleaseEvent
-
-        self.replEdit.customContextMenuRequested.connect(self.__showContextMenu)
+        self.repopulateButton.clicked.connect(self.__populateDeviceTypeComboBox)
+        self.webreplConfigButton.clicked.connect(self.__configureWebreplUrls)
         self.__ui.preferencesChanged.connect(self.__handlePreferencesChanged)
-        self.__ui.preferencesChanged.connect(self.__interface.handlePreferencesChanged)
 
         self.__handlePreferencesChanged()
 
-        charFormat = self.replEdit.currentCharFormat()
-        self.DefaultForeground = charFormat.foreground()
-        self.DefaultBackground = charFormat.background()
-
     def __populateDeviceTypeComboBox(self):
         """
         Private method to populate the device type selector.
@@ -345,7 +171,7 @@
         devices, unknownDevices, unknownPorts = Devices.getFoundDevices()
         if devices:
             supportedMessage = self.tr(
-                "%n supported device(s) detected.", "", len(devices)
+                "%n supported serial device(s) detected.", "", len(devices)
             )
 
             for index, (
@@ -376,9 +202,12 @@
                 self.deviceTypeComboBox.setItemData(
                     index, serialNumber, self.DeviceSerNoRole
                 )
+                self.deviceTypeComboBox.setItemData(
+                    index, "serial", self.DeviceInterfaceTypeRole
+                )
 
         else:
-            supportedMessage = self.tr("No supported devices detected.")
+            supportedMessage = self.tr("No supported serial devices detected.")
 
         self.__unknownPorts = unknownPorts
         if self.__unknownPorts:
@@ -398,7 +227,33 @@
         else:
             unknownMessage = ""
 
-        self.deviceInfoLabel.setText(supportedMessage + unknownMessage)
+        # add WebREPL entries
+        self.deviceTypeComboBox.insertSeparator(self.deviceTypeComboBox.count())
+        self.deviceTypeComboBox.addItem(self.tr("WebREPL (manual)"))
+        index = self.deviceTypeComboBox.count() - 1
+        self.deviceTypeComboBox.setItemData(
+            index, "webrepl", self.DeviceInterfaceTypeRole
+        )
+        webreplUrlsDict = Preferences.getMicroPython("WebreplUrls")
+        for name in sorted(webreplUrlsDict):
+            self.deviceTypeComboBox.addItem(webreplUrlsDict[name]["description"])
+            index = self.deviceTypeComboBox.count() - 1
+            self.deviceTypeComboBox.setItemData(
+                index, webreplUrlsDict[name]["device_type"], self.DeviceTypeRole
+            )
+            self.deviceTypeComboBox.setItemData(
+                index, "webrepl", self.DeviceInterfaceTypeRole
+            )
+            self.deviceTypeComboBox.setItemData(
+                index, webreplUrlsDict[name]["url"], self.DeviceWebreplUrlRole
+            )
+        webreplMessage = (
+            self.tr("\n%n WebREPL connection(s) defined.", "", len(webreplUrlsDict))
+            if webreplUrlsDict
+            else ""
+        )
+
+        self.deviceInfoLabel.setText(supportedMessage + unknownMessage + webreplMessage)
 
         index = self.deviceTypeComboBox.findText(
             currentDevice, Qt.MatchFlag.MatchExactly
@@ -467,20 +322,31 @@
         """
         Private slot to handle a change in preferences.
         """
-        self.__colorScheme = Preferences.getMicroPython("ColorScheme")
+        self.replWidget.replEdit().handlePreferencesChanged()
 
-        self.__font = Preferences.getEditorOtherFonts("MonospacedFont")
-        self.replEdit.setFontFamily(self.__font.family())
-        self.replEdit.setFontPointSize(self.__font.pointSize())
-
-        if Preferences.getMicroPython("ReplLineWrap"):
-            self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth)
-        else:
-            self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap)
+        if self.__interface is not None:
+            self.__interface.handlePreferencesChanged
 
         if self.__chartWidget is not None:
             self.__chartWidget.preferencesChanged()
 
+    @pyqtSlot()
+    def __configureWebreplUrls(self):
+        """
+        Private slot to configure the list of selectable WebREPL URLs.
+        """
+        from .MicroPythonWebreplUrlsConfigDialog import (
+            MicroPythonWebreplUrlsConfigDialog,
+        )
+
+        webreplUrlsDict = Preferences.getMicroPython("WebreplUrls")
+        dlg = MicroPythonWebreplUrlsConfigDialog(webreplUrlsDict)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            webreplUrlsDict = dlg.getWebreplDict()
+            Preferences.setMicroPython("WebreplUrls", webreplUrlsDict)
+
+            self.__populateDeviceTypeComboBox()
+
     def deviceInterface(self):
         """
         Public method to get a reference to the device interface object.
@@ -529,7 +395,7 @@
             pid = self.deviceTypeComboBox.itemData(index, self.DevicePidRole)
             serNo = self.deviceTypeComboBox.itemData(index, self.DeviceSerNoRole)
 
-            if deviceType or (pid is not None and pid is not None):
+            if deviceType or (vid is not None and pid is not None):
                 deviceWorkspace = (
                     self.__device.getWorkspace() if self.__device is not None else None
                 )
@@ -544,13 +410,6 @@
             else:
                 self.__device = None
 
-    @pyqtSlot()
-    def on_checkButton_clicked(self):
-        """
-        Private slot to check for connected devices.
-        """
-        self.__populateDeviceTypeComboBox()
-
     def setActionButtons(self, **kwargs):
         """
         Public method to set the enabled state of the various action buttons.
@@ -570,50 +429,6 @@
                 kwargs["chart"] and HAS_QTCHART and self.__connected
             )
 
-    @pyqtSlot(QPoint)
-    def __showContextMenu(self, pos):
-        """
-        Private slot to show the REPL context menu.
-
-        @param pos position to show the menu at
-        @type QPoint
-        """
-        if OSUtilities.isMacPlatform():
-            copyKeys = QKeySequence("Ctrl+C")
-            pasteKeys = QKeySequence("Ctrl+V")
-            selectAllKeys = QKeySequence("Ctrl+A")
-        else:
-            copyKeys = QKeySequence("Ctrl+Shift+C")
-            pasteKeys = QKeySequence("Ctrl+Shift+V")
-            selectAllKeys = QKeySequence("Ctrl+Shift+A")
-
-        menu = QMenu(self)
-        menu.addAction(
-            EricPixmapCache.getIcon("editDelete"), self.tr("Clear"), self.__clear
-        ).setEnabled(bool(self.replEdit.toPlainText()))
-        menu.addSeparator()
-        menu.addAction(
-            EricPixmapCache.getIcon("editCopy"),
-            self.tr("Copy"),
-            copyKeys,
-            self.replEdit.copy,
-        ).setEnabled(self.replEdit.textCursor().hasSelection())
-        menu.addAction(
-            EricPixmapCache.getIcon("editPaste"),
-            self.tr("Paste"),
-            pasteKeys,
-            self.__paste,
-        ).setEnabled(self.replEdit.canPaste() and self.__connected)
-        menu.addSeparator()
-        menu.addAction(
-            EricPixmapCache.getIcon("editSelectAll"),
-            self.tr("Select All"),
-            selectAllKeys,
-            self.replEdit.selectAll,
-        ).setEnabled(bool(self.replEdit.toPlainText()))
-
-        menu.exec(self.replEdit.mapToGlobal(pos))
-
     def __setConnected(self, connected):
         """
         Private method to set the connection status LED.
@@ -622,7 +437,7 @@
         @type bool
         """
         self.__connected = connected
-        self.__linkConnected = self.__interface.isConnected()
+        self.__linkConnected = bool(self.__interface) and self.__interface.isConnected()
 
         self.deviceConnectedLed.setOn(self.__linkConnected)
         if self.__fileManagerWidget:
@@ -708,8 +523,10 @@
                 )
                 return
 
-            self.replEdit.clear()
-            self.__interface.dataReceived.connect(self.__processData)
+            self.replWidget.replEdit().clear()
+            self.__interface.dataReceived.connect(
+                self.replWidget.replEdit().processData
+            )
 
             if not self.__interface.isConnected():
                 self.__connectToDevice()
@@ -720,10 +537,13 @@
                     self.__interface.write(b"\x03")
 
             self.__device.setRepl(True)
-            self.replEdit.setFocus(Qt.FocusReason.OtherFocusReason)
+            self.replWidget.replEdit().setFocus(Qt.FocusReason.OtherFocusReason)
         else:
             with contextlib.suppress(TypeError):
-                self.__interface.dataReceived.disconnect(self.__processData)
+                if self.__interface is not None:
+                    self.__interface.dataReceived.disconnect(
+                        self.replWidget.replEdit().processData
+                    )
             if not self.chartButton.isChecked() and not self.filesButton.isChecked():
                 self.__disconnectFromDevice()
             self.__device.setRepl(False)
@@ -735,7 +555,7 @@
         Private slot to connect to the selected device or disconnect from the
         currently connected device.
         """
-        self.__osdLabel.clear()
+        self.replWidget.clearOSD()
         if self.__linkConnected:
             with EricOverrideCursor():
                 self.__disconnectFromDevice()
@@ -750,372 +570,6 @@
             with EricOverrideCursor():
                 self.__connectToDevice(withAutostart=True)
 
-    @pyqtSlot()
-    def __clear(self):
-        """
-        Private slot to clear the REPL pane.
-        """
-        self.replEdit.clear()
-        self.__interface.isConnected() and self.__interface.write(b"\r")
-
-    @pyqtSlot()
-    def __paste(self, mode=QClipboard.Mode.Clipboard):
-        """
-        Private slot to perform a paste operation.
-
-        @param mode paste mode (defaults to QClipboard.Mode.Clipboard)
-        @type QClipboard.Mode (optional)
-        """
-        # add support for paste by mouse middle button
-        clipboard = QApplication.clipboard()
-        if clipboard:
-            pasteText = clipboard.text(mode=mode)
-            if pasteText:
-                pasteText = pasteText.replace("\n\r", "\r")
-                pasteText = pasteText.replace("\n", "\r")
-                if self.__interface.isConnected():
-                    self.__interface.write(b"\x05")
-                    self.__interface.write(pasteText.encode("utf-8"))
-                    self.__interface.write(b"\x04")
-
-    def eventFilter(self, obj, evt):
-        """
-        Public method to process events for the REPL pane.
-
-        @param obj reference to the object the event was meant for
-        @type QObject
-        @param evt reference to the event object
-        @type QEvent
-        @return flag to indicate that the event was handled
-        @rtype bool
-        """
-        if obj is self.replEdit and evt.type() == QEvent.Type.KeyPress:
-            # handle the key press event on behalf of the REPL pane
-            key = evt.key()
-            msg = bytes(evt.text(), "utf8")
-            if key == Qt.Key.Key_Backspace:
-                msg = b"\b"
-            elif key == Qt.Key.Key_Delete:
-                msg = b"\x1B[\x33\x7E"
-            elif key == Qt.Key.Key_Up:
-                msg = b"\x1B[A"
-            elif key == Qt.Key.Key_Down:
-                msg = b"\x1B[B"
-            elif key == Qt.Key.Key_Right:
-                msg = b"\x1B[C"
-            elif key == Qt.Key.Key_Left:
-                msg = b"\x1B[D"
-            elif key == Qt.Key.Key_Home:
-                msg = b"\x1B[H"
-            elif key == Qt.Key.Key_End:
-                msg = b"\x1B[F"
-            elif (
-                OSUtilities.isMacPlatform()
-                and evt.modifiers() == Qt.KeyboardModifier.MetaModifier
-            ) or (
-                not OSUtilities.isMacPlatform()
-                and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
-            ):
-                if Qt.Key.Key_A <= key <= Qt.Key.Key_Z:
-                    # devices treat an input of \x01 as Ctrl+A, etc.
-                    msg = bytes([1 + key - Qt.Key.Key_A])
-            elif evt.modifiers() == (
-                Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier
-            ) or (
-                OSUtilities.isMacPlatform()
-                and evt.modifiers() == Qt.KeyboardModifier.ControlModifier
-            ):
-                if key == Qt.Key.Key_C:
-                    self.replEdit.copy()
-                    msg = b""
-                elif key == Qt.Key.Key_V:
-                    self.__paste()
-                    msg = b""
-                elif key == Qt.Key.Key_A:
-                    self.replEdit.selectAll()
-                    msg = b""
-            elif key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
-                tc = self.replEdit.textCursor()
-                tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
-                self.replEdit.setTextCursor(tc)
-            self.__interface.isConnected() and self.__interface.write(msg)
-            return True
-        else:
-            # standard event processing
-            return super().eventFilter(obj, evt)
-
-    def __replEditMouseReleaseEvent(self, evt):
-        """
-        Private method handling mouse release events for the replEdit widget.
-
-        Note: this is a hack because QTextEdit does not allow filtering of
-        QEvent.Type.MouseButtonRelease. To make middle button paste work, we
-        had to intercept the protected event method (some kind of
-        reimplementing it).
-
-        @param evt reference to the event object
-        @type QMouseEvent
-        """
-        if evt.button() == Qt.MouseButton.MiddleButton:
-            self.__paste(mode=QClipboard.Mode.Selection)
-            msg = b""
-            if self.__interface.isConnected():
-                self.__interface.write(msg)
-            evt.accept()
-        else:
-            self.__origReplEditMouseReleaseEvent(evt)
-
-    def __processData(self, data):
-        """
-        Private slot to process bytes received from the device.
-
-        @param data bytes received from the device
-        @type bytes
-        """
-        tc = self.replEdit.textCursor()
-        # the text cursor must be on the last line
-        while tc.movePosition(QTextCursor.MoveOperation.Down):
-            pass
-
-        # set the font
-        charFormat = tc.charFormat()
-        charFormat.setFontFamilies([self.__font.family()])
-        charFormat.setFontPointSize(self.__font.pointSize())
-        tc.setCharFormat(charFormat)
-
-        # add received data to the buffered one
-        data = self.__replBuffer + data
-
-        index = 0
-        while index < len(data):
-            if data[index] == 8:  # \b
-                tc.movePosition(QTextCursor.MoveOperation.Left)
-                self.replEdit.setTextCursor(tc)
-            elif data[index] in (4, 13):  # EOT, \r
-                pass
-            elif len(data) > index + 1 and data[index] == 27 and data[index + 1] == 91:
-                # VT100 cursor command detected: <Esc>[
-                index += 2  # move index to after the [
-                match = self.__vt100Re.search(
-                    data[index:].decode("utf-8", errors="replace")
-                )
-                if match:
-                    # move to last position in control sequence
-                    # ++ will be done at end of loop
-                    index += match.end() - 1
-
-                    action = match.group("action")
-                    if action in "ABCD":
-                        if match.group("count") == "":
-                            count = 1
-                        else:
-                            count = int(match.group("count"))
-
-                        if action == "A":  # up
-                            tc.movePosition(QTextCursor.MoveOperation.Up, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "B":  # down
-                            tc.movePosition(QTextCursor.MoveOperation.Down, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "C":  # right
-                            tc.movePosition(QTextCursor.MoveOperation.Right, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "D":  # left
-                            tc.movePosition(QTextCursor.MoveOperation.Left, n=count)
-                            self.replEdit.setTextCursor(tc)
-                    elif action == "K":  # delete things
-                        if match.group("count") in ("", "0"):
-                            # delete to end of line
-                            tc.movePosition(
-                                QTextCursor.MoveOperation.EndOfLine,
-                                mode=QTextCursor.MoveMode.KeepAnchor,
-                            )
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                        elif match.group("count") == "1":
-                            # delete to beginning of line
-                            tc.movePosition(
-                                QTextCursor.MoveOperation.StartOfLine,
-                                mode=QTextCursor.MoveMode.KeepAnchor,
-                            )
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                        elif match.group("count") == "2":
-                            # delete whole line
-                            tc.movePosition(QTextCursor.MoveOperation.EndOfLine)
-                            tc.movePosition(
-                                QTextCursor.MoveOperation.StartOfLine,
-                                mode=QTextCursor.MoveMode.KeepAnchor,
-                            )
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                    elif action == "m":
-                        self.__setCharFormat(match.group(0)[:-1].split(";"), tc)
-            elif (
-                len(data) > index + 1
-                and data[index] == 27
-                and data[index + 1 : index + 4] == b"]0;"
-            ):
-                if b"\x1b\\" in data[index + 4 :]:
-                    # 'set window title' command detected: <Esc>]0;...<Esc>\
-                    # __IGNORE_WARNING_M891__
-                    titleData = data[index + 4 :].split(b"\x1b\\")[0]
-                    title = titleData.decode()
-                    index += len(titleData) + 5  # one more is done at the end
-                    self.__osdLabel.setText(title)
-                else:
-                    # data is incomplete; buffer and stop processing
-                    self.__replBuffer = data[index:]
-                    return
-            else:
-                tc.deleteChar()
-                self.replEdit.setTextCursor(tc)
-                self.replEdit.insertPlainText(chr(data[index]))
-
-            index += 1
-
-        self.replEdit.ensureCursorVisible()
-        self.__replBuffer = b""
-
-    def __setCharFormat(self, formatCodes, textCursor):
-        """
-        Private method setting the current text format of the REPL pane based
-        on the passed ANSI codes.
-
-        Following codes are used:
-        <ul>
-        <li>0: Reset</li>
-        <li>1: Bold font (weight 75)</li>
-        <li>2: Light font (weight 25)</li>
-        <li>3: Italic font</li>
-        <li>4: Underlined font</li>
-        <li>9: Strikeout font</li>
-        <li>21: Bold off (weight 50)</li>
-        <li>22: Light off (weight 50)</li>
-        <li>23: Italic off</li>
-        <li>24: Underline off</li>
-        <li>29: Strikeout off</li>
-        <li>30: foreground Black</li>
-        <li>31: foreground Dark Red</li>
-        <li>32: foreground Dark Green</li>
-        <li>33: foreground Dark Yellow</li>
-        <li>34: foreground Dark Blue</li>
-        <li>35: foreground Dark Magenta</li>
-        <li>36: foreground Dark Cyan</li>
-        <li>37: foreground Light Gray</li>
-        <li>39: reset foreground to default</li>
-        <li>40: background Black</li>
-        <li>41: background Dark Red</li>
-        <li>42: background Dark Green</li>
-        <li>43: background Dark Yellow</li>
-        <li>44: background Dark Blue</li>
-        <li>45: background Dark Magenta</li>
-        <li>46: background Dark Cyan</li>
-        <li>47: background Light Gray</li>
-        <li>49: reset background to default</li>
-        <li>53: Overlined font</li>
-        <li>55: Overline off</li>
-        <li>90: bright foreground Dark Gray</li>
-        <li>91: bright foreground Red</li>
-        <li>92: bright foreground Green</li>
-        <li>93: bright foreground Yellow</li>
-        <li>94: bright foreground Blue</li>
-        <li>95: bright foreground Magenta</li>
-        <li>96: bright foreground Cyan</li>
-        <li>97: bright foreground White</li>
-        <li>100: bright background Dark Gray</li>
-        <li>101: bright background Red</li>
-        <li>102: bright background Green</li>
-        <li>103: bright background Yellow</li>
-        <li>104: bright background Blue</li>
-        <li>105: bright background Magenta</li>
-        <li>106: bright background Cyan</li>
-        <li>107: bright background White</li>
-        </ul>
-
-        @param formatCodes list of format codes
-        @type list of str
-        @param textCursor reference to the text cursor
-        @type QTextCursor
-        """
-        if not formatCodes:
-            # empty format codes list is treated as a reset
-            formatCodes = ["0"]
-
-        charFormat = textCursor.charFormat()
-        for formatCode in formatCodes:
-            try:
-                formatCode = int(formatCode)
-            except ValueError:
-                # ignore non digit values
-                continue
-
-            if formatCode == 0:
-                charFormat.setFontWeight(50)
-                charFormat.setFontItalic(False)
-                charFormat.setFontUnderline(False)
-                charFormat.setFontStrikeOut(False)
-                charFormat.setFontOverline(False)
-                charFormat.setForeground(self.DefaultForeground)
-                charFormat.setBackground(self.DefaultBackground)
-            elif formatCode == 1:
-                charFormat.setFontWeight(75)
-            elif formatCode == 2:
-                charFormat.setFontWeight(25)
-            elif formatCode == 3:
-                charFormat.setFontItalic(True)
-            elif formatCode == 4:
-                charFormat.setFontUnderline(True)
-            elif formatCode == 9:
-                charFormat.setFontStrikeOut(True)
-            elif formatCode in (21, 22):
-                charFormat.setFontWeight(50)
-            elif formatCode == 23:
-                charFormat.setFontItalic(False)
-            elif formatCode == 24:
-                charFormat.setFontUnderline(False)
-            elif formatCode == 29:
-                charFormat.setFontStrikeOut(False)
-            elif formatCode == 53:
-                charFormat.setFontOverline(True)
-            elif formatCode == 55:
-                charFormat.setFontOverline(False)
-            elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
-                charFormat.setForeground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 30]
-                )
-            elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
-                charFormat.setBackground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 40]
-                )
-            elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
-                charFormat.setForeground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 80]
-                )
-            elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
-                charFormat.setBackground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 90]
-                )
-            elif formatCode == 39:
-                charFormat.setForeground(self.DefaultForeground)
-            elif formatCode == 49:
-                charFormat.setBackground(self.DefaultBackground)
-
-        textCursor.setCharFormat(charFormat)
-
-    def __doZoom(self, value):
-        """
-        Private slot to zoom the REPL pane.
-
-        @param value zoom value
-        @type int
-        """
-        if value < self.__currentZoom:
-            self.replEdit.zoomOut(self.__currentZoom - value)
-        elif value > self.__currentZoom:
-            self.replEdit.zoomIn(value - self.__currentZoom)
-        self.__currentZoom = value
-
     def getCurrentPort(self):
         """
         Public method to determine the port path of the selected device.
@@ -1134,16 +588,6 @@
         else:
             return ""
 
-    def getCurrentBoard(self):
-        """
-        Public method to get the board name of the selected device.
-
-        @return board name of the selected device
-        @rtype str
-        """
-        boardName = self.deviceTypeComboBox.currentData(self.DeviceBoardRole)
-        return boardName
-
     def getDevice(self):
         """
         Public method to get a reference to the current device.
@@ -1182,27 +626,67 @@
         @param withAutostart flag indicating to start the repl and file manager
             automatically
         @type bool
+        @exception ValueError raised to indicate an unsupported interface type
         """
         from .ConnectionSelectionDialog import ConnectionSelectionDialog
+        from .MicroPythonWebreplConnectionDialog import (
+            MicroPythonWebreplConnectionDialog,
+        )
 
-        port = self.getCurrentPort()
-        if not port:
-            with EricOverridenCursor():
-                dlg = ConnectionSelectionDialog(
-                    self.__unknownPorts, self.__lastPort, self.__lastDeviceType
-                )
-                if dlg.exec() == QDialog.DialogCode.Accepted:
-                    vid, pid, port, deviceType = dlg.getData()
+        interfaceType = (
+            self.deviceTypeComboBox.currentData(self.DeviceInterfaceTypeRole)
+            or "serial"
+        )  # 'serial' is the default
+
+        if interfaceType not in ("serial", "webrepl"):
+            raise ValueError(
+                "Unsupported interface type detected ('{0}')".format(interfaceType)
+            )
+
+        if interfaceType == "serial":
+            port = self.getCurrentPort()
+            if not port:
+                with EricOverridenCursor():
+                    dlg = ConnectionSelectionDialog(
+                        self.__unknownPorts, self.__lastPort, self.__lastDeviceType
+                    )
+                    if dlg.exec() == QDialog.DialogCode.Accepted:
+                        vid, pid, port, deviceType = dlg.getData()
+
+                        self.deviceIconLabel.setPixmap(
+                            Devices.getDeviceIcon(deviceType, False)
+                        )
+                        self.__device = Devices.getDevice(deviceType, self, vid, pid)
 
-                    self.deviceIconLabel.setPixmap(
-                        Devices.getDeviceIcon(deviceType, False)
-                    )
-                    self.__device = Devices.getDevice(deviceType, self, vid, pid)
+                        self.__lastPort = port
+                        self.__lastDeviceType = deviceType
+                    else:
+                        return
 
-                    self.__lastPort = port
-                    self.__lastDeviceType = deviceType
-                else:
-                    return
+            self.__interface = MicroPythonSerialDeviceInterface(self)
+        elif interfaceType == "webrepl":
+            port = self.deviceTypeComboBox.currentData(self.DeviceWebreplUrlRole)
+            if not port:
+                with EricOverridenCursor():
+                    dlg = MicroPythonWebreplConnectionDialog(
+                        self.__lastWebreplUrl, self.__lastDeviceType
+                    )
+                    if dlg.exec() == QDialog.DialogCode.Accepted:
+                        port, deviceType = dlg.getWebreplConnectionParameters()
+
+                        self.deviceIconLabel.setPixmap(
+                            Devices.getDeviceIcon(deviceType, False)
+                        )
+                        self.__device = Devices.getDevice(deviceType, self, None, None)
+
+                        self.__lastWebreplUrl = port
+                        self.__lastDeviceType = deviceType
+                    else:
+                        return
+
+            self.__interface = MicroPythonWebreplDeviceInterface(self)
+        self.replWidget.replEdit().setInterface(self.__interface)
+        self.__interface.osdInfo.connect(self.replWidget.setOSDInfo)
 
         if self.__interface.connectToDevice(port):
             deviceResponding = self.__interface.probeDevice()
@@ -1250,9 +734,14 @@
         Private method to disconnect from the device.
         """
         self.__device and self.__device.setConnected(False)
-        self.__interface.disconnectFromDevice()
         self.__setConnected(False)
 
+        if self.__interface is not None:
+            self.__interface.disconnectFromDevice()
+            self.__interface.deleteLater()
+            self.__interface = None
+            self.replWidget.replEdit().setInterface(None)
+
     @pyqtSlot()
     def on_runButton_clicked(self):
         """
--- a/src/eric7/MicroPython/MicroPythonWidget.ui	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/MicroPython/MicroPythonWidget.ui	Thu May 04 17:31:13 2023 +0200
@@ -10,6 +10,12 @@
     <height>548</height>
    </rect>
   </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
@@ -34,21 +40,15 @@
      </item>
      <item>
       <layout class="QGridLayout" name="gridLayout">
-       <item row="1" column="0" colspan="4">
-        <widget class="QLabel" name="deviceInfoLabel">
-         <property name="sizePolicy">
-          <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-           <horstretch>0</horstretch>
-           <verstretch>0</verstretch>
-          </sizepolicy>
-         </property>
-         <property name="wordWrap">
-          <bool>true</bool>
+       <item row="0" column="1">
+        <widget class="QToolButton" name="repopulateButton">
+         <property name="toolTip">
+          <string>Press to detect connected devices and repopulate the device selector.</string>
          </property>
         </widget>
        </item>
        <item row="0" column="3">
-        <widget class="EricLed" name="deviceConnectedLed" native="true"/>
+        <widget class="EricToolButton" name="menuButton"/>
        </item>
        <item row="0" column="0">
         <widget class="QComboBox" name="deviceTypeComboBox">
@@ -63,15 +63,28 @@
          </property>
         </widget>
        </item>
-       <item row="0" column="1">
-        <widget class="QToolButton" name="checkButton">
-         <property name="toolTip">
-          <string>Press to check for connected devices</string>
+       <item row="1" column="0" colspan="5">
+        <widget class="QLabel" name="deviceInfoLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
          </property>
         </widget>
        </item>
+       <item row="0" column="4">
+        <widget class="EricLed" name="deviceConnectedLed" native="true"/>
+       </item>
        <item row="0" column="2">
-        <widget class="EricToolButton" name="menuButton"/>
+        <widget class="QToolButton" name="webreplConfigButton">
+         <property name="toolTip">
+          <string>Press to edit the list of configured WebREPL connections.</string>
+         </property>
+        </widget>
        </item>
       </layout>
      </item>
@@ -139,18 +152,15 @@
     </layout>
    </item>
    <item>
-    <widget class="QTextEdit" name="replEdit">
-     <property name="contextMenuPolicy">
-      <enum>Qt::CustomContextMenu</enum>
+    <widget class="MicroPythonReplWidget" name="replWidget" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
      </property>
-     <property name="undoRedoEnabled">
-      <bool>false</bool>
-     </property>
-     <property name="lineWrapMode">
-      <enum>QTextEdit::NoWrap</enum>
-     </property>
-     <property name="acceptRichText">
-      <bool>false</bool>
+     <property name="focusPolicy">
+      <enum>Qt::StrongFocus</enum>
      </property>
     </widget>
    </item>
@@ -168,17 +178,24 @@
    <header>eric7/EricWidgets/EricLed.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>MicroPythonReplWidget</class>
+   <extends>QWidget</extends>
+   <header>eric7/MicroPython/MicroPythonReplWidget.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <tabstops>
   <tabstop>deviceTypeComboBox</tabstop>
-  <tabstop>checkButton</tabstop>
+  <tabstop>repopulateButton</tabstop>
+  <tabstop>webreplConfigButton</tabstop>
   <tabstop>menuButton</tabstop>
   <tabstop>runButton</tabstop>
   <tabstop>replButton</tabstop>
   <tabstop>filesButton</tabstop>
   <tabstop>chartButton</tabstop>
   <tabstop>connectButton</tabstop>
-  <tabstop>replEdit</tabstop>
+  <tabstop>replWidget</tabstop>
  </tabstops>
  <resources/>
  <connections/>
--- a/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.py	Thu May 04 17:31:13 2023 +0200
@@ -16,7 +16,7 @@
 from eric7.EricGui import EricPixmapCache
 from eric7.EricWidgets.EricApplication import ericApp
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
-from eric7.MicroPython.MicroPythonWidget import AnsiColorSchemes
+from eric7.MicroPython.MicroPythonReplWidget import AnsiColorSchemes
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
 
 from .ConfigurationPageBase import ConfigurationPageBase
@@ -115,11 +115,15 @@
             Preferences.getMicroPython("EnableManualDeviceSelection")
         )
 
-        # serial link parameters
-        self.timeoutSpinBox.setValue(
+        # device communication
+        self.serialTimeoutSpinBox.setValue(
             Preferences.getMicroPython("SerialTimeout") // 1000
-        )
-        # converted to seconds
+        )  # converted to seconds
+        self.webreplTimeoutSpinBox.setValue(
+            Preferences.getMicroPython("WebreplTimeout") // 1000
+        )  # converted to seconds
+
+        # device time handling
         self.syncTimeCheckBox.setChecked(
             Preferences.getMicroPython("SyncTimeAfterConnect")
         )
@@ -227,9 +231,15 @@
             "EnableManualDeviceSelection", self.manualSelectionCheckBox.isChecked()
         )
 
-        # serial link parameters
-        Preferences.setMicroPython("SerialTimeout", self.timeoutSpinBox.value() * 1000)
-        # converted to milliseconds
+        # device communication
+        Preferences.setMicroPython(
+            "SerialTimeout", self.serialTimeoutSpinBox.value() * 1000
+        ) # converted to milliseconds
+        Preferences.setMicroPython(
+            "WebreplTimeout", self.webreplTimeoutSpinBox.value() * 1000
+        ) # converted to milliseconds
+
+        # device time handling
         Preferences.setMicroPython(
             "SyncTimeAfterConnect", self.syncTimeCheckBox.isChecked()
         )
--- a/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.ui	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Preferences/ConfigurationPages/MicroPythonPage.ui	Thu May 04 17:31:13 2023 +0200
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>541</width>
-    <height>2038</height>
+    <height>2150</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout_3">
@@ -78,18 +78,18 @@
    <item>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="title">
-      <string>Serial Link</string>
+      <string>Device Communication</string>
      </property>
      <layout class="QGridLayout" name="gridLayout_2">
       <item row="0" column="0">
        <widget class="QLabel" name="label_2">
         <property name="text">
-         <string>Timeout for Serial Link Communication:</string>
+         <string>Serial Link Timeout:</string>
         </property>
        </widget>
       </item>
       <item row="0" column="1">
-       <widget class="QSpinBox" name="timeoutSpinBox">
+       <widget class="QSpinBox" name="serialTimeoutSpinBox">
         <property name="toolTip">
          <string>Enter the timout value</string>
         </property>
@@ -120,7 +120,42 @@
         </property>
        </spacer>
       </item>
-      <item row="1" column="0" colspan="3">
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_33">
+        <property name="text">
+         <string>WebRepl Timeout:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QSpinBox" name="webreplTimeoutSpinBox">
+        <property name="toolTip">
+         <string>Enter the timout value</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+        <property name="suffix">
+         <string> s</string>
+        </property>
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>30</number>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_19">
+     <property name="title">
+      <string>Device Time</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_9">
+      <item>
        <widget class="QCheckBox" name="syncTimeCheckBox">
         <property name="sizePolicy">
          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -1004,7 +1039,8 @@
  <tabstops>
   <tabstop>workspacePicker</tabstop>
   <tabstop>manualSelectionCheckBox</tabstop>
-  <tabstop>timeoutSpinBox</tabstop>
+  <tabstop>serialTimeoutSpinBox</tabstop>
+  <tabstop>webreplTimeoutSpinBox</tabstop>
   <tabstop>syncTimeCheckBox</tabstop>
   <tabstop>colorSchemeComboBox</tabstop>
   <tabstop>replWrapCheckBox</tabstop>
--- a/src/eric7/Preferences/__init__.py	Tue May 02 11:39:09 2023 +0200
+++ b/src/eric7/Preferences/__init__.py	Thu May 04 17:31:13 2023 +0200
@@ -1570,7 +1570,9 @@
     # defaults for MicroPython
     microPythonDefaults = {
         "MpyWorkspace": "",
-        "SerialTimeout": 2000,  # timeout in milliseconds
+        "SerialTimeout": 2000,  # timeout in milliseconds for serial connections
+        "WebreplTimeout": 5000,  # timeout in milliseconds for webrepl connections
+        "WebreplUrls": "{}",  # empty dict of WebREPL URLs as JSON
         "ReplLineWrap": True,  # wrap the REPL lines
         "SyncTimeAfterConnect": True,
         "ShowHiddenLocal": True,
@@ -3806,6 +3808,7 @@
     """
     if key in (
         "SerialTimeout",
+        "WebreplTimeout",
         "ChartColorTheme",
         "WifiApAuthMode",
         "NtpOffset",
@@ -3825,7 +3828,7 @@
         return toBool(
             Prefs.settings.value("MicroPython/" + key, Prefs.microPythonDefaults[key])
         )
-    elif key in ["IgnoredUnknownDevices", "ManualDevices"]:
+    elif key in ["IgnoredUnknownDevices", "ManualDevices", "WebreplUrls"]:
         jsonStr = Prefs.settings.value(
             "MicroPython/" + key, Prefs.microPythonDefaults[key]
         )
@@ -3851,7 +3854,7 @@
     @param key the key of the setting to be set
     @param value the value to be set
     """
-    if key in ["IgnoredUnknownDevices", "ManualDevices"]:
+    if key in ["IgnoredUnknownDevices", "ManualDevices", "WebreplUrls"]:
         Prefs.settings.setValue("MicroPython/" + key, json.dumps(value))
     elif key in ("WifiPassword", "WifiApPassword"):
         Prefs.settings.setValue("MicroPython/" + key, pwConvert(value, encode=True))

eric ide

mercurial