|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2019 - 2021 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 import sys |
|
12 |
|
13 from PyQt5.QtCore import pyqtSlot, QProcess |
|
14 from PyQt5.QtWidgets import QDialog |
|
15 |
|
16 from E5Gui import E5MessageBox |
|
17 from E5Gui.E5ProcessDialog import E5ProcessDialog |
|
18 from E5Gui.E5Application import e5App |
|
19 |
|
20 from .MicroPythonDevices import MicroPythonDevice |
|
21 from .MicroPythonWidget import HAS_QTCHART |
|
22 |
|
23 import Preferences |
|
24 |
|
25 |
|
26 class EspDevice(MicroPythonDevice): |
|
27 """ |
|
28 Class implementing the device for ESP32 and ESP8266 based boards. |
|
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 def setButtons(self): |
|
44 """ |
|
45 Public method to enable the supported action buttons. |
|
46 """ |
|
47 super().setButtons() |
|
48 self.microPython.setActionButtons( |
|
49 run=True, repl=True, files=True, chart=HAS_QTCHART) |
|
50 |
|
51 def forceInterrupt(self): |
|
52 """ |
|
53 Public method to determine the need for an interrupt when opening the |
|
54 serial connection. |
|
55 |
|
56 @return flag indicating an interrupt is needed |
|
57 @rtype bool |
|
58 """ |
|
59 return True |
|
60 |
|
61 def deviceName(self): |
|
62 """ |
|
63 Public method to get the name of the device. |
|
64 |
|
65 @return name of the device |
|
66 @rtype str |
|
67 """ |
|
68 return self.tr("ESP8266, ESP32") |
|
69 |
|
70 def canStartRepl(self): |
|
71 """ |
|
72 Public method to determine, if a REPL can be started. |
|
73 |
|
74 @return tuple containing a flag indicating it is safe to start a REPL |
|
75 and a reason why it cannot. |
|
76 @rtype tuple of (bool, str) |
|
77 """ |
|
78 return True, "" |
|
79 |
|
80 def canStartPlotter(self): |
|
81 """ |
|
82 Public method to determine, if a Plotter can be started. |
|
83 |
|
84 @return tuple containing a flag indicating it is safe to start a |
|
85 Plotter and a reason why it cannot. |
|
86 @rtype tuple of (bool, str) |
|
87 """ |
|
88 return True, "" |
|
89 |
|
90 def canRunScript(self): |
|
91 """ |
|
92 Public method to determine, if a script can be executed. |
|
93 |
|
94 @return tuple containing a flag indicating it is safe to start a |
|
95 Plotter and a reason why it cannot. |
|
96 @rtype tuple of (bool, str) |
|
97 """ |
|
98 return True, "" |
|
99 |
|
100 def runScript(self, script): |
|
101 """ |
|
102 Public method to run the given Python script. |
|
103 |
|
104 @param script script to be executed |
|
105 @type str |
|
106 """ |
|
107 pythonScript = script.split("\n") |
|
108 self.sendCommands(pythonScript) |
|
109 |
|
110 def canStartFileManager(self): |
|
111 """ |
|
112 Public method to determine, if a File Manager can be started. |
|
113 |
|
114 @return tuple containing a flag indicating it is safe to start a |
|
115 File Manager and a reason why it cannot. |
|
116 @rtype tuple of (bool, str) |
|
117 """ |
|
118 return True, "" |
|
119 |
|
120 def addDeviceMenuEntries(self, menu): |
|
121 """ |
|
122 Public method to add device specific entries to the given menu. |
|
123 |
|
124 @param menu reference to the context menu |
|
125 @type QMenu |
|
126 """ |
|
127 connected = self.microPython.isConnected() |
|
128 |
|
129 act = menu.addAction(self.tr("Erase Flash"), |
|
130 self.__eraseFlash) |
|
131 act.setEnabled(not connected) |
|
132 act = menu.addAction(self.tr("Flash MicroPython Firmware"), |
|
133 self.__flashMicroPython) |
|
134 act.setEnabled(not connected) |
|
135 menu.addSeparator() |
|
136 act = menu.addAction(self.tr("Flash Additional Firmware"), |
|
137 self.__flashAddons) |
|
138 act.setEnabled(not connected) |
|
139 menu.addSeparator() |
|
140 act = menu.addAction(self.tr("Backup Firmware"), |
|
141 self.__backupFlash) |
|
142 act.setEnabled(not connected) |
|
143 act = menu.addAction(self.tr("Restore Firmware"), |
|
144 self.__restoreFlash) |
|
145 act.setEnabled(not connected) |
|
146 menu.addSeparator() |
|
147 act = menu.addAction(self.tr("Show Chip ID"), |
|
148 self.__showChipID) |
|
149 act.setEnabled(not connected) |
|
150 act = menu.addAction(self.tr("Show Flash ID"), |
|
151 self.__showFlashID) |
|
152 act.setEnabled(not connected) |
|
153 act = menu.addAction(self.tr("Show MAC Address"), |
|
154 self.__showMACAddress) |
|
155 act.setEnabled(not connected) |
|
156 menu.addSeparator() |
|
157 act = menu.addAction(self.tr("Reset Device"), self.__resetDevice) |
|
158 menu.addSeparator() |
|
159 menu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool) |
|
160 |
|
161 def hasFlashMenuEntry(self): |
|
162 """ |
|
163 Public method to check, if the device has its own flash menu entry. |
|
164 |
|
165 @return flag indicating a specific flash menu entry |
|
166 @rtype bool |
|
167 """ |
|
168 return True |
|
169 |
|
170 @pyqtSlot() |
|
171 def __eraseFlash(self): |
|
172 """ |
|
173 Private slot to erase the device flash memory. |
|
174 """ |
|
175 ok = E5MessageBox.yesNo( |
|
176 self.microPython, |
|
177 self.tr("Erase Flash"), |
|
178 self.tr("""Shall the flash of the selected device really be""" |
|
179 """ erased?""")) |
|
180 if ok: |
|
181 flashArgs = [ |
|
182 "-u", |
|
183 "-m", "esptool", |
|
184 "--port", self.microPython.getCurrentPort(), |
|
185 "erase_flash", |
|
186 ] |
|
187 dlg = E5ProcessDialog(self.tr("'esptool erase_flash' Output"), |
|
188 self.tr("Erase Flash"), |
|
189 showProgress=True) |
|
190 res = dlg.startProcess(sys.executable, flashArgs) |
|
191 if res: |
|
192 dlg.exec() |
|
193 |
|
194 @pyqtSlot() |
|
195 def __flashMicroPython(self): |
|
196 """ |
|
197 Private slot to flash a MicroPython firmware to the device. |
|
198 |
|
199 @exception ValueError raised to indicate an unsupported chip type |
|
200 """ |
|
201 from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog |
|
202 dlg = EspFirmwareSelectionDialog() |
|
203 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
204 chip, firmware, baudRate, flashMode, _ = dlg.getData() |
|
205 if chip not in ("esp8266", "esp32"): |
|
206 raise ValueError(self.tr("Unsupported chip type '{0}'.") |
|
207 .format(chip)) |
|
208 |
|
209 if chip == "esp8266": |
|
210 flashAddress = "0x0000" |
|
211 elif chip == "esp32": |
|
212 flashAddress = "0x1000" |
|
213 |
|
214 flashArgs = [ |
|
215 "-u", |
|
216 "-m", "esptool", |
|
217 "--chip", chip, |
|
218 "--port", self.microPython.getCurrentPort(), |
|
219 ] |
|
220 if baudRate != "115200": |
|
221 flashArgs += [ |
|
222 "--baud", baudRate |
|
223 ] |
|
224 flashArgs.append("write_flash") |
|
225 if flashMode: |
|
226 flashArgs += [ |
|
227 "--flash_mode", flashMode |
|
228 ] |
|
229 flashArgs += [ |
|
230 flashAddress, |
|
231 firmware, |
|
232 ] |
|
233 dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"), |
|
234 self.tr("Flash MicroPython Firmware"), |
|
235 showProgress=True) |
|
236 res = dlg.startProcess(sys.executable, flashArgs) |
|
237 if res: |
|
238 dlg.exec() |
|
239 |
|
240 @pyqtSlot() |
|
241 def __flashAddons(self): |
|
242 """ |
|
243 Private slot to flash some additional firmware images. |
|
244 """ |
|
245 from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog |
|
246 dlg = EspFirmwareSelectionDialog(addon=True) |
|
247 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
248 chip, firmware, baudRate, flashMode, flashAddress = dlg.getData() |
|
249 flashArgs = [ |
|
250 "-u", |
|
251 "-m", "esptool", |
|
252 "--chip", chip, |
|
253 "--port", self.microPython.getCurrentPort(), |
|
254 ] |
|
255 if baudRate != "115200": |
|
256 flashArgs += [ |
|
257 "--baud", baudRate |
|
258 ] |
|
259 flashArgs.append("write_flash") |
|
260 if flashMode: |
|
261 flashArgs += [ |
|
262 "--flash_mode", flashMode |
|
263 ] |
|
264 flashArgs += [ |
|
265 flashAddress.lower(), |
|
266 firmware, |
|
267 ] |
|
268 dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"), |
|
269 self.tr("Flash Additional Firmware"), |
|
270 showProgress=True) |
|
271 res = dlg.startProcess(sys.executable, flashArgs) |
|
272 if res: |
|
273 dlg.exec() |
|
274 |
|
275 @pyqtSlot() |
|
276 def __backupFlash(self): |
|
277 """ |
|
278 Private slot to backup the currently flashed firmware. |
|
279 """ |
|
280 from .EspBackupRestoreFirmwareDialog import ( |
|
281 EspBackupRestoreFirmwareDialog |
|
282 ) |
|
283 dlg = EspBackupRestoreFirmwareDialog(backupMode=True) |
|
284 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
285 chip, flashSize, flashMode, firmware = dlg.getData() |
|
286 flashArgs = [ |
|
287 "-u", |
|
288 "-m", "esptool", |
|
289 "--chip", chip, |
|
290 "--port", self.microPython.getCurrentPort(), |
|
291 "read_flash", |
|
292 "0x0", flashSize, |
|
293 firmware, |
|
294 ] |
|
295 dlg = E5ProcessDialog(self.tr("'esptool read_flash' Output"), |
|
296 self.tr("Backup Firmware"), |
|
297 showProgress=True) |
|
298 res = dlg.startProcess(sys.executable, flashArgs) |
|
299 if res: |
|
300 dlg.exec() |
|
301 |
|
302 @pyqtSlot() |
|
303 def __restoreFlash(self): |
|
304 """ |
|
305 Private slot to restore a previously saved firmware. |
|
306 """ |
|
307 from .EspBackupRestoreFirmwareDialog import ( |
|
308 EspBackupRestoreFirmwareDialog |
|
309 ) |
|
310 dlg = EspBackupRestoreFirmwareDialog(backupMode=False) |
|
311 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
312 chip, flashSize, flashMode, firmware = dlg.getData() |
|
313 flashArgs = [ |
|
314 "-u", |
|
315 "-m", "esptool", |
|
316 "--chip", chip, |
|
317 "--port", self.microPython.getCurrentPort(), |
|
318 "write_flash", |
|
319 "--flash_mode", flashMode, |
|
320 ] |
|
321 if bool(flashSize): |
|
322 flashArgs.extend([ |
|
323 "--flash_size", flashSize, |
|
324 ]) |
|
325 flashArgs.extend([ |
|
326 "0x0", |
|
327 firmware, |
|
328 ]) |
|
329 dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"), |
|
330 self.tr("Restore Firmware"), |
|
331 showProgress=True) |
|
332 res = dlg.startProcess(sys.executable, flashArgs) |
|
333 if res: |
|
334 dlg.exec() |
|
335 |
|
336 @pyqtSlot() |
|
337 def __showChipID(self): |
|
338 """ |
|
339 Private slot to show the ID of the ESP chip. |
|
340 """ |
|
341 args = [ |
|
342 "-u", |
|
343 "-m", "esptool", |
|
344 "--port", self.microPython.getCurrentPort(), |
|
345 "chip_id" |
|
346 ] |
|
347 dlg = E5ProcessDialog(self.tr("'esptool chip_id' Output"), |
|
348 self.tr("Show Chip ID")) |
|
349 res = dlg.startProcess(sys.executable, args) |
|
350 if res: |
|
351 dlg.exec() |
|
352 |
|
353 @pyqtSlot() |
|
354 def __showFlashID(self): |
|
355 """ |
|
356 Private slot to show the ID of the ESP flash chip. |
|
357 """ |
|
358 args = [ |
|
359 "-u", |
|
360 "-m", "esptool", |
|
361 "--port", self.microPython.getCurrentPort(), |
|
362 "flash_id" |
|
363 ] |
|
364 dlg = E5ProcessDialog(self.tr("'esptool flash_id' Output"), |
|
365 self.tr("Show Flash ID")) |
|
366 res = dlg.startProcess(sys.executable, args) |
|
367 if res: |
|
368 dlg.exec() |
|
369 |
|
370 @pyqtSlot() |
|
371 def __showMACAddress(self): |
|
372 """ |
|
373 Private slot to show the MAC address of the ESP chip. |
|
374 """ |
|
375 args = [ |
|
376 "-u", |
|
377 "-m", "esptool", |
|
378 "--port", self.microPython.getCurrentPort(), |
|
379 "read_mac" |
|
380 ] |
|
381 dlg = E5ProcessDialog(self.tr("'esptool read_mac' Output"), |
|
382 self.tr("Show MAC Address")) |
|
383 res = dlg.startProcess(sys.executable, args) |
|
384 if res: |
|
385 dlg.exec() |
|
386 |
|
387 @pyqtSlot() |
|
388 def __resetDevice(self): |
|
389 """ |
|
390 Private slot to reset the connected device. |
|
391 """ |
|
392 if self.microPython.isConnected(): |
|
393 self.microPython.commandsInterface().execute([ |
|
394 "import machine", |
|
395 "machine.reset()", |
|
396 ]) |
|
397 else: |
|
398 # perform a reset via esptool using flash_id command ignoring |
|
399 # the output |
|
400 args = [ |
|
401 "-u", |
|
402 "-m", "esptool", |
|
403 "--port", self.microPython.getCurrentPort(), |
|
404 "flash_id" |
|
405 ] |
|
406 proc = QProcess() |
|
407 proc.start(sys.executable, args) |
|
408 procStarted = proc.waitForStarted(10000) |
|
409 if procStarted: |
|
410 proc.waitForFinished(10000) |
|
411 |
|
412 @pyqtSlot() |
|
413 def __installEspTool(self): |
|
414 """ |
|
415 Private slot to install the esptool package via pip. |
|
416 """ |
|
417 pip = e5App().getObject("Pip") |
|
418 pip.installPackages(["esptool"], interpreter=sys.executable) |
|
419 |
|
420 def getDocumentationUrl(self): |
|
421 """ |
|
422 Public method to get the device documentation URL. |
|
423 |
|
424 @return documentation URL of the device |
|
425 @rtype str |
|
426 """ |
|
427 return Preferences.getMicroPython("MicroPythonDocuUrl") |
|
428 |
|
429 def getFirmwareUrl(self): |
|
430 """ |
|
431 Public method to get the device firmware download URL. |
|
432 |
|
433 @return firmware download URL of the device |
|
434 @rtype str |
|
435 """ |
|
436 return Preferences.getMicroPython("MicroPythonFirmwareUrl") |