src/eric7/MicroPython/Devices/EspDevices.py

branch
eric7
changeset 9756
9854647c8c5c
parent 9752
2b9546c0cbd9
child 9763
52f982c08301
equal deleted inserted replaced
9755:1a09700229e7 9756:9854647c8c5c
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the device interface class for ESP32 and ESP8266 based
8 boards.
9 """
10
11 from PyQt6.QtCore import QProcess, QUrl, pyqtSlot
12 from PyQt6.QtNetwork import QNetworkRequest
13 from PyQt6.QtWidgets import QDialog, QMenu
14
15 from eric7 import Globals, Preferences
16 from eric7.EricWidgets import EricMessageBox
17 from eric7.EricWidgets.EricApplication import ericApp
18 from eric7.EricWidgets.EricProcessDialog import EricProcessDialog
19 from eric7.SystemUtilities import PythonUtilities
20
21 from . import FirmwareGithubUrls
22 from .DeviceBase import BaseDevice
23 from ..MicroPythonWidget import HAS_QTCHART
24
25
26 class EspDevice(BaseDevice):
27 """
28 Class implementing the device for ESP32 and ESP8266 based boards.
29 """
30
31 def __init__(self, microPythonWidget, deviceType, parent=None):
32 """
33 Constructor
34
35 @param microPythonWidget reference to the main MicroPython widget
36 @type MicroPythonWidget
37 @param deviceType device type assigned to this device interface
38 @type str
39 @param parent reference to the parent object
40 @type QObject
41 """
42 super().__init__(microPythonWidget, deviceType, parent)
43
44 self.__createEsp32Submenu()
45
46 def setButtons(self):
47 """
48 Public method to enable the supported action buttons.
49 """
50 super().setButtons()
51 self.microPython.setActionButtons(
52 run=True, repl=True, files=True, chart=HAS_QTCHART
53 )
54
55 def forceInterrupt(self):
56 """
57 Public method to determine the need for an interrupt when opening the
58 serial connection.
59
60 @return flag indicating an interrupt is needed
61 @rtype bool
62 """
63 return True
64
65 def deviceName(self):
66 """
67 Public method to get the name of the device.
68
69 @return name of the device
70 @rtype str
71 """
72 return self.tr("ESP8266, ESP32")
73
74 def canStartRepl(self):
75 """
76 Public method to determine, if a REPL can be started.
77
78 @return tuple containing a flag indicating it is safe to start a REPL
79 and a reason why it cannot.
80 @rtype tuple of (bool, str)
81 """
82 return True, ""
83
84 def canStartPlotter(self):
85 """
86 Public method to determine, if a Plotter can be started.
87
88 @return tuple containing a flag indicating it is safe to start a
89 Plotter and a reason why it cannot.
90 @rtype tuple of (bool, str)
91 """
92 return True, ""
93
94 def canRunScript(self):
95 """
96 Public method to determine, if a script can be executed.
97
98 @return tuple containing a flag indicating it is safe to start a
99 Plotter and a reason why it cannot.
100 @rtype tuple of (bool, str)
101 """
102 return True, ""
103
104 def runScript(self, script):
105 """
106 Public method to run the given Python script.
107
108 @param script script to be executed
109 @type str
110 """
111 pythonScript = script.split("\n")
112 self.sendCommands(pythonScript)
113
114 def canStartFileManager(self):
115 """
116 Public method to determine, if a File Manager can be started.
117
118 @return tuple containing a flag indicating it is safe to start a
119 File Manager and a reason why it cannot.
120 @rtype tuple of (bool, str)
121 """
122 return True, ""
123
124 def __createEsp32Submenu(self):
125 """
126 Private method to create the ESP32 submenu.
127 """
128 self.__espMenu = QMenu(self.tr("ESP32 Functions"))
129
130 self.__showMpyAct = self.__espMenu.addAction(
131 self.tr("Show MicroPython Versions"), self.__showFirmwareVersions
132 )
133 self.__espMenu.addSeparator()
134 self.__eraseFlashAct = self.__espMenu.addAction(
135 self.tr("Erase Flash"), self.__eraseFlash
136 )
137 self.__flashMpyAct = self.__espMenu.addAction(
138 self.tr("Flash MicroPython Firmware"), self.__flashMicroPython
139 )
140 self.__espMenu.addSeparator()
141 self.__flashAdditionalAct = self.__espMenu.addAction(
142 self.tr("Flash Additional Firmware"), self.__flashAddons
143 )
144 self.__espMenu.addSeparator()
145 self.__backupAct = self.__espMenu.addAction(
146 self.tr("Backup Firmware"), self.__backupFlash
147 )
148 self.__restoreAct = self.__espMenu.addAction(
149 self.tr("Restore Firmware"), self.__restoreFlash
150 )
151 self.__espMenu.addSeparator()
152 self.__chipIdAct = self.__espMenu.addAction(
153 self.tr("Show Chip ID"), self.__showChipID
154 )
155 self.__flashIdAct = self.__espMenu.addAction(
156 self.tr("Show Flash ID"), self.__showFlashID
157 )
158 self.__macAddressAct = self.__espMenu.addAction(
159 self.tr("Show MAC Address"), self.__showMACAddress
160 )
161 self.__espMenu.addSeparator()
162 self.__resetAct = self.__espMenu.addAction(
163 self.tr("Reset Device"), self.__resetDevice
164 )
165 self.__espMenu.addSeparator()
166 self.__espMenu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool)
167
168 def addDeviceMenuEntries(self, menu):
169 """
170 Public method to add device specific entries to the given menu.
171
172 @param menu reference to the context menu
173 @type QMenu
174 """
175 connected = self.microPython.isConnected()
176 linkConnected = self.microPython.isLinkConnected()
177
178 self.__showMpyAct.setEnabled(connected)
179 self.__eraseFlashAct.setEnabled(not linkConnected)
180 self.__flashMpyAct.setEnabled(not linkConnected)
181 self.__flashAdditionalAct.setEnabled(not linkConnected)
182 self.__backupAct.setEnabled(not linkConnected)
183 self.__restoreAct.setEnabled(not linkConnected)
184 self.__chipIdAct.setEnabled(not linkConnected)
185 self.__flashIdAct.setEnabled(not linkConnected)
186 self.__macAddressAct.setEnabled(not linkConnected)
187 self.__resetAct.setEnabled(connected or not linkConnected)
188
189 menu.addMenu(self.__espMenu)
190
191 def hasFlashMenuEntry(self):
192 """
193 Public method to check, if the device has its own flash menu entry.
194
195 @return flag indicating a specific flash menu entry
196 @rtype bool
197 """
198 return True
199
200 @pyqtSlot()
201 def __eraseFlash(self):
202 """
203 Private slot to erase the device flash memory.
204 """
205 ok = EricMessageBox.yesNo(
206 self.microPython,
207 self.tr("Erase Flash"),
208 self.tr("""Shall the flash of the selected device really be erased?"""),
209 )
210 if ok:
211 flashArgs = [
212 "-u",
213 "-m",
214 "esptool",
215 "--port",
216 self.microPython.getCurrentPort(),
217 "erase_flash",
218 ]
219 dlg = EricProcessDialog(
220 self.tr("'esptool erase_flash' Output"),
221 self.tr("Erase Flash"),
222 showProgress=True,
223 )
224 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs)
225 if res:
226 dlg.exec()
227
228 @pyqtSlot()
229 def __flashMicroPython(self):
230 """
231 Private slot to flash a MicroPython firmware to the device.
232 """
233 from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog
234
235 dlg = EspFirmwareSelectionDialog()
236 if dlg.exec() == QDialog.DialogCode.Accepted:
237 chip, firmware, baudRate, flashMode, flashAddress = dlg.getData()
238 flashArgs = [
239 "-u",
240 "-m",
241 "esptool",
242 "--chip",
243 chip,
244 "--port",
245 self.microPython.getCurrentPort(),
246 ]
247 if baudRate != "115200":
248 flashArgs += ["--baud", baudRate]
249 flashArgs.append("write_flash")
250 if flashMode:
251 flashArgs += ["--flash_mode", flashMode]
252 flashArgs += [
253 flashAddress,
254 firmware,
255 ]
256 dlg = EricProcessDialog(
257 self.tr("'esptool write_flash' Output"),
258 self.tr("Flash MicroPython Firmware"),
259 showProgress=True,
260 )
261 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs)
262 if res:
263 dlg.exec()
264
265 @pyqtSlot()
266 def __flashAddons(self):
267 """
268 Private slot to flash some additional firmware images.
269 """
270 from .EspDialogs.EspFirmwareSelectionDialog import EspFirmwareSelectionDialog
271
272 dlg = EspFirmwareSelectionDialog(addon=True)
273 if dlg.exec() == QDialog.DialogCode.Accepted:
274 chip, firmware, baudRate, flashMode, flashAddress = dlg.getData()
275 flashArgs = [
276 "-u",
277 "-m",
278 "esptool",
279 "--chip",
280 chip,
281 "--port",
282 self.microPython.getCurrentPort(),
283 ]
284 if baudRate != "115200":
285 flashArgs += ["--baud", baudRate]
286 flashArgs.append("write_flash")
287 if flashMode:
288 flashArgs += ["--flash_mode", flashMode]
289 flashArgs += [
290 flashAddress.lower(),
291 firmware,
292 ]
293 dlg = EricProcessDialog(
294 self.tr("'esptool write_flash' Output"),
295 self.tr("Flash Additional Firmware"),
296 showProgress=True,
297 )
298 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs)
299 if res:
300 dlg.exec()
301
302 @pyqtSlot()
303 def __backupFlash(self):
304 """
305 Private slot to backup the currently flashed firmware.
306 """
307 from .EspDialogs.EspBackupRestoreFirmwareDialog import (
308 EspBackupRestoreFirmwareDialog
309 )
310
311 dlg = EspBackupRestoreFirmwareDialog(backupMode=True)
312 if dlg.exec() == QDialog.DialogCode.Accepted:
313 chip, flashSize, baudRate, flashMode, firmware = dlg.getData()
314 flashArgs = [
315 "-u",
316 "-m",
317 "esptool",
318 "--chip",
319 chip,
320 "--port",
321 self.microPython.getCurrentPort(),
322 "--baud",
323 baudRate,
324 "read_flash",
325 "0x0",
326 flashSize,
327 firmware,
328 ]
329 dlg = EricProcessDialog(
330 self.tr("'esptool read_flash' Output"),
331 self.tr("Backup Firmware"),
332 showProgress=True,
333 )
334 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs)
335 if res:
336 dlg.exec()
337
338 @pyqtSlot()
339 def __restoreFlash(self):
340 """
341 Private slot to restore a previously saved firmware.
342 """
343 from .EspDialogs.EspBackupRestoreFirmwareDialog import (
344 EspBackupRestoreFirmwareDialog
345 )
346
347 dlg = EspBackupRestoreFirmwareDialog(backupMode=False)
348 if dlg.exec() == QDialog.DialogCode.Accepted:
349 chip, flashSize, baudRate, flashMode, firmware = dlg.getData()
350 flashArgs = [
351 "-u",
352 "-m",
353 "esptool",
354 "--chip",
355 chip,
356 "--port",
357 self.microPython.getCurrentPort(),
358 "--baud",
359 baudRate,
360 "write_flash",
361 ]
362 if flashMode:
363 flashArgs.extend(
364 [
365 "--flash_mode",
366 flashMode,
367 ]
368 )
369 if bool(flashSize):
370 flashArgs.extend(
371 [
372 "--flash_size",
373 flashSize,
374 ]
375 )
376 flashArgs.extend(
377 [
378 "0x0",
379 firmware,
380 ]
381 )
382 dlg = EricProcessDialog(
383 self.tr("'esptool write_flash' Output"),
384 self.tr("Restore Firmware"),
385 showProgress=True,
386 )
387 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), flashArgs)
388 if res:
389 dlg.exec()
390
391 @pyqtSlot()
392 def __showFirmwareVersions(self):
393 """
394 Private slot to show the firmware version of the connected device and the
395 available firmware version.
396 """
397 if self.microPython.isConnected():
398 if self._deviceData["mpy_name"] == "micropython":
399 url = QUrl(FirmwareGithubUrls["micropython"])
400 elif self._deviceData["mpy_name"] == "circuitpython":
401 url = QUrl(FirmwareGithubUrls["circuitpython"])
402 else:
403 EricMessageBox.critical(
404 None,
405 self.tr("Show MicroPython Versions"),
406 self.tr(
407 """The firmware of the connected device cannot be"""
408 """ determined or the board does not run MicroPython"""
409 """ or CircuitPython. Aborting..."""
410 ),
411 )
412 return
413
414 ui = ericApp().getObject("UserInterface")
415 request = QNetworkRequest(url)
416 reply = ui.networkAccessManager().head(request)
417 reply.finished.connect(lambda: self.__firmwareVersionResponse(reply))
418
419 def __firmwareVersionResponse(self, reply):
420 """
421 Private method handling the response of the latest version request.
422
423 @param reply reference to the reply object
424 @type QNetworkReply
425 """
426 latestUrl = reply.url().toString()
427 tag = latestUrl.rsplit("/", 1)[-1]
428 while tag and not tag[0].isdecimal():
429 # get rid of leading non-decimal characters
430 tag = tag[1:]
431 latestVersion = Globals.versionToTuple(tag)
432
433 if self._deviceData["mpy_version"] == "unknown":
434 currentVersionStr = self.tr("unknown")
435 currentVersion = (0, 0, 0)
436 else:
437 currentVersionStr = self._deviceData["mpy_version"]
438 currentVersion = Globals.versionToTuple(currentVersionStr)
439
440 if self._deviceData["mpy_name"] == "circuitpython":
441 kind = "CircuitPython"
442 elif self._deviceData["mpy_name"] == "micropython":
443 kind = "MicroPython"
444
445 msg = self.tr(
446 "<h4>{0} Version Information</h4>"
447 "<table>"
448 "<tr><td>Installed:</td><td>{1}</td></tr>"
449 "<tr><td>Available:</td><td>{2}</td></tr>"
450 "</table>"
451 ).format(kind, currentVersionStr, tag)
452 if currentVersion < latestVersion:
453 msg += self.tr("<p><b>Update available!</b></p>")
454
455 EricMessageBox.information(
456 None,
457 self.tr("{0} Version").format(kind),
458 msg,
459 )
460
461 @pyqtSlot()
462 def __showChipID(self):
463 """
464 Private slot to show the ID of the ESP chip.
465 """
466 args = [
467 "-u",
468 "-m",
469 "esptool",
470 "--port",
471 self.microPython.getCurrentPort(),
472 "chip_id",
473 ]
474 dlg = EricProcessDialog(
475 self.tr("'esptool chip_id' Output"), self.tr("Show Chip ID")
476 )
477 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args)
478 if res:
479 dlg.exec()
480
481 @pyqtSlot()
482 def __showFlashID(self):
483 """
484 Private slot to show the ID of the ESP flash chip.
485 """
486 args = [
487 "-u",
488 "-m",
489 "esptool",
490 "--port",
491 self.microPython.getCurrentPort(),
492 "flash_id",
493 ]
494 dlg = EricProcessDialog(
495 self.tr("'esptool flash_id' Output"), self.tr("Show Flash ID")
496 )
497 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args)
498 if res:
499 dlg.exec()
500
501 @pyqtSlot()
502 def __showMACAddress(self):
503 """
504 Private slot to show the MAC address of the ESP chip.
505 """
506 args = [
507 "-u",
508 "-m",
509 "esptool",
510 "--port",
511 self.microPython.getCurrentPort(),
512 "read_mac",
513 ]
514 dlg = EricProcessDialog(
515 self.tr("'esptool read_mac' Output"), self.tr("Show MAC Address")
516 )
517 res = dlg.startProcess(PythonUtilities.getPythonExecutable(), args)
518 if res:
519 dlg.exec()
520
521 @pyqtSlot()
522 def __resetDevice(self):
523 """
524 Private slot to reset the connected device.
525 """
526 if self.microPython.isConnected():
527 self.microPython.commandsInterface().execute(
528 [
529 "import machine",
530 "machine.reset()",
531 ]
532 )
533 else:
534 # perform a reset via esptool using flash_id command ignoring
535 # the output
536 args = [
537 "-u",
538 "-m",
539 "esptool",
540 "--port",
541 self.microPython.getCurrentPort(),
542 "flash_id",
543 ]
544 proc = QProcess()
545 proc.start(PythonUtilities.getPythonExecutable(), args)
546 procStarted = proc.waitForStarted(10000)
547 if procStarted:
548 proc.waitForFinished(10000)
549
550 @pyqtSlot()
551 def __installEspTool(self):
552 """
553 Private slot to install the esptool package via pip.
554 """
555 pip = ericApp().getObject("Pip")
556 pip.installPackages(
557 ["esptool"], interpreter=PythonUtilities.getPythonExecutable()
558 )
559
560 def getDocumentationUrl(self):
561 """
562 Public method to get the device documentation URL.
563
564 @return documentation URL of the device
565 @rtype str
566 """
567 return Preferences.getMicroPython("MicroPythonDocuUrl")
568
569 def getFirmwareUrl(self):
570 """
571 Public method to get the device firmware download URL.
572
573 @return firmware download URL of the device
574 @rtype str
575 """
576 return Preferences.getMicroPython("MicroPythonFirmwareUrl")
577
578
579 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
580 """
581 Function to instantiate a MicroPython device object.
582
583 @param microPythonWidget reference to the main MicroPython widget
584 @type MicroPythonWidget
585 @param deviceType device type assigned to this device interface
586 @type str
587 @param vid vendor ID
588 @type int
589 @param pid product ID
590 @type int
591 @param boardName name of the board
592 @type str
593 @param serialNumber serial number of the board
594 @type str
595 @return reference to the instantiated device object
596 @rtype EspDevice
597 """
598 return EspDevice(microPythonWidget, deviceType)

eric ide

mercurial