|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the device interface class for Teensy boards with MicroPython. |
|
8 """ |
|
9 |
|
10 from PyQt6.QtCore import QProcess, QUrl, pyqtSlot |
|
11 from PyQt6.QtNetwork import QNetworkRequest |
|
12 from PyQt6.QtWidgets import QMenu |
|
13 |
|
14 from eric7 import Globals, Preferences |
|
15 from eric7.EricWidgets import EricMessageBox |
|
16 from eric7.EricWidgets.EricApplication import ericApp |
|
17 |
|
18 from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice |
|
19 from .MicroPythonWidget import HAS_QTCHART |
|
20 |
|
21 |
|
22 class TeensyDevice(MicroPythonDevice): |
|
23 """ |
|
24 Class implementing the device for Teensy boards with MicroPython. |
|
25 """ |
|
26 |
|
27 def __init__(self, microPythonWidget, deviceType, parent=None): |
|
28 """ |
|
29 Constructor |
|
30 |
|
31 @param microPythonWidget reference to the main MicroPython widget |
|
32 @type MicroPythonWidget |
|
33 @param deviceType device type assigned to this device interface |
|
34 @type str |
|
35 @param parent reference to the parent object |
|
36 @type QObject |
|
37 """ |
|
38 super().__init__(microPythonWidget, deviceType, parent) |
|
39 |
|
40 self.__createTeensyMenu() |
|
41 |
|
42 def setButtons(self): |
|
43 """ |
|
44 Public method to enable the supported action buttons. |
|
45 """ |
|
46 super().setButtons() |
|
47 self.microPython.setActionButtons( |
|
48 run=True, repl=True, files=True, chart=HAS_QTCHART |
|
49 ) |
|
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 False |
|
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("Teensy") |
|
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 getDocumentationUrl(self): |
|
121 """ |
|
122 Public method to get the device documentation URL. |
|
123 |
|
124 @return documentation URL of the device |
|
125 @rtype str |
|
126 """ |
|
127 return Preferences.getMicroPython("MicroPythonDocuUrl") |
|
128 |
|
129 def getFirmwareUrl(self): |
|
130 """ |
|
131 Public method to get the device firmware download URL. |
|
132 |
|
133 @return firmware download URL of the device |
|
134 @rtype str |
|
135 """ |
|
136 return Preferences.getMicroPython("MicroPythonFirmwareUrl") |
|
137 |
|
138 def __createTeensyMenu(self): |
|
139 """ |
|
140 Private method to create the microbit submenu. |
|
141 """ |
|
142 self.__teensyMenu = QMenu(self.tr("Teensy Functions")) |
|
143 |
|
144 self.__showMpyAct = self.__teensyMenu.addAction( |
|
145 self.tr("Show MicroPython Versions"), self.__showFirmwareVersions |
|
146 ) |
|
147 self.__teensyMenu.addSeparator() |
|
148 self.__teensyMenu.addAction( |
|
149 self.tr("MicroPython Flash Instructions"), self.__showFlashInstructions |
|
150 ) |
|
151 self.__flashMpyAct = self.__teensyMenu.addAction( |
|
152 self.tr("Flash MicroPython Firmware"), self.__startTeensyLoader |
|
153 ) |
|
154 self.__flashMpyAct.setToolTip( |
|
155 self.tr("Start the 'Teensy Loader' application to flash the Teensy device.") |
|
156 ) |
|
157 |
|
158 def addDeviceMenuEntries(self, menu): |
|
159 """ |
|
160 Public method to add device specific entries to the given menu. |
|
161 |
|
162 @param menu reference to the context menu |
|
163 @type QMenu |
|
164 """ |
|
165 connected = self.microPython.isConnected() |
|
166 linkConnected = self.microPython.isLinkConnected() |
|
167 |
|
168 self.__showMpyAct.setEnabled(connected) |
|
169 self.__flashMpyAct.setEnabled(not linkConnected) |
|
170 |
|
171 menu.addMenu(self.__teensyMenu) |
|
172 |
|
173 @pyqtSlot() |
|
174 def __showFirmwareVersions(self): |
|
175 """ |
|
176 Private slot to show the firmware version of the connected device and the |
|
177 available firmware version. |
|
178 """ |
|
179 if self.microPython.isConnected(): |
|
180 if self._deviceData["mpy_name"] != "micropython": |
|
181 EricMessageBox.critical( |
|
182 None, |
|
183 self.tr("Show MicroPython Versions"), |
|
184 self.tr( |
|
185 """The firmware of the connected device cannot be""" |
|
186 """ determined or the board does not run MicroPython.""" |
|
187 """ Aborting...""" |
|
188 ), |
|
189 ) |
|
190 else: |
|
191 ui = ericApp().getObject("UserInterface") |
|
192 request = QNetworkRequest(QUrl(FirmwareGithubUrls["micropython"])) |
|
193 reply = ui.networkAccessManager().head(request) |
|
194 reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) |
|
195 |
|
196 def __firmwareVersionResponse(self, reply): |
|
197 """ |
|
198 Private method handling the response of the latest version request. |
|
199 |
|
200 @param reply reference to the reply object |
|
201 @type QNetworkReply |
|
202 """ |
|
203 latestUrl = reply.url().toString() |
|
204 tag = latestUrl.rsplit("/", 1)[-1] |
|
205 while tag and not tag[0].isdecimal(): |
|
206 # get rid of leading non-decimal characters |
|
207 tag = tag[1:] |
|
208 latestVersion = Globals.versionToTuple(tag) |
|
209 |
|
210 if self._deviceData["mpy_version"] == "unknown": |
|
211 currentVersionStr = self.tr("unknown") |
|
212 currentVersion = (0, 0, 0) |
|
213 else: |
|
214 currentVersionStr = self._deviceData["mpy_version"] |
|
215 currentVersion = Globals.versionToTuple(currentVersionStr) |
|
216 |
|
217 msg = self.tr( |
|
218 "<h4>MicroPython Version Information</h4>" |
|
219 "<table>" |
|
220 "<tr><td>Installed:</td><td>{0}</td></tr>" |
|
221 "<tr><td>Available:</td><td>{1}</td></tr>" |
|
222 "</table>" |
|
223 ).format(currentVersionStr, tag) |
|
224 if currentVersion < latestVersion: |
|
225 msg += self.tr("<p><b>Update available!</b></p>") |
|
226 |
|
227 EricMessageBox.information( |
|
228 None, |
|
229 self.tr("MicroPython Version"), |
|
230 msg, |
|
231 ) |
|
232 |
|
233 def __showFlashInstructions(self): |
|
234 """ |
|
235 Private method to show a message box with instruction to flash the Teensy. |
|
236 """ |
|
237 EricMessageBox.information( |
|
238 self.microPython, |
|
239 self.tr("Flash MicroPython Firmware"), |
|
240 self.tr( |
|
241 """<p>Teensy 4.0 and Teensy 4.1 are flashed using the 'Teensy Loader'""" |
|
242 """ application. Make sure you downloaded the MicroPython or""" |
|
243 """ CircuitPython .hex file.</p>""" |
|
244 """<p>See <a href="{0}">the PJRC Teensy web site</a>""" |
|
245 """ for details.</p>""" |
|
246 ).format("https://www.pjrc.com/teensy/loader.html"), |
|
247 ) |
|
248 |
|
249 def __startTeensyLoader(self): |
|
250 """ |
|
251 Private method to start the 'Teensy Loader' application. |
|
252 |
|
253 Note: The application must be accessible via the application search path. |
|
254 """ |
|
255 ok, _ = QProcess.startDetached("teensy") |
|
256 if not ok: |
|
257 EricMessageBox.warning( |
|
258 self.microPython, |
|
259 self.tr("Start 'Teensy Loader'"), |
|
260 self.tr( |
|
261 """<p>The 'Teensy Loader' application <b>teensy</b> could not""" |
|
262 """ be started. Ensure it is in the application search path or""" |
|
263 """ start it manually.</p>""" |
|
264 ), |
|
265 ) |
|
266 |
|
267 |
|
268 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): |
|
269 """ |
|
270 Function to instantiate a MicroPython device object. |
|
271 |
|
272 @param microPythonWidget reference to the main MicroPython widget |
|
273 @type MicroPythonWidget |
|
274 @param deviceType device type assigned to this device interface |
|
275 @type str |
|
276 @param vid vendor ID |
|
277 @type int |
|
278 @param pid product ID |
|
279 @type int |
|
280 @param boardName name of the board |
|
281 @type str |
|
282 @param serialNumber serial number of the board |
|
283 @type str |
|
284 @return reference to the instantiated device object |
|
285 @rtype PyBoardDevice |
|
286 """ |
|
287 return TeensyDevice(microPythonWidget, deviceType) |