9 |
9 |
10 import os |
10 import os |
11 import shutil |
11 import shutil |
12 |
12 |
13 from PyQt6.QtCore import pyqtSlot |
13 from PyQt6.QtCore import pyqtSlot |
|
14 from PyQt6.QtWidgets import QMenu |
14 |
15 |
15 from eric7 import Preferences |
16 from eric7 import Preferences |
16 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
17 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
17 from eric7.SystemUtilities import FileSystemUtilities |
18 from eric7.SystemUtilities import FileSystemUtilities |
18 |
19 |
25 Class implementing the device for CircuitPython boards. |
26 Class implementing the device for CircuitPython boards. |
26 """ |
27 """ |
27 |
28 |
28 DeviceVolumeName = "CIRCUITPY" |
29 DeviceVolumeName = "CIRCUITPY" |
29 |
30 |
30 def __init__(self, microPythonWidget, deviceType, parent=None): |
31 def __init__(self, microPythonWidget, deviceType, boardName, parent=None): |
31 """ |
32 """ |
32 Constructor |
33 Constructor |
33 |
34 |
34 @param microPythonWidget reference to the main MicroPython widget |
35 @param microPythonWidget reference to the main MicroPython widget |
35 @type MicroPythonWidget |
36 @type MicroPythonWidget |
36 @param deviceType device type assigned to this device interface |
37 @param deviceType device type assigned to this device interface |
37 @type str |
38 @type str |
|
39 @param boardName name of the board |
|
40 @type str |
38 @param parent reference to the parent object |
41 @param parent reference to the parent object |
39 @type QObject |
42 @type QObject |
40 """ |
43 """ |
41 super().__init__(microPythonWidget, deviceType, parent) |
44 super().__init__(microPythonWidget, deviceType, parent) |
42 |
45 |
|
46 self.__boardName = boardName |
43 self.__workspace = self.__findWorkspace() |
47 self.__workspace = self.__findWorkspace() |
44 |
48 |
45 self.__nonUF2devices = { |
49 self.__nonUF2devices = { |
46 "teensy": self.__flashTeensy, |
50 "teensy": self.__flashTeensy, |
47 } |
51 } |
162 return self.__workspace |
166 return self.__workspace |
163 else: |
167 else: |
164 self.__workspace = self.__findWorkspace(silent=silent) |
168 self.__workspace = self.__findWorkspace(silent=silent) |
165 return self.__workspace |
169 return self.__workspace |
166 |
170 |
|
171 def __findDeviceDirectories(self, directories): |
|
172 """ |
|
173 Private method to find the device directories associated with the |
|
174 current board name. |
|
175 |
|
176 @param directories list of directories to be checked |
|
177 @type list of str |
|
178 @return list of associated directories |
|
179 @rtype list of str |
|
180 """ |
|
181 boardDirectories = [] |
|
182 for directory in directories: |
|
183 bootFile = os.path.join(directory, "boot_out.txt") |
|
184 if os.path.exists(bootFile): |
|
185 with open(bootFile, "r") as f: |
|
186 line = f.readline() |
|
187 if self.__boardName in line: |
|
188 boardDirectories.append(directory) |
|
189 |
|
190 return boardDirectories |
|
191 |
167 def __findWorkspace(self, silent=False): |
192 def __findWorkspace(self, silent=False): |
168 """ |
193 """ |
169 Private method to find the workspace directory. |
194 Private method to find the workspace directory. |
170 |
195 |
171 @param silent flag indicating silent operations |
196 @param silent flag indicating silent operations |
181 |
206 |
182 if deviceDirectories: |
207 if deviceDirectories: |
183 if len(deviceDirectories) == 1: |
208 if len(deviceDirectories) == 1: |
184 return deviceDirectories[0] |
209 return deviceDirectories[0] |
185 else: |
210 else: |
186 return self.selectDeviceDirectory(deviceDirectories) |
211 boardDirectories = self.__findDeviceDirectories(deviceDirectories) |
|
212 if len(boardDirectories) == 1: |
|
213 return boardDirectories[0] |
|
214 elif len(boardDirectories) > 1: |
|
215 return self.selectDeviceDirectory(boardDirectories) |
|
216 else: |
|
217 return self.selectDeviceDirectory(deviceDirectories) |
187 else: |
218 else: |
188 # return the default workspace and give the user a warning (unless |
219 # return the default workspace and give the user a warning (unless |
189 # silent mode is selected) |
220 # silent mode is selected) |
190 if not silent: |
221 if not silent: |
191 EricMessageBox.warning( |
222 EricMessageBox.warning( |
208 @param menu reference to the context menu |
239 @param menu reference to the context menu |
209 @type QMenu |
240 @type QMenu |
210 """ |
241 """ |
211 connected = self.microPython.isConnected() |
242 connected = self.microPython.isConnected() |
212 |
243 |
|
244 self.__libraryMenu = QMenu(self.tr("Library Management")) |
|
245 act = self.__libraryMenu.addAction( |
|
246 self.tr("Install Library Files"), self.__installLibraryFiles |
|
247 ) |
|
248 act.setEnabled(self.__deviceVolumeMounted()) |
|
249 act = self.__libraryMenu.addAction( |
|
250 self.tr("Install Library Package"), |
|
251 lambda: self.__installLibraryFiles(packageMode=True), |
|
252 ) |
|
253 act.setEnabled(self.__deviceVolumeMounted()) |
|
254 |
213 act = menu.addAction( |
255 act = menu.addAction( |
214 self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython |
256 self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython |
215 ) |
257 ) |
216 act.setEnabled(not connected) |
258 act.setEnabled(not connected) |
217 menu.addSeparator() |
259 menu.addSeparator() |
218 act = menu.addAction( |
260 menu.addMenu(self.__libraryMenu) |
219 self.tr("Install Library Files"), self.__installLibraryFiles |
|
220 ) |
|
221 act.setEnabled(self.__deviceVolumeMounted()) |
|
222 |
261 |
223 def hasFlashMenuEntry(self): |
262 def hasFlashMenuEntry(self): |
224 """ |
263 """ |
225 Public method to check, if the device has its own flash menu entry. |
264 Public method to check, if the device has its own flash menu entry. |
226 |
265 |
263 """ for details.</p>""" |
302 """ for details.</p>""" |
264 ).format("https://www.pjrc.com/teensy/loader.html"), |
303 ).format("https://www.pjrc.com/teensy/loader.html"), |
265 ) |
304 ) |
266 |
305 |
267 @pyqtSlot() |
306 @pyqtSlot() |
268 def __installLibraryFiles(self): |
307 def __installLibraryFiles(self, packageMode=False): |
269 """ |
308 """ |
270 Private slot to install Python files into the onboard library. |
309 Private slot to install Python files into the onboard library. |
271 """ |
310 |
|
311 @param packageMode flag indicating to install a library package |
|
312 (defaults to False) |
|
313 @type bool (optional) |
|
314 """ |
|
315 title = ( |
|
316 self.tr("Install Library Package") |
|
317 if packageMode |
|
318 else self.tr("Install Library Files") |
|
319 ) |
272 if not self.__deviceVolumeMounted(): |
320 if not self.__deviceVolumeMounted(): |
273 EricMessageBox.critical( |
321 EricMessageBox.critical( |
274 self.microPython, |
322 self.microPython, |
275 self.tr("Install Library Files"), |
323 title, |
276 self.tr( |
324 self.tr( |
277 """The device volume "<b>{0}</b>" is not available.""" |
325 """The device volume "<b>{0}</b>" is not available.""" |
278 """ Ensure it is mounted properly and try again.""" |
326 """ Ensure it is mounted properly and try again.""" |
279 ), |
327 ), |
280 ) |
328 ) |
283 target = os.path.join(self.getWorkspace(), "lib") |
331 target = os.path.join(self.getWorkspace(), "lib") |
284 # ensure that the library directory exists on the device |
332 # ensure that the library directory exists on the device |
285 if not os.path.isdir(target): |
333 if not os.path.isdir(target): |
286 os.makedirs(target) |
334 os.makedirs(target) |
287 |
335 |
288 libraryFiles = EricFileDialog.getOpenFileNames( |
336 if packageMode: |
289 self.microPython, |
337 libraryPackage = EricFileDialog.getExistingDirectory( |
290 self.tr("Install Library Files"), |
338 self.microPython, |
291 os.path.expanduser("~"), |
339 title, |
292 self.tr( |
340 os.path.expanduser("~"), |
293 "Compiled Python Files (*.mpy);;" |
341 EricFileDialog.Option(0), |
294 "Python Files (*.py);;" |
342 ) |
295 "All Files (*)" |
343 if libraryPackage: |
296 ), |
344 target = os.path.join(target, os.path.basename(libraryPackage)) |
297 ) |
345 shutil.rmtree(target, ignore_errors=True) |
298 |
346 shutil.copytree(libraryPackage, target) |
299 for libraryFile in libraryFiles: |
347 else: |
300 if os.path.exists(libraryFile): |
348 libraryFiles = EricFileDialog.getOpenFileNames( |
301 shutil.copy2(libraryFile, target) |
349 self.microPython, |
|
350 title, |
|
351 os.path.expanduser("~"), |
|
352 self.tr( |
|
353 "Compiled Python Files (*.mpy);;" |
|
354 "Python Files (*.py);;" |
|
355 "All Files (*)" |
|
356 ), |
|
357 ) |
|
358 |
|
359 for libraryFile in libraryFiles: |
|
360 if os.path.exists(libraryFile): |
|
361 shutil.copy2(libraryFile, target) |
302 |
362 |
303 def getDocumentationUrl(self): |
363 def getDocumentationUrl(self): |
304 """ |
364 """ |
305 Public method to get the device documentation URL. |
365 Public method to get the device documentation URL. |
306 |
366 |
327 Preferences.getMicroPython("CircuitPythonLibrariesUrl"), |
387 Preferences.getMicroPython("CircuitPythonLibrariesUrl"), |
328 ), |
388 ), |
329 ] |
389 ] |
330 |
390 |
331 |
391 |
332 def createDevice(microPythonWidget, deviceType, vid, pid): |
392 def createDevice(microPythonWidget, deviceType, vid, pid, boardName): |
333 """ |
393 """ |
334 Function to instantiate a MicroPython device object. |
394 Function to instantiate a MicroPython device object. |
335 |
395 |
336 @param microPythonWidget reference to the main MicroPython widget |
396 @param microPythonWidget reference to the main MicroPython widget |
337 @type MicroPythonWidget |
397 @type MicroPythonWidget |
339 @type str |
399 @type str |
340 @param vid vendor ID |
400 @param vid vendor ID |
341 @type int |
401 @type int |
342 @param pid product ID |
402 @param pid product ID |
343 @type int |
403 @type int |
|
404 @param boardName name of the board |
|
405 @type str |
344 @return reference to the instantiated device object |
406 @return reference to the instantiated device object |
345 @rtype CircuitPythonDevice |
407 @rtype CircuitPythonDevice |
346 """ |
408 """ |
347 return CircuitPythonDevice(microPythonWidget, deviceType) |
409 return CircuitPythonDevice(microPythonWidget, deviceType, boardName) |