src/eric7/MicroPython/EspDevices.py

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

eric ide

mercurial