1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2019 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the device interface class for BBC micro:bit and |
|
8 Calliope mini boards. |
|
9 """ |
|
10 |
|
11 import contextlib |
|
12 import os |
|
13 import shutil |
|
14 |
|
15 from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot |
|
16 from PyQt6.QtNetwork import QNetworkRequest |
|
17 from PyQt6.QtWidgets import QInputDialog, QLineEdit, QMenu |
|
18 |
|
19 from eric7 import Globals, Preferences |
|
20 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
|
21 from eric7.EricWidgets.EricApplication import ericApp |
|
22 from eric7.SystemUtilities import FileSystemUtilities |
|
23 |
|
24 from .MicroPythonDevices import FirmwareGithubUrls, MicroPythonDevice |
|
25 from .MicroPythonWidget import HAS_QTCHART |
|
26 |
|
27 |
|
28 class MicrobitDevice(MicroPythonDevice): |
|
29 """ |
|
30 Class implementing the device for BBC micro:bit and Calliope mini boards. |
|
31 """ |
|
32 |
|
33 def __init__(self, microPythonWidget, deviceType, serialNumber, parent=None): |
|
34 """ |
|
35 Constructor |
|
36 |
|
37 @param microPythonWidget reference to the main MicroPython widget |
|
38 @type MicroPythonWidget |
|
39 @param deviceType type of the device |
|
40 @type str |
|
41 @param serialNumber serial number of the board |
|
42 @type str |
|
43 @param parent reference to the parent object |
|
44 @type QObject |
|
45 """ |
|
46 super().__init__(microPythonWidget, deviceType, parent) |
|
47 |
|
48 self.__boardId = 0 # illegal ID |
|
49 if serialNumber: |
|
50 with contextlib.suppress(ValueError): |
|
51 self.__boardId = int(serialNumber[:4], 16) |
|
52 |
|
53 self.__createMicrobitMenu() |
|
54 |
|
55 def setButtons(self): |
|
56 """ |
|
57 Public method to enable the supported action buttons. |
|
58 """ |
|
59 super().setButtons() |
|
60 self.microPython.setActionButtons( |
|
61 run=True, repl=True, files=True, chart=HAS_QTCHART |
|
62 ) |
|
63 |
|
64 def forceInterrupt(self): |
|
65 """ |
|
66 Public method to determine the need for an interrupt when opening the |
|
67 serial connection. |
|
68 |
|
69 @return flag indicating an interrupt is needed |
|
70 @rtype bool |
|
71 """ |
|
72 return True |
|
73 |
|
74 def deviceName(self): |
|
75 """ |
|
76 Public method to get the name of the device. |
|
77 |
|
78 @return name of the device |
|
79 @rtype str |
|
80 """ |
|
81 if self.getDeviceType() == "bbc_microbit": |
|
82 # BBC micro:bit |
|
83 return self.tr("BBC micro:bit") |
|
84 else: |
|
85 # Calliope mini |
|
86 return self.tr("Calliope mini") |
|
87 |
|
88 def canStartRepl(self): |
|
89 """ |
|
90 Public method to determine, if a REPL can be started. |
|
91 |
|
92 @return tuple containing a flag indicating it is safe to start a REPL |
|
93 and a reason why it cannot. |
|
94 @rtype tuple of (bool, str) |
|
95 """ |
|
96 return True, "" |
|
97 |
|
98 def canStartPlotter(self): |
|
99 """ |
|
100 Public method to determine, if a Plotter can be started. |
|
101 |
|
102 @return tuple containing a flag indicating it is safe to start a |
|
103 Plotter and a reason why it cannot. |
|
104 @rtype tuple of (bool, str) |
|
105 """ |
|
106 return True, "" |
|
107 |
|
108 def canRunScript(self): |
|
109 """ |
|
110 Public method to determine, if a script can be executed. |
|
111 |
|
112 @return tuple containing a flag indicating it is safe to start a |
|
113 Plotter and a reason why it cannot. |
|
114 @rtype tuple of (bool, str) |
|
115 """ |
|
116 return True, "" |
|
117 |
|
118 def runScript(self, script): |
|
119 """ |
|
120 Public method to run the given Python script. |
|
121 |
|
122 @param script script to be executed |
|
123 @type str |
|
124 """ |
|
125 pythonScript = script.split("\n") |
|
126 self.sendCommands(pythonScript) |
|
127 |
|
128 def canStartFileManager(self): |
|
129 """ |
|
130 Public method to determine, if a File Manager can be started. |
|
131 |
|
132 @return tuple containing a flag indicating it is safe to start a |
|
133 File Manager and a reason why it cannot. |
|
134 @rtype tuple of (bool, str) |
|
135 """ |
|
136 return True, "" |
|
137 |
|
138 def hasTimeCommands(self): |
|
139 """ |
|
140 Public method to check, if the device supports time commands. |
|
141 |
|
142 The default returns True. |
|
143 |
|
144 @return flag indicating support for time commands |
|
145 @rtype bool |
|
146 """ |
|
147 if ( |
|
148 self.microPython.isConnected() |
|
149 and self.checkDeviceData() |
|
150 and self._deviceData["mpy_name"] == "circuitpython" |
|
151 ): |
|
152 return True |
|
153 |
|
154 return False |
|
155 |
|
156 def __isMicroBitV1(self): |
|
157 """ |
|
158 Private method to check, if the device is a BBC micro:bit v1. |
|
159 |
|
160 @return falg indicating a BBC micro:bit v1 |
|
161 @rtype bool |
|
162 """ |
|
163 return self.__boardId in (0x9900, 0x9901) |
|
164 |
|
165 def __isMicroBitV2(self): |
|
166 """ |
|
167 Private method to check, if the device is a BBC micro:bit v2. |
|
168 |
|
169 @return falg indicating a BBC micro:bit v2 |
|
170 @rtype bool |
|
171 """ |
|
172 return self.__boardId in (0x9903, 0x9904, 0x9905, 0x9906) |
|
173 |
|
174 def __isCalliope(self): |
|
175 """ |
|
176 Private method to check, if the device is a Calliope mini. |
|
177 |
|
178 @return flag indicating a Calliope mini |
|
179 @rtype bool |
|
180 """ |
|
181 return self.__boardId in (0x12A0,) |
|
182 |
|
183 def __createMicrobitMenu(self): |
|
184 """ |
|
185 Private method to create the microbit submenu. |
|
186 """ |
|
187 self.__microbitMenu = QMenu(self.tr("BBC micro:bit/Calliope Functions")) |
|
188 |
|
189 self.__showMpyAct = self.__microbitMenu.addAction( |
|
190 self.tr("Show MicroPython Versions"), self.__showFirmwareVersions |
|
191 ) |
|
192 self.__microbitMenu.addSeparator() |
|
193 self.__flashMpyAct = self.__microbitMenu.addAction( |
|
194 self.tr("Flash MicroPython"), self.__flashMicroPython |
|
195 ) |
|
196 self.__flashDAPLinkAct = self.__microbitMenu.addAction( |
|
197 self.tr("Flash Firmware"), lambda: self.__flashMicroPython(firmware=True) |
|
198 ) |
|
199 self.__microbitMenu.addSeparator() |
|
200 self.__saveScripAct = self.__microbitMenu.addAction( |
|
201 self.tr("Save Script"), self.__saveScriptToDevice |
|
202 ) |
|
203 self.__saveScripAct.setToolTip( |
|
204 self.tr("Save the current script to the selected device") |
|
205 ) |
|
206 self.__saveMainScriptAct = self.__microbitMenu.addAction( |
|
207 self.tr("Save Script as 'main.py'"), self.__saveMain |
|
208 ) |
|
209 self.__saveMainScriptAct.setToolTip( |
|
210 self.tr("Save the current script as 'main.py' on the connected device") |
|
211 ) |
|
212 self.__microbitMenu.addSeparator() |
|
213 self.__resetAct = self.__microbitMenu.addAction( |
|
214 self.tr("Reset {0}").format(self.deviceName()), self.__resetDevice |
|
215 ) |
|
216 |
|
217 def addDeviceMenuEntries(self, menu): |
|
218 """ |
|
219 Public method to add device specific entries to the given menu. |
|
220 |
|
221 @param menu reference to the context menu |
|
222 @type QMenu |
|
223 """ |
|
224 connected = self.microPython.isConnected() |
|
225 linkConnected = self.microPython.isLinkConnected() |
|
226 |
|
227 self.__showMpyAct.setEnabled(connected and self.getDeviceType() != "calliope") |
|
228 self.__flashMpyAct.setEnabled(not linkConnected) |
|
229 self.__flashDAPLinkAct.setEnabled(not linkConnected) |
|
230 self.__saveScripAct.setEnabled(connected) |
|
231 self.__saveMainScriptAct.setEnabled(connected) |
|
232 self.__resetAct.setEnabled(connected) |
|
233 |
|
234 menu.addMenu(self.__microbitMenu) |
|
235 |
|
236 def hasFlashMenuEntry(self): |
|
237 """ |
|
238 Public method to check, if the device has its own flash menu entry. |
|
239 |
|
240 @return flag indicating a specific flash menu entry |
|
241 @rtype bool |
|
242 """ |
|
243 return True |
|
244 |
|
245 @pyqtSlot() |
|
246 def __flashMicroPython(self, firmware=False): |
|
247 """ |
|
248 Private slot to flash MicroPython or the DAPLink firmware to the |
|
249 device. |
|
250 |
|
251 @param firmware flag indicating to flash the DAPLink firmware |
|
252 @type bool |
|
253 """ |
|
254 # Attempts to find the path on the file system that represents the |
|
255 # plugged in micro:bit board. To flash the DAPLink firmware, it must be |
|
256 # in maintenance mode, for MicroPython in standard mode. |
|
257 if self.getDeviceType() == "bbc_microbit": |
|
258 # BBC micro:bit |
|
259 if firmware: |
|
260 deviceDirectories = FileSystemUtilities.findVolume( |
|
261 "MAINTENANCE", findAll=True |
|
262 ) |
|
263 else: |
|
264 deviceDirectories = FileSystemUtilities.findVolume( |
|
265 "MICROBIT", findAll=True |
|
266 ) |
|
267 else: |
|
268 # Calliope mini |
|
269 if firmware: |
|
270 deviceDirectories = FileSystemUtilities.findVolume( |
|
271 "MAINTENANCE", findAll=True |
|
272 ) |
|
273 else: |
|
274 deviceDirectories = FileSystemUtilities.findVolume("MINI", findAll=True) |
|
275 if len(deviceDirectories) == 0: |
|
276 if self.getDeviceType() == "bbc_microbit": |
|
277 # BBC micro:bit is not ready or not mounted |
|
278 if firmware: |
|
279 EricMessageBox.critical( |
|
280 self.microPython, |
|
281 self.tr("Flash MicroPython/Firmware"), |
|
282 self.tr( |
|
283 "<p>The BBC micro:bit is not ready for flashing" |
|
284 " the DAPLink firmware. Follow these" |
|
285 " instructions. </p>" |
|
286 "<ul>" |
|
287 "<li>unplug USB cable and any batteries</li>" |
|
288 "<li>keep RESET button pressed and plug USB cable" |
|
289 " back in</li>" |
|
290 "<li>a drive called MAINTENANCE should be" |
|
291 " available</li>" |
|
292 "</ul>" |
|
293 "<p>See the " |
|
294 '<a href="https://microbit.org/guide/firmware/">' |
|
295 "micro:bit web site</a> for details.</p>" |
|
296 ), |
|
297 ) |
|
298 else: |
|
299 EricMessageBox.critical( |
|
300 self.microPython, |
|
301 self.tr("Flash MicroPython/Firmware"), |
|
302 self.tr( |
|
303 "<p>The BBC micro:bit is not ready for flashing" |
|
304 " the MicroPython firmware. Please make sure," |
|
305 " that a drive called MICROBIT is available." |
|
306 "</p>" |
|
307 ), |
|
308 ) |
|
309 else: |
|
310 # Calliope mini is not ready or not mounted |
|
311 if firmware: |
|
312 EricMessageBox.critical( |
|
313 self.microPython, |
|
314 self.tr("Flash MicroPython/Firmware"), |
|
315 self.tr( |
|
316 '<p>The "Calliope mini" is not ready for flashing' |
|
317 " the DAPLink firmware. Follow these" |
|
318 " instructions. </p>" |
|
319 "<ul>" |
|
320 "<li>unplug USB cable and any batteries</li>" |
|
321 "<li>keep RESET button pressed an plug USB cable" |
|
322 " back in</li>" |
|
323 "<li>a drive called MAINTENANCE should be" |
|
324 " available</li>" |
|
325 "</ul>" |
|
326 ), |
|
327 ) |
|
328 else: |
|
329 EricMessageBox.critical( |
|
330 self.microPython, |
|
331 self.tr("Flash MicroPython/Firmware"), |
|
332 self.tr( |
|
333 '<p>The "Calliope mini" is not ready for flashing' |
|
334 " the MicroPython firmware. Please make sure," |
|
335 " that a drive called MINI is available." |
|
336 "</p>" |
|
337 ), |
|
338 ) |
|
339 elif len(deviceDirectories) == 1: |
|
340 downloadsPath = QStandardPaths.standardLocations( |
|
341 QStandardPaths.StandardLocation.DownloadLocation |
|
342 )[0] |
|
343 firmware = EricFileDialog.getOpenFileName( |
|
344 self.microPython, |
|
345 self.tr("Flash MicroPython/Firmware"), |
|
346 downloadsPath, |
|
347 self.tr("MicroPython/Firmware Files (*.hex *.bin);;All Files (*)"), |
|
348 ) |
|
349 if firmware and os.path.exists(firmware): |
|
350 shutil.copy2(firmware, deviceDirectories[0]) |
|
351 else: |
|
352 EricMessageBox.warning( |
|
353 self, |
|
354 self.tr("Flash MicroPython/Firmware"), |
|
355 self.tr( |
|
356 "There are multiple devices ready for flashing." |
|
357 " Please make sure, that only one device is prepared." |
|
358 ), |
|
359 ) |
|
360 |
|
361 @pyqtSlot() |
|
362 def __showFirmwareVersions(self): |
|
363 """ |
|
364 Private slot to show the firmware version of the connected device and the |
|
365 available firmware version. |
|
366 """ |
|
367 if self.microPython.isConnected() and self.checkDeviceData(): |
|
368 if self._deviceData["mpy_name"] not in ("micropython", "circuitpython"): |
|
369 EricMessageBox.critical( |
|
370 None, |
|
371 self.tr("Show MicroPython Versions"), |
|
372 self.tr( |
|
373 """The firmware of the connected device cannot be""" |
|
374 """ determined or the board does not run MicroPython""" |
|
375 """ or CircuitPython. Aborting...""" |
|
376 ), |
|
377 ) |
|
378 else: |
|
379 if self.getDeviceType() == "bbc_microbit": |
|
380 if self._deviceData["mpy_name"] == "micropython": |
|
381 if self.__isMicroBitV1(): |
|
382 url = QUrl(FirmwareGithubUrls["microbit_v1"]) |
|
383 elif self.__isMicroBitV2(): |
|
384 url = QUrl(FirmwareGithubUrls["microbit_v2"]) |
|
385 else: |
|
386 EricMessageBox.critical( |
|
387 None, |
|
388 self.tr("Show MicroPython Versions"), |
|
389 self.tr( |
|
390 """<p>The BBC micro:bit generation cannot be""" |
|
391 """ determined. Aborting...</p>""" |
|
392 ), |
|
393 ) |
|
394 return |
|
395 elif self._deviceData["mpy_name"] == "circuitpython": |
|
396 url = QUrl(FirmwareGithubUrls["circuitpython"]) |
|
397 else: |
|
398 EricMessageBox.critical( |
|
399 None, |
|
400 self.tr("Show MicroPython Versions"), |
|
401 self.tr( |
|
402 """<p>The firmware URL for the device type <b>{0}</b>""" |
|
403 """ is not known. Aborting...</p>""" |
|
404 ).format(self.getDeviceType()), |
|
405 ) |
|
406 return |
|
407 |
|
408 ui = ericApp().getObject("UserInterface") |
|
409 request = QNetworkRequest(url) |
|
410 reply = ui.networkAccessManager().head(request) |
|
411 reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) |
|
412 |
|
413 def __firmwareVersionResponse(self, reply): |
|
414 """ |
|
415 Private method handling the response of the latest version request. |
|
416 |
|
417 @param reply reference to the reply object |
|
418 @type QNetworkReply |
|
419 """ |
|
420 latestUrl = reply.url().toString() |
|
421 tag = latestUrl.rsplit("/", 1)[-1] |
|
422 while tag and not tag[0].isdecimal(): |
|
423 # get rid of leading non-decimal characters |
|
424 tag = tag[1:] |
|
425 latestVersion = Globals.versionToTuple(tag) |
|
426 |
|
427 if self._deviceData["release"] == "unknown": |
|
428 currentVersionStr = self.tr("unknown") |
|
429 currentVersion = (0, 0, 0) |
|
430 else: |
|
431 currentVersionStr = self._deviceData["release"] |
|
432 currentVersion = Globals.versionToTuple(currentVersionStr) |
|
433 |
|
434 if self._deviceData["mpy_name"] == "circuitpython": |
|
435 kind = "CircuitPython" |
|
436 microbitVersion = "2" # only v2 device can run CircuitPython |
|
437 elif self._deviceData["mpy_name"] == "micropython": |
|
438 kind = "MicroPython" |
|
439 if self.__isMicroBitV1(): |
|
440 microbitVersion = "1" |
|
441 elif self.__isMicroBitV2(): |
|
442 microbitVersion = "2" |
|
443 else: |
|
444 kind = self.tr("Firmware") |
|
445 microbitVersion = "?" |
|
446 |
|
447 msg = self.tr( |
|
448 "<h4>{0} Version Information<br/>" |
|
449 "(BBC micro:bit v{1})</h4>" |
|
450 "<table>" |
|
451 "<tr><td>Installed:</td><td>{2}</td></tr>" |
|
452 "<tr><td>Available:</td><td>{3}</td></tr>" |
|
453 "</table>" |
|
454 ).format(kind, microbitVersion, currentVersionStr, tag) |
|
455 if currentVersion < latestVersion: |
|
456 msg += self.tr("<p><b>Update available!</b></p>") |
|
457 |
|
458 EricMessageBox.information( |
|
459 None, |
|
460 self.tr("{0} Version").format(kind), |
|
461 msg, |
|
462 ) |
|
463 |
|
464 @pyqtSlot() |
|
465 def __saveMain(self): |
|
466 """ |
|
467 Private slot to copy the current script as 'main.py' onto the |
|
468 connected device. |
|
469 """ |
|
470 self.__saveScriptToDevice("main.py") |
|
471 |
|
472 @pyqtSlot() |
|
473 def __saveScriptToDevice(self, scriptName=""): |
|
474 """ |
|
475 Private method to save the current script onto the connected |
|
476 device. |
|
477 |
|
478 @param scriptName name of the file on the device |
|
479 @type str |
|
480 """ |
|
481 aw = ericApp().getObject("ViewManager").activeWindow() |
|
482 if not aw: |
|
483 return |
|
484 |
|
485 title = ( |
|
486 self.tr("Save Script as '{0}'").format(scriptName) |
|
487 if scriptName |
|
488 else self.tr("Save Script") |
|
489 ) |
|
490 |
|
491 if not (aw.isPyFile() or aw.isMicroPythonFile()): |
|
492 yes = EricMessageBox.yesNo( |
|
493 self.microPython, |
|
494 title, |
|
495 self.tr( |
|
496 """The current editor does not contain a Python""" |
|
497 """ script. Write it anyway?""" |
|
498 ), |
|
499 ) |
|
500 if not yes: |
|
501 return |
|
502 |
|
503 script = aw.text().strip() |
|
504 if not script: |
|
505 EricMessageBox.warning( |
|
506 self.microPython, title, self.tr("""The script is empty. Aborting.""") |
|
507 ) |
|
508 return |
|
509 |
|
510 if not scriptName: |
|
511 scriptName = os.path.basename(aw.getFileName()) |
|
512 scriptName, ok = QInputDialog.getText( |
|
513 self.microPython, |
|
514 title, |
|
515 self.tr("Enter a file name on the device:"), |
|
516 QLineEdit.EchoMode.Normal, |
|
517 scriptName, |
|
518 ) |
|
519 if not ok or not bool(scriptName): |
|
520 return |
|
521 |
|
522 title = self.tr("Save Script as '{0}'").format(scriptName) |
|
523 |
|
524 commands = [ |
|
525 "fd = open('{0}', 'wb')".format(scriptName), |
|
526 "f = fd.write", |
|
527 ] |
|
528 for line in script.splitlines(): |
|
529 commands.append("f(" + repr(line + "\n") + ")") |
|
530 commands.append("fd.close()") |
|
531 out, err = self.microPython.commandsInterface().execute(commands) |
|
532 if err: |
|
533 EricMessageBox.critical( |
|
534 self.microPython, |
|
535 title, |
|
536 self.tr( |
|
537 """<p>The script could not be saved to the""" |
|
538 """ device.</p><p>Reason: {0}</p>""" |
|
539 ).format(err.decode("utf-8")), |
|
540 ) |
|
541 |
|
542 # reset the device |
|
543 self.__resetDevice() |
|
544 |
|
545 @pyqtSlot() |
|
546 def __resetDevice(self): |
|
547 """ |
|
548 Private slot to reset the connected device. |
|
549 """ |
|
550 if self.getDeviceType() == "bbc_microbit": |
|
551 # BBC micro:bit |
|
552 self.microPython.commandsInterface().execute( |
|
553 [ |
|
554 "import microbit", |
|
555 "microbit.reset()", |
|
556 ] |
|
557 ) |
|
558 else: |
|
559 # Calliope mini |
|
560 self.microPython.commandsInterface().execute( |
|
561 [ |
|
562 "import calliope_mini", |
|
563 "calliope_mini.reset()", |
|
564 ] |
|
565 ) |
|
566 |
|
567 def getDocumentationUrl(self): |
|
568 """ |
|
569 Public method to get the device documentation URL. |
|
570 |
|
571 @return documentation URL of the device |
|
572 @rtype str |
|
573 """ |
|
574 if self.getDeviceType() == "bbc_microbit": |
|
575 # BBC micro:bit |
|
576 if self._deviceData and self._deviceData["mpy_name"] == "circuitpython": |
|
577 return Preferences.getMicroPython("CircuitPythonDocuUrl") |
|
578 else: |
|
579 return Preferences.getMicroPython("MicrobitDocuUrl") |
|
580 else: |
|
581 # Calliope mini |
|
582 return Preferences.getMicroPython("CalliopeDocuUrl") |
|
583 |
|
584 def getDownloadMenuEntries(self): |
|
585 """ |
|
586 Public method to retrieve the entries for the downloads menu. |
|
587 |
|
588 @return list of tuples with menu text and URL to be opened for each |
|
589 entry |
|
590 @rtype list of tuple of (str, str) |
|
591 """ |
|
592 if self.getDeviceType() == "bbc_microbit": |
|
593 if self.__isMicroBitV1(): |
|
594 return [ |
|
595 ( |
|
596 self.tr("MicroPython Firmware for BBC micro:bit V1"), |
|
597 Preferences.getMicroPython("MicrobitMicroPythonUrl"), |
|
598 ), |
|
599 ( |
|
600 self.tr("DAPLink Firmware"), |
|
601 Preferences.getMicroPython("MicrobitFirmwareUrl"), |
|
602 ), |
|
603 ] |
|
604 elif self.__isMicroBitV2(): |
|
605 return [ |
|
606 ( |
|
607 self.tr("MicroPython Firmware for BBC micro:bit V2"), |
|
608 Preferences.getMicroPython("MicrobitV2MicroPythonUrl"), |
|
609 ), |
|
610 ( |
|
611 self.tr("CircuitPython Firmware for BBC micro:bit V2"), |
|
612 "https://circuitpython.org/board/microbit_v2/", |
|
613 ), |
|
614 ( |
|
615 self.tr("DAPLink Firmware"), |
|
616 Preferences.getMicroPython("MicrobitFirmwareUrl"), |
|
617 ), |
|
618 ] |
|
619 else: |
|
620 return [] |
|
621 else: |
|
622 return [ |
|
623 ( |
|
624 self.tr("MicroPython Firmware"), |
|
625 Preferences.getMicroPython("CalliopeMicroPythonUrl"), |
|
626 ), |
|
627 ( |
|
628 self.tr("DAPLink Firmware"), |
|
629 Preferences.getMicroPython("CalliopeDAPLinkUrl"), |
|
630 ), |
|
631 ] |
|
632 |
|
633 |
|
634 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): |
|
635 """ |
|
636 Function to instantiate a MicroPython device object. |
|
637 |
|
638 @param microPythonWidget reference to the main MicroPython widget |
|
639 @type MicroPythonWidget |
|
640 @param deviceType device type assigned to this device interface |
|
641 @type str |
|
642 @param vid vendor ID |
|
643 @type int |
|
644 @param pid product ID |
|
645 @type int |
|
646 @param boardName name of the board |
|
647 @type str |
|
648 @param serialNumber serial number of the board |
|
649 @type str |
|
650 @return reference to the instantiated device object |
|
651 @rtype MicrobitDevice |
|
652 """ |
|
653 return MicrobitDevice(microPythonWidget, deviceType, serialNumber) |
|