src/eric7/MicroPython/PyBoardDevices.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
24 24
25 class PyBoardDevice(MicroPythonDevice): 25 class PyBoardDevice(MicroPythonDevice):
26 """ 26 """
27 Class implementing the device for PyBoard boards. 27 Class implementing the device for PyBoard boards.
28 """ 28 """
29
29 DeviceVolumeName = "PYBFLASH" 30 DeviceVolumeName = "PYBFLASH"
30 31
31 FlashInstructionsURL = ( 32 FlashInstructionsURL = (
32 "https://github.com/micropython/micropython/wiki/" 33 "https://github.com/micropython/micropython/wiki/" "Pyboard-Firmware-Update"
33 "Pyboard-Firmware-Update"
34 ) 34 )
35 35
36 def __init__(self, microPythonWidget, deviceType, parent=None): 36 def __init__(self, microPythonWidget, deviceType, parent=None):
37 """ 37 """
38 Constructor 38 Constructor
39 39
40 @param microPythonWidget reference to the main MicroPython widget 40 @param microPythonWidget reference to the main MicroPython widget
41 @type MicroPythonWidget 41 @type MicroPythonWidget
42 @param deviceType device type assigned to this device interface 42 @param deviceType device type assigned to this device interface
43 @type str 43 @type str
44 @param parent reference to the parent object 44 @param parent reference to the parent object
45 @type QObject 45 @type QObject
46 """ 46 """
47 super().__init__(microPythonWidget, deviceType, parent) 47 super().__init__(microPythonWidget, deviceType, parent)
48 48
49 self.__workspace = self.__findWorkspace() 49 self.__workspace = self.__findWorkspace()
50 50
51 def setButtons(self): 51 def setButtons(self):
52 """ 52 """
53 Public method to enable the supported action buttons. 53 Public method to enable the supported action buttons.
54 """ 54 """
55 super().setButtons() 55 super().setButtons()
56 self.microPython.setActionButtons( 56 self.microPython.setActionButtons(
57 run=True, repl=True, files=True, chart=HAS_QTCHART) 57 run=True, repl=True, files=True, chart=HAS_QTCHART
58 58 )
59
59 if self.__deviceVolumeMounted(): 60 if self.__deviceVolumeMounted():
60 self.microPython.setActionButtons(open=True, save=True) 61 self.microPython.setActionButtons(open=True, save=True)
61 62
62 def forceInterrupt(self): 63 def forceInterrupt(self):
63 """ 64 """
64 Public method to determine the need for an interrupt when opening the 65 Public method to determine the need for an interrupt when opening the
65 serial connection. 66 serial connection.
66 67
67 @return flag indicating an interrupt is needed 68 @return flag indicating an interrupt is needed
68 @rtype bool 69 @rtype bool
69 """ 70 """
70 return False 71 return False
71 72
72 def deviceName(self): 73 def deviceName(self):
73 """ 74 """
74 Public method to get the name of the device. 75 Public method to get the name of the device.
75 76
76 @return name of the device 77 @return name of the device
77 @rtype str 78 @rtype str
78 """ 79 """
79 return self.tr("PyBoard") 80 return self.tr("PyBoard")
80 81
81 def canStartRepl(self): 82 def canStartRepl(self):
82 """ 83 """
83 Public method to determine, if a REPL can be started. 84 Public method to determine, if a REPL can be started.
84 85
85 @return tuple containing a flag indicating it is safe to start a REPL 86 @return tuple containing a flag indicating it is safe to start a REPL
86 and a reason why it cannot. 87 and a reason why it cannot.
87 @rtype tuple of (bool, str) 88 @rtype tuple of (bool, str)
88 """ 89 """
89 return True, "" 90 return True, ""
90 91
91 def canStartPlotter(self): 92 def canStartPlotter(self):
92 """ 93 """
93 Public method to determine, if a Plotter can be started. 94 Public method to determine, if a Plotter can be started.
94 95
95 @return tuple containing a flag indicating it is safe to start a 96 @return tuple containing a flag indicating it is safe to start a
96 Plotter and a reason why it cannot. 97 Plotter and a reason why it cannot.
97 @rtype tuple of (bool, str) 98 @rtype tuple of (bool, str)
98 """ 99 """
99 return True, "" 100 return True, ""
100 101
101 def canRunScript(self): 102 def canRunScript(self):
102 """ 103 """
103 Public method to determine, if a script can be executed. 104 Public method to determine, if a script can be executed.
104 105
105 @return tuple containing a flag indicating it is safe to start a 106 @return tuple containing a flag indicating it is safe to start a
106 Plotter and a reason why it cannot. 107 Plotter and a reason why it cannot.
107 @rtype tuple of (bool, str) 108 @rtype tuple of (bool, str)
108 """ 109 """
109 return True, "" 110 return True, ""
110 111
111 def runScript(self, script): 112 def runScript(self, script):
112 """ 113 """
113 Public method to run the given Python script. 114 Public method to run the given Python script.
114 115
115 @param script script to be executed 116 @param script script to be executed
116 @type str 117 @type str
117 """ 118 """
118 pythonScript = script.split("\n") 119 pythonScript = script.split("\n")
119 self.sendCommands(pythonScript) 120 self.sendCommands(pythonScript)
120 121
121 def canStartFileManager(self): 122 def canStartFileManager(self):
122 """ 123 """
123 Public method to determine, if a File Manager can be started. 124 Public method to determine, if a File Manager can be started.
124 125
125 @return tuple containing a flag indicating it is safe to start a 126 @return tuple containing a flag indicating it is safe to start a
126 File Manager and a reason why it cannot. 127 File Manager and a reason why it cannot.
127 @rtype tuple of (bool, str) 128 @rtype tuple of (bool, str)
128 """ 129 """
129 return True, "" 130 return True, ""
130 131
131 def supportsLocalFileAccess(self): 132 def supportsLocalFileAccess(self):
132 """ 133 """
133 Public method to indicate file access via a local directory. 134 Public method to indicate file access via a local directory.
134 135
135 @return flag indicating file access via local directory 136 @return flag indicating file access via local directory
136 @rtype bool 137 @rtype bool
137 """ 138 """
138 return self.__deviceVolumeMounted() 139 return self.__deviceVolumeMounted()
139 140
140 def __deviceVolumeMounted(self): 141 def __deviceVolumeMounted(self):
141 """ 142 """
142 Private method to check, if the device volume is mounted. 143 Private method to check, if the device volume is mounted.
143 144
144 @return flag indicated a mounted device 145 @return flag indicated a mounted device
145 @rtype bool 146 @rtype bool
146 """ 147 """
147 if self.__workspace and not os.path.exists(self.__workspace): 148 if self.__workspace and not os.path.exists(self.__workspace):
148 self.__workspace = "" # reset 149 self.__workspace = "" # reset
149 150
150 return self.DeviceVolumeName in self.getWorkspace(silent=True) 151 return self.DeviceVolumeName in self.getWorkspace(silent=True)
151 152
152 def getWorkspace(self, silent=False): 153 def getWorkspace(self, silent=False):
153 """ 154 """
154 Public method to get the workspace directory. 155 Public method to get the workspace directory.
155 156
156 @param silent flag indicating silent operations 157 @param silent flag indicating silent operations
157 @type bool 158 @type bool
158 @return workspace directory used for saving files 159 @return workspace directory used for saving files
159 @rtype str 160 @rtype str
160 """ 161 """
162 # return cached entry 163 # return cached entry
163 return self.__workspace 164 return self.__workspace
164 else: 165 else:
165 self.__workspace = self.__findWorkspace(silent=silent) 166 self.__workspace = self.__findWorkspace(silent=silent)
166 return self.__workspace 167 return self.__workspace
167 168
168 def __findWorkspace(self, silent=False): 169 def __findWorkspace(self, silent=False):
169 """ 170 """
170 Private method to find the workspace directory. 171 Private method to find the workspace directory.
171 172
172 @param silent flag indicating silent operations 173 @param silent flag indicating silent operations
173 @type bool 174 @type bool
174 @return workspace directory used for saving files 175 @return workspace directory used for saving files
175 @rtype str 176 @rtype str
176 """ 177 """
177 # Attempts to find the path on the filesystem that represents the 178 # Attempts to find the path on the filesystem that represents the
178 # plugged in PyBoard board. 179 # plugged in PyBoard board.
179 deviceDirectories = Utilities.findVolume(self.DeviceVolumeName, 180 deviceDirectories = Utilities.findVolume(self.DeviceVolumeName, findAll=True)
180 findAll=True) 181
181
182 if deviceDirectories: 182 if deviceDirectories:
183 if len(deviceDirectories) == 1: 183 if len(deviceDirectories) == 1:
184 return deviceDirectories[0] 184 return deviceDirectories[0]
185 else: 185 else:
186 return self.selectDeviceDirectory(deviceDirectories) 186 return self.selectDeviceDirectory(deviceDirectories)
189 # silent mode is selected) 189 # silent mode is selected)
190 if not silent: 190 if not silent:
191 EricMessageBox.warning( 191 EricMessageBox.warning(
192 self.microPython, 192 self.microPython,
193 self.tr("Workspace Directory"), 193 self.tr("Workspace Directory"),
194 self.tr("Python files for PyBoard can be edited in" 194 self.tr(
195 " place, if the device volume is locally" 195 "Python files for PyBoard can be edited in"
196 " available. Such a volume was not found. In" 196 " place, if the device volume is locally"
197 " place editing will not be available." 197 " available. Such a volume was not found. In"
198 ) 198 " place editing will not be available."
199 ),
199 ) 200 )
200 201
201 return super().getWorkspace() 202 return super().getWorkspace()
202 203
203 def getDocumentationUrl(self): 204 def getDocumentationUrl(self):
204 """ 205 """
205 Public method to get the device documentation URL. 206 Public method to get the device documentation URL.
206 207
207 @return documentation URL of the device 208 @return documentation URL of the device
208 @rtype str 209 @rtype str
209 """ 210 """
210 return Preferences.getMicroPython("MicroPythonDocuUrl") 211 return Preferences.getMicroPython("MicroPythonDocuUrl")
211 212
212 def getFirmwareUrl(self): 213 def getFirmwareUrl(self):
213 """ 214 """
214 Public method to get the device firmware download URL. 215 Public method to get the device firmware download URL.
215 216
216 @return firmware download URL of the device 217 @return firmware download URL of the device
217 @rtype str 218 @rtype str
218 """ 219 """
219 return Preferences.getMicroPython("MicroPythonFirmwareUrl") 220 return Preferences.getMicroPython("MicroPythonFirmwareUrl")
220 221
221 def addDeviceMenuEntries(self, menu): 222 def addDeviceMenuEntries(self, menu):
222 """ 223 """
223 Public method to add device specific entries to the given menu. 224 Public method to add device specific entries to the given menu.
224 225
225 @param menu reference to the context menu 226 @param menu reference to the context menu
226 @type QMenu 227 @type QMenu
227 """ 228 """
228 connected = self.microPython.isConnected() 229 connected = self.microPython.isConnected()
229 230
230 act = menu.addAction(self.tr("Activate Bootloader"), 231 act = menu.addAction(self.tr("Activate Bootloader"), self.__activateBootloader)
231 self.__activateBootloader)
232 act.setEnabled(connected) 232 act.setEnabled(connected)
233 act = menu.addAction(self.tr("List DFU-capable Devices"), 233 act = menu.addAction(
234 self.__listDfuCapableDevices) 234 self.tr("List DFU-capable Devices"), self.__listDfuCapableDevices
235 )
235 act.setEnabled(not connected) 236 act.setEnabled(not connected)
236 act = menu.addAction(self.tr("Flash MicroPython Firmware"), 237 act = menu.addAction(
237 self.__flashMicroPython) 238 self.tr("Flash MicroPython Firmware"), self.__flashMicroPython
239 )
238 act.setEnabled(not connected) 240 act.setEnabled(not connected)
239 menu.addSeparator() 241 menu.addSeparator()
240 menu.addAction(self.tr("MicroPython Flash Instructions"), 242 menu.addAction(
241 self.__showFlashInstructions) 243 self.tr("MicroPython Flash Instructions"), self.__showFlashInstructions
242 244 )
245
243 def hasFlashMenuEntry(self): 246 def hasFlashMenuEntry(self):
244 """ 247 """
245 Public method to check, if the device has its own flash menu entry. 248 Public method to check, if the device has its own flash menu entry.
246 249
247 @return flag indicating a specific flash menu entry 250 @return flag indicating a specific flash menu entry
248 @rtype bool 251 @rtype bool
249 """ 252 """
250 return True 253 return True
251 254
252 @pyqtSlot() 255 @pyqtSlot()
253 def __showFlashInstructions(self): 256 def __showFlashInstructions(self):
254 """ 257 """
255 Private slot to open the URL containing instructions for installing 258 Private slot to open the URL containing instructions for installing
256 MicroPython on the pyboard. 259 MicroPython on the pyboard.
257 """ 260 """
258 ericApp().getObject("UserInterface").launchHelpViewer( 261 ericApp().getObject("UserInterface").launchHelpViewer(
259 PyBoardDevice.FlashInstructionsURL) 262 PyBoardDevice.FlashInstructionsURL
260 263 )
264
261 def __dfuUtilAvailable(self): 265 def __dfuUtilAvailable(self):
262 """ 266 """
263 Private method to check the availability of dfu-util. 267 Private method to check the availability of dfu-util.
264 268
265 @return flag indicating the availability of dfu-util 269 @return flag indicating the availability of dfu-util
266 @rtype bool 270 @rtype bool
267 """ 271 """
268 available = False 272 available = False
269 program = Preferences.getMicroPython("DfuUtilPath") 273 program = Preferences.getMicroPython("DfuUtilPath")
272 if Utilities.isinpath(program): 276 if Utilities.isinpath(program):
273 available = True 277 available = True
274 else: 278 else:
275 if Utilities.isExecutable(program): 279 if Utilities.isExecutable(program):
276 available = True 280 available = True
277 281
278 if not available: 282 if not available:
279 EricMessageBox.critical( 283 EricMessageBox.critical(
280 self.microPython, 284 self.microPython,
281 self.tr("dfu-util not available"), 285 self.tr("dfu-util not available"),
282 self.tr("""The dfu-util firmware flashing tool""" 286 self.tr(
283 """ <b>dfu-util</b> cannot be found or is not""" 287 """The dfu-util firmware flashing tool"""
284 """ executable. Ensure it is in the search path""" 288 """ <b>dfu-util</b> cannot be found or is not"""
285 """ or configure it on the MicroPython""" 289 """ executable. Ensure it is in the search path"""
286 """ configuration page.""") 290 """ or configure it on the MicroPython"""
291 """ configuration page."""
292 ),
287 ) 293 )
288 294
289 return available 295 return available
290 296
291 def __showDfuEnableInstructions(self, flash=True): 297 def __showDfuEnableInstructions(self, flash=True):
292 """ 298 """
293 Private method to show some instructions to enable the DFU mode. 299 Private method to show some instructions to enable the DFU mode.
294 300
295 @param flash flag indicating to show a warning message for flashing 301 @param flash flag indicating to show a warning message for flashing
296 @type bool 302 @type bool
297 @return flag indicating OK to continue or abort 303 @return flag indicating OK to continue or abort
298 @rtype bool 304 @rtype bool
299 """ 305 """
303 "<p>2. Disconnect your board</p>" 309 "<p>2. Disconnect your board</p>"
304 "<p>3. Connect the DFU/BOOT0 pin with a 3.3V pin</p>" 310 "<p>3. Connect the DFU/BOOT0 pin with a 3.3V pin</p>"
305 "<p>4. Re-connect your board</p>" 311 "<p>4. Re-connect your board</p>"
306 "<hr />" 312 "<hr />"
307 ) 313 )
308 314
309 if flash: 315 if flash:
310 msg += self.tr( 316 msg += self.tr(
311 "<p><b>Warning:</b> Make sure that all other DFU capable" 317 "<p><b>Warning:</b> Make sure that all other DFU capable"
312 " devices except your PyBoard are disconnected." 318 " devices except your PyBoard are disconnected."
313 "<hr />" 319 "<hr />"
314 ) 320 )
315 321
316 msg += self.tr( 322 msg += self.tr("<p>Press <b>OK</b> to continue...</p>")
317 "<p>Press <b>OK</b> to continue...</p>"
318 )
319 res = EricMessageBox.information( 323 res = EricMessageBox.information(
320 self.microPython, 324 self.microPython,
321 self.tr("Enable DFU mode"), 325 self.tr("Enable DFU mode"),
322 msg, 326 msg,
323 EricMessageBox.Abort | EricMessageBox.Ok) 327 EricMessageBox.Abort | EricMessageBox.Ok,
324 328 )
329
325 return res == EricMessageBox.Ok 330 return res == EricMessageBox.Ok
326 331
327 def __showDfuDisableInstructions(self): 332 def __showDfuDisableInstructions(self):
328 """ 333 """
329 Private method to show some instructions to disable the DFU mode. 334 Private method to show some instructions to disable the DFU mode.
330 """ 335 """
331 msg = self.tr( 336 msg = self.tr(
334 "<p>2. Remove the DFU jumper</p>" 339 "<p>2. Remove the DFU jumper</p>"
335 "<p>3. Re-connect your board</p>" 340 "<p>3. Re-connect your board</p>"
336 "<hr />" 341 "<hr />"
337 "<p>Press <b>OK</b> to continue...</p>" 342 "<p>Press <b>OK</b> to continue...</p>"
338 ) 343 )
339 EricMessageBox.information( 344 EricMessageBox.information(self.microPython, self.tr("Disable DFU mode"), msg)
340 self.microPython, 345
341 self.tr("Disable DFU mode"),
342 msg
343 )
344
345 @pyqtSlot() 346 @pyqtSlot()
346 def __listDfuCapableDevices(self): 347 def __listDfuCapableDevices(self):
347 """ 348 """
348 Private slot to list all DFU-capable devices. 349 Private slot to list all DFU-capable devices.
349 """ 350 """
351 ok2continue = self.__showDfuEnableInstructions(flash=False) 352 ok2continue = self.__showDfuEnableInstructions(flash=False)
352 if ok2continue: 353 if ok2continue:
353 program = Preferences.getMicroPython("DfuUtilPath") 354 program = Preferences.getMicroPython("DfuUtilPath")
354 if not program: 355 if not program:
355 program = "dfu-util" 356 program = "dfu-util"
356 357
357 args = [ 358 args = [
358 "--list", 359 "--list",
359 ] 360 ]
360 dlg = EricProcessDialog( 361 dlg = EricProcessDialog(
361 self.tr("'dfu-util' Output"), 362 self.tr("'dfu-util' Output"), self.tr("List DFU capable Devices")
362 self.tr("List DFU capable Devices")
363 ) 363 )
364 res = dlg.startProcess(program, args) 364 res = dlg.startProcess(program, args)
365 if res: 365 if res:
366 dlg.exec() 366 dlg.exec()
367 367
368 @pyqtSlot() 368 @pyqtSlot()
369 def __flashMicroPython(self): 369 def __flashMicroPython(self):
370 """ 370 """
371 Private slot to flash a MicroPython firmware. 371 Private slot to flash a MicroPython firmware.
372 """ 372 """
374 ok2continue = self.__showDfuEnableInstructions() 374 ok2continue = self.__showDfuEnableInstructions()
375 if ok2continue: 375 if ok2continue:
376 program = Preferences.getMicroPython("DfuUtilPath") 376 program = Preferences.getMicroPython("DfuUtilPath")
377 if not program: 377 if not program:
378 program = "dfu-util" 378 program = "dfu-util"
379 379
380 downloadsPath = QStandardPaths.standardLocations( 380 downloadsPath = QStandardPaths.standardLocations(
381 QStandardPaths.StandardLocation.DownloadLocation)[0] 381 QStandardPaths.StandardLocation.DownloadLocation
382 )[0]
382 firmware = EricFileDialog.getOpenFileName( 383 firmware = EricFileDialog.getOpenFileName(
383 self.microPython, 384 self.microPython,
384 self.tr("Flash MicroPython Firmware"), 385 self.tr("Flash MicroPython Firmware"),
385 downloadsPath, 386 downloadsPath,
386 self.tr( 387 self.tr("MicroPython Firmware Files (*.dfu);;All Files (*)"),
387 "MicroPython Firmware Files (*.dfu);;All Files (*)")
388 ) 388 )
389 if firmware and os.path.exists(firmware): 389 if firmware and os.path.exists(firmware):
390 args = [ 390 args = [
391 "--alt", "0", 391 "--alt",
392 "--download", firmware, 392 "0",
393 "--download",
394 firmware,
393 ] 395 ]
394 dlg = EricProcessDialog( 396 dlg = EricProcessDialog(
395 self.tr("'dfu-util' Output"), 397 self.tr("'dfu-util' Output"),
396 self.tr("Flash MicroPython Firmware") 398 self.tr("Flash MicroPython Firmware"),
397 ) 399 )
398 res = dlg.startProcess(program, args) 400 res = dlg.startProcess(program, args)
399 if res: 401 if res:
400 dlg.exec() 402 dlg.exec()
401 self.__showDfuDisableInstructions() 403 self.__showDfuDisableInstructions()
402 404
403 @pyqtSlot() 405 @pyqtSlot()
404 def __activateBootloader(self): 406 def __activateBootloader(self):
405 """ 407 """
406 Private slot to activate the bootloader and disconnect. 408 Private slot to activate the bootloader and disconnect.
407 """ 409 """
408 if self.microPython.isConnected(): 410 if self.microPython.isConnected():
409 self.microPython.commandsInterface().execute([ 411 self.microPython.commandsInterface().execute(
410 "import pyb", 412 [
411 "pyb.bootloader()", 413 "import pyb",
412 ]) 414 "pyb.bootloader()",
415 ]
416 )
413 # simulate pressing the disconnect button 417 # simulate pressing the disconnect button
414 self.microPython.on_connectButton_clicked() 418 self.microPython.on_connectButton_clicked()

eric ide

mercurial