Thu, 04 May 2023 17:31:13 +0200
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
--- 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 "<unsupported> <unsupported>" @@ -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><b>Note:</b> 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))