Mon, 27 Feb 2023 17:43:11 +0100
MicroPython
- did some corrections to the device interface modules
--- a/src/eric7/MicroPython/Devices/CircuitPythonDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/CircuitPythonDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -7,11 +7,13 @@ Module implementing the device interface class for CircuitPython boards. """ +import ast +import json import os import shutil -from PyQt6.QtCore import QProcess, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtCore import QUrl, pyqtSlot +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QMenu from eric7 import Globals, Preferences @@ -69,6 +71,9 @@ if not connected and self.__libraryMenu.isTearOffMenuVisible(): self.__libraryMenu.hideTearOffMenu() + if self.__flashMenu.isTearOffMenuVisible(): + self.__flashMenu.hideTearOffMenu() + super().setConnected(connected) def setButtons(self): @@ -262,33 +267,14 @@ self.__libraryMenu.aboutToShow.connect(self.__aboutToShowLibraryMenu) self.__libraryMenu.setTearOffEnabled(True) + self.__flashMenu = self.__createFlashMenus() + self.__cpyMenu = QMenu(self.tr("CircuitPython Functions")) - self.__cpyMenu.addAction( self.tr("Show CircuitPython Versions"), self.__showCircuitPythonVersions ) self.__cpyMenu.addSeparator() - - boardName = self.microPython.getCurrentBoard() - lBoardName = boardName.lower() if boardName else "" - if "teensy" in lBoardName: - # Teensy 4.0 and 4.1 don't support UF2 flashing - self.__cpyMenu.addAction( - self.tr("CircuitPython Flash Instructions"), - self.__showTeensyFlashInstructions, - ) - self.__flashCpyAct = self.__cpyMenu.addAction( - self.tr("Flash CircuitPython Firmware"), self.__startTeensyLoader - ) - self.__flashCpyAct.setToolTip( - self.tr( - "Start the 'Teensy Loader' application to flash the Teensy device." - ) - ) - else: - self.__flashCpyAct = self.__cpyMenu.addAction( - self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython - ) + self.__flashCpyAct = self.__cpyMenu.addMenu(self.__flashMenu) self.__cpyMenu.addSeparator() self.__cpyMenu.addMenu(self.__libraryMenu) self.__cpyMenu.addSeparator() @@ -296,6 +282,48 @@ self.tr("Reset Device"), self.__resetDevice ) + def __createFlashMenus(self): + """ + Private method to create the various menus to flash a CircuitPython firmware. + + @return reference to the created top level flash menu + @rtype QMenu + """ + menu = QMenu(self.tr("Flash CircuitPython Firmware")) + menu.setTearOffEnabled(True) + + # UF2 devices + menu.addAction(self.tr("UF2 Device"), self.__flashCircuitPython) + menu.addSeparator() + + # ESP32 specific submenu + self.__esp32FlashMenu = QMenu(self.tr("ESP32 Device")) + self.__esp32FlashMenu.addAction(self.tr("Erase Flash"), self.__esp32EraseFlash) + self.__esp32FlashMenu.addAction( + self.tr("Flash MicroPython Firmware"), self.__esp32FlashPython + ) + self.__esp32FlashMenu.addSeparator() + self.__esp32FlashMenu.addAction( + self.tr("Flash Additional Firmware"), self.__esp32FlashAddons + ) + menu.addMenu(self.__esp32FlashMenu) + + # Teensy 4.0 and 4.1 specific submenu + self.__teensyFlashMenu = QMenu(self.tr("Teensy Device")) + self.__teensyFlashMenu.addAction( + self.tr("CircuitPython Flash Instructions"), + self.__showTeensyFlashInstructions, + ) + act = self.__teensyFlashMenu.addAction( + self.tr("Start 'Teensy Loader'"), self.__startTeensyLoader + ) + act.setToolTip( + self.tr("Start the 'Teensy Loader' application to flash the Teensy device.") + ) + menu.addMenu(self.__teensyFlashMenu) + + return menu + def addDeviceMenuEntries(self, menu): """ Public method to add device specific entries to the given menu. @@ -365,41 +393,54 @@ dlg = UF2FlashDialog(boardType="circuitpython") dlg.exec() + @pyqtSlot() def __showTeensyFlashInstructions(self): """ - Private method to show a message box because Teensy does not support + Private slot to show a message box because Teensy does not support the UF2 bootloader yet. """ - EricMessageBox.information( - self.microPython, - self.tr("Flash CircuitPython Firmware"), - self.tr( - """<p>Teensy 4.0 and Teensy 4.1 do not support the UF2""" - """ bootloader. Please use the 'Teensy Loader'""" - """ application to flash CircuitPython. Make sure you""" - """ downloaded the CircuitPython .hex file.</p>""" - """<p>See <a href="{0}">the PJRC Teensy web site</a>""" - """ for details.</p>""" - ).format("https://www.pjrc.com/teensy/loader.html"), - ) + from .TeensyDevices import showTeensyFlashInstructions + showTeensyFlashInstructions() + + @pyqtSlot() def __startTeensyLoader(self): """ - Private method to start the 'Teensy Loader' application. + Private slot to start the 'Teensy Loader' application. Note: The application must be accessible via the application search path. """ - ok, _ = QProcess.startDetached("teensy") - if not ok: - EricMessageBox.warning( - self.microPython, - self.tr("Start 'Teensy Loader'"), - self.tr( - """<p>The 'Teensy Loader' application <b>teensy</b> could not""" - """ be started. Ensure it is in the application search path or""" - """ start it manually.</p>""" - ), - ) + from .TeensyDevices import startTeensyLoader + + startTeensyLoader() + + @pyqtSlot() + def __esp32EraseFlash(self): + """ + Private slot to erase the flash of an ESP32 device. + """ + from .EspDevices import eraseFlash + + eraseFlash(self.microPython.getCurrentPort()) + + @pyqtSlot() + def __esp32FlashPython(self): + """ + Private slot to flash a MicroPython or CircuitPython firmware to an ESP32 + device. + """ + from .EspDevices import flashPythonFirmware + + flashPythonFirmware(self.microPython.getCurrentPort()) + + @pyqtSlot() + def __esp32FlashAddons(self): + """ + Private slot to flash additional firmware to an ESP32 device. + """ + from .EspDevices import flashAddonFirmware + + flashAddonFirmware(self.microPython.getCurrentPort()) @pyqtSlot() def __showCircuitPythonVersions(self): @@ -412,9 +453,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__cpyVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __cpyVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply @@ -543,6 +585,104 @@ ), ] + ################################################################## + ## Methods below implement WiFi related methods + ################################################################## + + def hasWifi(self): + """ + Public method to check the availability of WiFi. + + @return tuple containing a flag indicating the availability of WiFi + and the WiFi type (picow or pimoroni) + @rtype tuple of (bool, str) + @exception OSError raised to indicate an issue with the device + """ + command = """ +def has_wifi(): + try: + import wifi + if hasattr(wifi, 'radio'): + return True, 'circuitpython' + except ImportError: + pass + + return False, '' + +print(has_wifi()) +del has_wifi +""" + out, err = self._interface.execute(command, mode=self.submitMode) + if err: + raise OSError(self._shortError(err)) + return ast.literal_eval(out.decode("utf-8")) + + def getWifiData(self): + """ + Public method to get data related to the current WiFi status. + + @return tuple of three dictionaries containing the WiFi status data + for the WiFi client, access point and overall data + @rtype tuple of (dict, dict, dict) + @exception OSError raised to indicate an issue with the device + """ + command = """ +def wifi_status(): + import binascii + import json + import wifi + + r = wifi.radio + + station = { + 'active': r.enabled and r.ipv4_address_ap is None, + 'connected': r.ipv4_address is not None, + 'ifconfig': ( + str(r.ipv4_address) if r.ipv4_address else'0.0.0.0', + str(r.ipv4_subnet) if r.ipv4_subnet else'0.0.0.0', + str(r.ipv4_gateway) if r.ipv4_gateway else'0.0.0.0', + str(r.ipv4_dns) if r.ipv4_dns else'0.0.0.0', + ), + 'mac': binascii.hexlify(r.mac_address, ':').decode(), + 'txpower': r.tx_power, + 'hostname': r.hostname, + } + print(json.dumps(station)) + + ap = { + 'active': r.enabled and r.ipv4_address_ap is not None, + 'connected': r.ipv4_address_ap is not None, + 'ifconfig': ( + str(r.ipv4_address_ap) if r.ipv4_address_ap else'0.0.0.0', + str(r.ipv4_subnet_ap) if r.ipv4_subnet_ap else'0.0.0.0', + str(r.ipv4_gateway_ap) if r.ipv4_gateway_ap else'0.0.0.0', + str(r.ipv4_dns) if r.ipv4_dns else'0.0.0.0', + ), + 'mac': binascii.hexlify(r.mac_address_ap, ':').decode(), + 'txpower': r.tx_power, + 'hostname': r.hostname, + } + print(json.dumps(ap)) + + overall = { + 'active': r.enabled + } + print(json.dumps(overall)) + +wifi_status() +del wifi_status +""" + + out, err = self._interface.execute(command, mode=self.submitMode) + if err: + raise OSError(self._shortError(err)) + + stationStr, apStr, overallStr = out.decode("utf-8").splitlines() + station = json.loads(stationStr) + ap = json.loads(apStr) + overall = json.loads(overallStr) + return station, ap, overall + def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): """
--- a/src/eric7/MicroPython/Devices/EspDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/EspDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -13,8 +13,8 @@ import json import os -from PyQt6.QtCore import QProcess, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtCore import QCoreApplication, QProcess, QUrl, pyqtSlot +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QDialog, QMenu from eric7 import Globals, Preferences @@ -230,102 +230,21 @@ """ Private slot to erase the device flash memory. """ - ok = EricMessageBox.yesNo( - self.microPython, - self.tr("Erase Flash"), - self.tr("""Shall the flash of the selected device really be erased?"""), - ) - if ok: - flashArgs = [ - "-u", - "-m", - "esptool", - "--port", - self.microPython.getCurrentPort(), - "erase_flash", - ] - dlg = EricProcessDialog( - self.tr("'esptool erase_flash' Output"), - self.tr("Erase Flash"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() + eraseFlash(self.microPython.getCurrentPort()) @pyqtSlot() def __flashMicroPython(self): """ Private slot to flash a MicroPython firmware to the device. """ - from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog - - dlg = EspFirmwareSelectionDialog() - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - ] - if baudRate != "115200": - flashArgs += ["--baud", baudRate] - flashArgs.append("write_flash") - if flashMode: - flashArgs += ["--flash_mode", flashMode] - flashArgs += [ - flashAddress, - firmware, - ] - dlg = EricProcessDialog( - self.tr("'esptool write_flash' Output"), - self.tr("Flash MicroPython Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() + flashPythonFirmware(self.microPython.getCurrentPort()) @pyqtSlot() def __flashAddons(self): """ Private slot to flash some additional firmware images. """ - from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog - - dlg = EspFirmwareSelectionDialog(addon=True) - if dlg.exec() == QDialog.DialogCode.Accepted: - chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() - flashArgs = [ - "-u", - "-m", - "esptool", - "--chip", - chip, - "--port", - self.microPython.getCurrentPort(), - ] - if baudRate != "115200": - flashArgs += ["--baud", baudRate] - flashArgs.append("write_flash") - if flashMode: - flashArgs += ["--flash_mode", flashMode] - flashArgs += [ - flashAddress.lower(), - firmware, - ] - dlg = EricProcessDialog( - self.tr("'esptool write_flash' Output"), - self.tr("Flash Additional Firmware"), - showProgress=True, - ) - res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) - if res: - dlg.exec() + flashAddonFirmware(self.microPython.getCurrentPort()) @pyqtSlot() def __backupFlash(self): @@ -444,9 +363,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __firmwareVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply @@ -1133,3 +1053,125 @@ @rtype EspDevice """ return EspDevice(microPythonWidget, deviceType) + + +################################################################################ +## Functions below implement flashing related functionality needed elsewhere ## +## as well. ## +################################################################################ + + +@pyqtSlot() +def eraseFlash(port): + """ + Slot to erase the device flash memory. + + @param port name of the serial port device to be used + @type str + """ + ok = EricMessageBox.yesNo( + None, + QCoreApplication.translate("EspDevice", "Erase Flash"), + QCoreApplication.translate( + "EspDevice", """Shall the flash of the selected device really be erased?""" + ), + ) + if ok: + flashArgs = [ + "-u", + "-m", + "esptool", + "--port", + port, + "erase_flash", + ] + dlg = EricProcessDialog( + QCoreApplication.translate("EspDevice", "'esptool erase_flash' Output"), + QCoreApplication.translate("EspDevice", "Erase Flash"), + showProgress=True, + ) + res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) + if res: + dlg.exec() + + +@pyqtSlot() +def flashPythonFirmware(port): + """ + Slot to flash a MicroPython firmware to the device. + + @param port name of the serial port device to be used + @type str + """ + from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog + + dlg = EspFirmwareSelectionDialog() + if dlg.exec() == QDialog.DialogCode.Accepted: + chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() + flashArgs = [ + "-u", + "-m", + "esptool", + "--chip", + chip, + "--port", + port, + ] + if baudRate != "115200": + flashArgs += ["--baud", baudRate] + flashArgs.append("write_flash") + if flashMode: + flashArgs += ["--flash_mode", flashMode] + flashArgs += [ + flashAddress, + firmware, + ] + dlg = EricProcessDialog( + QCoreApplication.translate("EspDevice", "'esptool write_flash' Output"), + QCoreApplication.translate("EspDevice", "Flash µPy/CPy Firmware"), + showProgress=True, + ) + res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) + if res: + dlg.exec() + + +@pyqtSlot() +def flashAddonFirmware(port): + """ + Slot to flash some additional firmware images. + + @param port name of the serial port device to be used + @type str + """ + from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog + + dlg = EspFirmwareSelectionDialog(addon=True) + if dlg.exec() == QDialog.DialogCode.Accepted: + chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() + flashArgs = [ + "-u", + "-m", + "esptool", + "--chip", + chip, + "--port", + port, + ] + if baudRate != "115200": + flashArgs += ["--baud", baudRate] + flashArgs.append("write_flash") + if flashMode: + flashArgs += ["--flash_mode", flashMode] + flashArgs += [ + flashAddress.lower(), + firmware, + ] + dlg = EricProcessDialog( + QCoreApplication.translate("EspDevice", "'esptool write_flash' Output"), + QCoreApplication.translate("EspDevice", "Flash Additional Firmware"), + showProgress=True, + ) + res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs) + if res: + dlg.exec()
--- a/src/eric7/MicroPython/Devices/EspDialogs/EspFirmwareSelectionDialog.ui Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/EspDialogs/EspFirmwareSelectionDialog.ui Mon Feb 27 17:43:11 2023 +0100 @@ -11,7 +11,7 @@ </rect> </property> <property name="windowTitle"> - <string>Flash MicroPython Firmware</string> + <string>Flash µPy/CPy Firmware</string> </property> <property name="sizeGripEnabled"> <bool>true</bool>
--- a/src/eric7/MicroPython/Devices/MicrobitDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/MicrobitDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -14,7 +14,7 @@ import shutil from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QMenu from eric7 import Globals, Preferences @@ -424,9 +424,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __firmwareVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply
--- a/src/eric7/MicroPython/Devices/PyBoardDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/PyBoardDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -10,7 +10,7 @@ import os from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QMenu from eric7 import Globals, Preferences @@ -449,9 +449,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __firmwareVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply
--- a/src/eric7/MicroPython/Devices/RP2040Devices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/RP2040Devices.py Mon Feb 27 17:43:11 2023 +0100 @@ -14,7 +14,7 @@ import os from PyQt6.QtCore import QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QDialog, QMenu from eric7 import Globals, Preferences @@ -203,9 +203,10 @@ dlg = UF2FlashDialog(boardType="rp2040") dlg.exec() + @pyqtSlot() def __activateBootloader(self): """ - Private method to switch the board into 'bootloader' mode. + Private slot to switch the board into 'bootloader' mode. """ if self.microPython.isConnected(): self.microPython.deviceInterface().execute( @@ -245,9 +246,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __firmwareVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply
--- a/src/eric7/MicroPython/Devices/TeensyDevices.py Mon Feb 27 16:55:09 2023 +0100 +++ b/src/eric7/MicroPython/Devices/TeensyDevices.py Mon Feb 27 17:43:11 2023 +0100 @@ -7,8 +7,8 @@ Module implementing the device interface class for Teensy boards with MicroPython. """ -from PyQt6.QtCore import QProcess, QUrl, pyqtSlot -from PyQt6.QtNetwork import QNetworkRequest +from PyQt6.QtCore import QCoreApplication, QProcess, QUrl, pyqtSlot +from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import QMenu from eric7 import Globals, Preferences @@ -148,10 +148,10 @@ ) self.__teensyMenu.addSeparator() self.__teensyMenu.addAction( - self.tr("MicroPython Flash Instructions"), self.__showFlashInstructions + self.tr("MicroPython Flash Instructions"), showTeensyFlashInstructions ) self.__flashMpyAct = self.__teensyMenu.addAction( - self.tr("Flash MicroPython Firmware"), self.__startTeensyLoader + self.tr("Flash MicroPython Firmware"), startTeensyLoader ) self.__flashMpyAct.setToolTip( self.tr("Start the 'Teensy Loader' application to flash the Teensy device.") @@ -195,9 +195,10 @@ reply = ui.networkAccessManager().head(request) reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) + @pyqtSlot(QNetworkReply) def __firmwareVersionResponse(self, reply): """ - Private method handling the response of the latest version request. + Private slot handling the response of the latest version request. @param reply reference to the reply object @type QNetworkReply @@ -232,40 +233,6 @@ msg, ) - def __showFlashInstructions(self): - """ - Private method to show a message box with instruction to flash the Teensy. - """ - EricMessageBox.information( - self.microPython, - self.tr("Flash MicroPython Firmware"), - self.tr( - """<p>Teensy 4.0 and Teensy 4.1 are flashed using the 'Teensy Loader'""" - """ application. Make sure you downloaded the MicroPython or""" - """ CircuitPython .hex file.</p>""" - """<p>See <a href="{0}">the PJRC Teensy web site</a>""" - """ for details.</p>""" - ).format("https://www.pjrc.com/teensy/loader.html"), - ) - - def __startTeensyLoader(self): - """ - Private method to start the 'Teensy Loader' application. - - Note: The application must be accessible via the application search path. - """ - ok, _ = QProcess.startDetached("teensy") - if not ok: - EricMessageBox.warning( - self.microPython, - self.tr("Start 'Teensy Loader'"), - self.tr( - """<p>The 'Teensy Loader' application <b>teensy</b> could not""" - """ be started. Ensure it is in the application search path or""" - """ start it manually.</p>""" - ), - ) - ################################################################## ## time related methods below ################################################################## @@ -322,3 +289,43 @@ @rtype PyBoardDevice """ return TeensyDevice(microPythonWidget, deviceType) + + +@pyqtSlot() +def showTeensyFlashInstructions(): + """ + Slot to show a message box with instruction to flash the Teensy. + """ + EricMessageBox.information( + None, + QCoreApplication.translate("TeensyDevice", "Flash MicroPython Firmware"), + QCoreApplication.translate( + "TeensyDevice", + """<p>Teensy 4.0 and Teensy 4.1 are flashed using the 'Teensy Loader'""" + """ application. Make sure you downloaded the MicroPython or""" + """ CircuitPython .hex file.</p>""" + """<p>See <a href="{0}">the PJRC Teensy web site</a>""" + """ for details.</p>""", + ).format("https://www.pjrc.com/teensy/loader.html"), + ) + + +@pyqtSlot() +def startTeensyLoader(): + """ + Slot to start the 'Teensy Loader' application. + + Note: The application must be accessible via the application search path. + """ + ok, _ = QProcess.startDetached("teensy") + if not ok: + EricMessageBox.warning( + None, + QCoreApplication.translate("TeensyDevice", "Start 'Teensy Loader'"), + QCoreApplication.translate( + "TeensyDevice", + """<p>The 'Teensy Loader' application <b>teensy</b> could not""" + """ be started. Ensure it is in the application search path or""" + """ start it manually.</p>""", + ), + )