|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing some utility functions and the MicroPythonDevice base |
|
8 class. |
|
9 """ |
|
10 |
|
11 import contextlib |
|
12 import copy |
|
13 import os |
|
14 |
|
15 from PyQt6.QtCore import QObject, pyqtSlot |
|
16 from PyQt6.QtWidgets import QInputDialog |
|
17 |
|
18 from eric7 import Preferences |
|
19 from eric7.EricWidgets import EricMessageBox |
|
20 from eric7.EricWidgets.EricApplication import ericApp |
|
21 |
|
22 |
|
23 class BaseDevice(QObject): |
|
24 """ |
|
25 Base class for the more specific MicroPython devices. |
|
26 """ |
|
27 |
|
28 def __init__(self, microPythonWidget, deviceType, parent=None): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param microPythonWidget reference to the main MicroPython widget |
|
33 @type MicroPythonWidget |
|
34 @param deviceType device type assigned to this device interface |
|
35 @type str |
|
36 @param parent reference to the parent object |
|
37 @type QObject |
|
38 """ |
|
39 super().__init__(parent) |
|
40 |
|
41 self._deviceType = deviceType |
|
42 self.microPython = microPythonWidget |
|
43 self._deviceData = {} # dictionary with essential device data |
|
44 |
|
45 def setConnected(self, connected): |
|
46 """ |
|
47 Public method to set the connection state. |
|
48 |
|
49 Note: This method can be overwritten to perform actions upon connect |
|
50 or disconnect of the device. |
|
51 |
|
52 @param connected connection state |
|
53 @type bool |
|
54 """ |
|
55 self._deviceData = {} |
|
56 |
|
57 if connected: |
|
58 with contextlib.suppress(OSError): |
|
59 self._deviceData = self.microPython.commandsInterface().getDeviceData() |
|
60 |
|
61 def getDeviceType(self): |
|
62 """ |
|
63 Public method to get the device type. |
|
64 |
|
65 @return type of the device |
|
66 @rtype str |
|
67 """ |
|
68 return self._deviceType |
|
69 |
|
70 def getDeviceData(self): |
|
71 """ |
|
72 Public method to get a copy of the determined device data. |
|
73 |
|
74 @return dictionary containing the essential device data |
|
75 @rtype dict |
|
76 """ |
|
77 return copy.deepcopy(self._deviceData) |
|
78 |
|
79 def checkDeviceData(self): |
|
80 """ |
|
81 Public method to check the validity of the device data determined during |
|
82 connecting the device. |
|
83 |
|
84 @return flag indicating valid device data |
|
85 @rtype bool |
|
86 """ |
|
87 if bool(self._deviceData): |
|
88 return True |
|
89 else: |
|
90 EricMessageBox.critical( |
|
91 None, |
|
92 self.tr("Show MicroPython Versions"), |
|
93 self.tr( |
|
94 """<p>The device data is not available. Try to connect to the""" |
|
95 """ device again. Aborting...</p>""" |
|
96 ).format(self.getDeviceType()), |
|
97 ) |
|
98 return False |
|
99 |
|
100 def setButtons(self): |
|
101 """ |
|
102 Public method to enable the supported action buttons. |
|
103 """ |
|
104 self.microPython.setActionButtons( |
|
105 open=False, save=False, run=False, repl=False, files=False, chart=False |
|
106 ) |
|
107 |
|
108 def forceInterrupt(self): |
|
109 """ |
|
110 Public method to determine the need for an interrupt when opening the |
|
111 serial connection. |
|
112 |
|
113 @return flag indicating an interrupt is needed |
|
114 @rtype bool |
|
115 """ |
|
116 return True |
|
117 |
|
118 def deviceName(self): |
|
119 """ |
|
120 Public method to get the name of the device. |
|
121 |
|
122 @return name of the device |
|
123 @rtype str |
|
124 """ |
|
125 return self.tr("Unsupported Device") |
|
126 |
|
127 def canStartRepl(self): |
|
128 """ |
|
129 Public method to determine, if a REPL can be started. |
|
130 |
|
131 @return tuple containing a flag indicating it is safe to start a REPL |
|
132 and a reason why it cannot. |
|
133 @rtype tuple of (bool, str) |
|
134 """ |
|
135 return False, self.tr("REPL is not supported by this device.") |
|
136 |
|
137 def setRepl(self, on): |
|
138 """ |
|
139 Public method to set the REPL status and dependent status. |
|
140 |
|
141 @param on flag indicating the active status |
|
142 @type bool |
|
143 """ |
|
144 pass |
|
145 |
|
146 def canStartPlotter(self): |
|
147 """ |
|
148 Public method to determine, if a Plotter can be started. |
|
149 |
|
150 @return tuple containing a flag indicating it is safe to start a |
|
151 Plotter and a reason why it cannot. |
|
152 @rtype tuple of (bool, str) |
|
153 """ |
|
154 return False, self.tr("Plotter is not supported by this device.") |
|
155 |
|
156 def setPlotter(self, on): |
|
157 """ |
|
158 Public method to set the Plotter status and dependent status. |
|
159 |
|
160 @param on flag indicating the active status |
|
161 @type bool |
|
162 """ |
|
163 pass |
|
164 |
|
165 def canRunScript(self): |
|
166 """ |
|
167 Public method to determine, if a script can be executed. |
|
168 |
|
169 @return tuple containing a flag indicating it is safe to start a |
|
170 Plotter and a reason why it cannot. |
|
171 @rtype tuple of (bool, str) |
|
172 """ |
|
173 return False, self.tr("Running scripts is not supported by this device.") |
|
174 |
|
175 def runScript(self, script): |
|
176 """ |
|
177 Public method to run the given Python script. |
|
178 |
|
179 @param script script to be executed |
|
180 @type str |
|
181 """ |
|
182 pass |
|
183 |
|
184 def canStartFileManager(self): |
|
185 """ |
|
186 Public method to determine, if a File Manager can be started. |
|
187 |
|
188 @return tuple containing a flag indicating it is safe to start a |
|
189 File Manager and a reason why it cannot. |
|
190 @rtype tuple of (bool, str) |
|
191 """ |
|
192 return False, self.tr("File Manager is not supported by this device.") |
|
193 |
|
194 def setFileManager(self, on): |
|
195 """ |
|
196 Public method to set the File Manager status and dependent status. |
|
197 |
|
198 @param on flag indicating the active status |
|
199 @type bool |
|
200 """ |
|
201 pass |
|
202 |
|
203 def supportsLocalFileAccess(self): |
|
204 """ |
|
205 Public method to indicate file access via a local directory. |
|
206 |
|
207 @return flag indicating file access via local directory |
|
208 @rtype bool |
|
209 """ |
|
210 return False # default |
|
211 |
|
212 def getWorkspace(self): |
|
213 """ |
|
214 Public method to get the workspace directory. |
|
215 |
|
216 @return workspace directory used for saving files |
|
217 @rtype str |
|
218 """ |
|
219 return ( |
|
220 Preferences.getMicroPython("MpyWorkspace") |
|
221 or Preferences.getMultiProject("Workspace") |
|
222 or os.path.expanduser("~") |
|
223 ) |
|
224 |
|
225 def selectDeviceDirectory(self, deviceDirectories): |
|
226 """ |
|
227 Public method to select the device directory from a list of detected |
|
228 ones. |
|
229 |
|
230 @param deviceDirectories list of directories to select from |
|
231 @type list of str |
|
232 @return selected directory or an empty string |
|
233 @rtype str |
|
234 """ |
|
235 deviceDirectory, ok = QInputDialog.getItem( |
|
236 None, |
|
237 self.tr("Select Device Directory"), |
|
238 self.tr("Select the directory for the connected device:"), |
|
239 [""] + deviceDirectories, |
|
240 0, |
|
241 False, |
|
242 ) |
|
243 if ok: |
|
244 return deviceDirectory |
|
245 else: |
|
246 # user cancelled |
|
247 return "" |
|
248 |
|
249 def sendCommands(self, commandsList): |
|
250 """ |
|
251 Public method to send a list of commands to the device. |
|
252 |
|
253 @param commandsList list of commands to be sent to the device |
|
254 @type list of str |
|
255 """ |
|
256 rawOn = [ # sequence of commands to enter raw mode |
|
257 b"\x02", # Ctrl-B: exit raw repl (just in case) |
|
258 b"\r\x03\x03\x03", # Ctrl-C three times: interrupt any running |
|
259 # program |
|
260 b"\r\x01", # Ctrl-A: enter raw REPL |
|
261 ] |
|
262 newLine = [ |
|
263 b'print("\\n")\r', |
|
264 ] |
|
265 commands = [c.encode("utf-8)") + b"\r" for c in commandsList] |
|
266 commands.append(b"\r") |
|
267 commands.append(b"\x04") |
|
268 rawOff = [b"\x02", b"\x02"] |
|
269 commandSequence = rawOn + newLine + commands + rawOff |
|
270 self.microPython.commandsInterface().executeAsync(commandSequence) |
|
271 |
|
272 @pyqtSlot() |
|
273 def handleDataFlood(self): |
|
274 """ |
|
275 Public slot handling a data floof from the device. |
|
276 """ |
|
277 pass |
|
278 |
|
279 def addDeviceMenuEntries(self, menu): |
|
280 """ |
|
281 Public method to add device specific entries to the given menu. |
|
282 |
|
283 @param menu reference to the context menu |
|
284 @type QMenu |
|
285 """ |
|
286 pass |
|
287 |
|
288 def hasFlashMenuEntry(self): |
|
289 """ |
|
290 Public method to check, if the device has its own flash menu entry. |
|
291 |
|
292 @return flag indicating a specific flash menu entry |
|
293 @rtype bool |
|
294 """ |
|
295 return False |
|
296 |
|
297 def hasTimeCommands(self): |
|
298 """ |
|
299 Public method to check, if the device supports time commands. |
|
300 |
|
301 The default returns True. |
|
302 |
|
303 @return flag indicating support for time commands |
|
304 @rtype bool |
|
305 """ |
|
306 return True |
|
307 |
|
308 def hasDocumentationUrl(self): |
|
309 """ |
|
310 Public method to check, if the device has a configured documentation |
|
311 URL. |
|
312 |
|
313 @return flag indicating a configured documentation URL |
|
314 @rtype bool |
|
315 """ |
|
316 return bool(self.getDocumentationUrl()) |
|
317 |
|
318 def getDocumentationUrl(self): |
|
319 """ |
|
320 Public method to get the device documentation URL. |
|
321 |
|
322 @return documentation URL of the device |
|
323 @rtype str |
|
324 """ |
|
325 return "" |
|
326 |
|
327 def hasFirmwareUrl(self): |
|
328 """ |
|
329 Public method to check, if the device has a configured firmware |
|
330 download URL. |
|
331 |
|
332 @return flag indicating a configured firmware download URL |
|
333 @rtype bool |
|
334 """ |
|
335 return bool(self.getFirmwareUrl()) |
|
336 |
|
337 def getFirmwareUrl(self): |
|
338 """ |
|
339 Public method to get the device firmware download URL. |
|
340 |
|
341 @return firmware download URL of the device |
|
342 @rtype str |
|
343 """ |
|
344 return "" |
|
345 |
|
346 def downloadFirmware(self): |
|
347 """ |
|
348 Public method to download the device firmware. |
|
349 """ |
|
350 url = self.getFirmwareUrl() |
|
351 if url: |
|
352 ericApp().getObject("UserInterface").launchHelpViewer(url) |
|
353 |
|
354 def getDownloadMenuEntries(self): |
|
355 """ |
|
356 Public method to retrieve the entries for the downloads menu. |
|
357 |
|
358 @return list of tuples with menu text and URL to be opened for each |
|
359 entry |
|
360 @rtype list of tuple of (str, str) |
|
361 """ |
|
362 return [] |