eric7/MicroPython/CircuitPythonDevices.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
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 CircuitPython boards.
8 """
9
10 import shutil
11 import os
12
13 from PyQt5.QtCore import pyqtSlot
14
15 from E5Gui import E5MessageBox, E5FileDialog
16
17 from .MicroPythonDevices import MicroPythonDevice
18 from .MicroPythonWidget import HAS_QTCHART
19
20 import Utilities
21 import Preferences
22
23
24 class CircuitPythonDevice(MicroPythonDevice):
25 """
26 Class implementing the device for CircuitPython boards.
27 """
28 DeviceVolumeName = "CIRCUITPY"
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__(
42 microPythonWidget, deviceType, parent)
43
44 self.__workspace = self.__findWorkspace()
45
46 self.__nonUF2devices = {
47 "teensy": self.__flashTeensy,
48 }
49
50 def setButtons(self):
51 """
52 Public method to enable the supported action buttons.
53 """
54 super().setButtons()
55 self.microPython.setActionButtons(
56 run=True, repl=True, files=True, chart=HAS_QTCHART)
57
58 if self.__deviceVolumeMounted():
59 self.microPython.setActionButtons(open=True, save=True)
60
61 def forceInterrupt(self):
62 """
63 Public method to determine the need for an interrupt when opening the
64 serial connection.
65
66 @return flag indicating an interrupt is needed
67 @rtype bool
68 """
69 return False
70
71 def deviceName(self):
72 """
73 Public method to get the name of the device.
74
75 @return name of the device
76 @rtype str
77 """
78 return self.tr("CircuitPython")
79
80 def canStartRepl(self):
81 """
82 Public method to determine, if a REPL can be started.
83
84 @return tuple containing a flag indicating it is safe to start a REPL
85 and a reason why it cannot.
86 @rtype tuple of (bool, str)
87 """
88 return True, ""
89
90 def canStartPlotter(self):
91 """
92 Public method to determine, if a Plotter can be started.
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 canRunScript(self):
101 """
102 Public method to determine, if a script can be executed.
103
104 @return tuple containing a flag indicating it is safe to start a
105 Plotter and a reason why it cannot.
106 @rtype tuple of (bool, str)
107 """
108 return True, ""
109
110 def runScript(self, script):
111 """
112 Public method to run the given Python script.
113
114 @param script script to be executed
115 @type str
116 """
117 pythonScript = script.split("\n")
118 self.sendCommands(pythonScript)
119
120 def canStartFileManager(self):
121 """
122 Public method to determine, if a File Manager can be started.
123
124 @return tuple containing a flag indicating it is safe to start a
125 File Manager and a reason why it cannot.
126 @rtype tuple of (bool, str)
127 """
128 return True, ""
129
130 def supportsLocalFileAccess(self):
131 """
132 Public method to indicate file access via a local directory.
133
134 @return flag indicating file access via local directory
135 @rtype bool
136 """
137 return self.__deviceVolumeMounted()
138
139 def __deviceVolumeMounted(self):
140 """
141 Private method to check, if the device volume is mounted.
142
143 @return flag indicated a mounted device
144 @rtype bool
145 """
146 if self.__workspace and not os.path.exists(self.__workspace):
147 self.__workspace = "" # reset
148
149 return self.DeviceVolumeName in self.getWorkspace(silent=True)
150
151 def getWorkspace(self, silent=False):
152 """
153 Public method to get the workspace directory.
154
155 @param silent flag indicating silent operations
156 @type bool
157 @return workspace directory used for saving files
158 @rtype str
159 """
160 if self.__workspace:
161 # return cached entry
162 return self.__workspace
163 else:
164 self.__workspace = self.__findWorkspace(silent=silent)
165 return self.__workspace
166
167 def __findWorkspace(self, silent=False):
168 """
169 Private method to find the workspace directory.
170
171 @param silent flag indicating silent operations
172 @type bool
173 @return workspace directory used for saving files
174 @rtype str
175 """
176 # Attempts to find the paths on the filesystem that represents the
177 # plugged in CIRCUITPY boards.
178 deviceDirectories = Utilities.findVolume(self.DeviceVolumeName,
179 findAll=True)
180
181 if deviceDirectories:
182 if len(deviceDirectories) == 1:
183 return deviceDirectories[0]
184 else:
185 return self.selectDeviceDirectory(deviceDirectories)
186 else:
187 # return the default workspace and give the user a warning (unless
188 # silent mode is selected)
189 if not silent:
190 E5MessageBox.warning(
191 self.microPython,
192 self.tr("Workspace Directory"),
193 self.tr("Python files for CircuitPython can be edited in"
194 " place, if the device volume is locally"
195 " available. Such a volume was not found. In"
196 " place editing will not be available."
197 )
198 )
199
200 return super().getWorkspace()
201
202 def addDeviceMenuEntries(self, menu):
203 """
204 Public method to add device specific entries to the given menu.
205
206 @param menu reference to the context menu
207 @type QMenu
208 """
209 connected = self.microPython.isConnected()
210
211 act = menu.addAction(self.tr("Flash CircuitPython Firmware"),
212 self.__flashCircuitPython)
213 act.setEnabled(not connected)
214 menu.addSeparator()
215 act = menu.addAction(self.tr("Install Library Files"),
216 self.__installLibraryFiles)
217 act.setEnabled(self.__deviceVolumeMounted())
218
219 def hasFlashMenuEntry(self):
220 """
221 Public method to check, if the device has its own flash menu entry.
222
223 @return flag indicating a specific flash menu entry
224 @rtype bool
225 """
226 return True
227
228 @pyqtSlot()
229 def __flashCircuitPython(self):
230 """
231 Private slot to flash a CircuitPython firmware to the device.
232 """
233 lBoardName = self.microPython.getCurrentBoard().lower()
234 if lBoardName:
235 for name in self.__nonUF2devices:
236 if name in lBoardName:
237 self.__nonUF2devices[name]()
238 break
239 else:
240 from .UF2FlashDialog import UF2FlashDialog
241 dlg = UF2FlashDialog(boardType="circuitpython")
242 dlg.exec()
243
244 def __flashTeensy(self):
245 """
246 Private method to show a message box because Teens does not support
247 the UF2 bootloader yet.
248 """
249 E5MessageBox.information(
250 self.microPython,
251 self.tr("Flash CircuitPython Firmware"),
252 self.tr("""<p>Teensy 4.0 and Teensy 4.1 do not support the UF2"""
253 """ bootloader. Please use the 'Teensy Loader'"""
254 """ application to flash CircuitPython. Make sure you"""
255 """ downloaded the CircuitPython .hex file.</p>"""
256 """<p>See <a href="{0}">the PJRC Teensy web site</a>"""
257 """ for details.</p>""")
258 .format("https://www.pjrc.com/teensy/loader.html"))
259
260 @pyqtSlot()
261 def __installLibraryFiles(self):
262 """
263 Private slot to install Python files into the onboard library.
264 """
265 if not self.__deviceVolumeMounted():
266 E5MessageBox.critical(
267 self.microPython,
268 self.tr("Install Library Files"),
269 self.tr("""The device volume "<b>{0}</b>" is not available."""
270 """ Ensure it is mounted properly and try again."""))
271 return
272
273 target = os.path.join(self.getWorkspace(), "lib")
274 # ensure that the library directory exists on the device
275 if not os.path.isdir(target):
276 os.makedirs(target)
277
278 libraryFiles = E5FileDialog.getOpenFileNames(
279 self.microPython,
280 self.tr("Install Library Files"),
281 os.path.expanduser("~"),
282 self.tr("Compiled Python Files (*.mpy);;"
283 "Python Files (*.py);;"
284 "All Files (*)"))
285
286 for libraryFile in libraryFiles:
287 if os.path.exists(libraryFile):
288 shutil.copy2(libraryFile, target)
289
290 def getDocumentationUrl(self):
291 """
292 Public method to get the device documentation URL.
293
294 @return documentation URL of the device
295 @rtype str
296 """
297 return Preferences.getMicroPython("CircuitPythonDocuUrl")
298
299 def getDownloadMenuEntries(self):
300 """
301 Public method to retrieve the entries for the downloads menu.
302
303 @return list of tuples with menu text and URL to be opened for each
304 entry
305 @rtype list of tuple of (str, str)
306 """
307 return [
308 (self.tr("CircuitPython Firmware"),
309 Preferences.getMicroPython("CircuitPythonFirmwareUrl")),
310 (self.tr("CircuitPython Libraries"),
311 Preferences.getMicroPython("CircuitPythonLibrariesUrl"))
312 ]

eric ide

mercurial