27 class MicrobitDevice(MicroPythonDevice): |
28 class MicrobitDevice(MicroPythonDevice): |
28 """ |
29 """ |
29 Class implementing the device for BBC micro:bit and Calliope mini boards. |
30 Class implementing the device for BBC micro:bit and Calliope mini boards. |
30 """ |
31 """ |
31 |
32 |
32 def __init__(self, microPythonWidget, deviceType, parent=None): |
33 def __init__(self, microPythonWidget, deviceType, serialNumber, parent=None): |
33 """ |
34 """ |
34 Constructor |
35 Constructor |
35 |
36 |
36 @param microPythonWidget reference to the main MicroPython widget |
37 @param microPythonWidget reference to the main MicroPython widget |
37 @type MicroPythonWidget |
38 @type MicroPythonWidget |
38 @param deviceType type of the device |
39 @param deviceType type of the device |
39 @type str |
40 @type str |
|
41 @param serialNumber serial number of the board |
|
42 @type str |
40 @param parent reference to the parent object |
43 @param parent reference to the parent object |
41 @type QObject |
44 @type QObject |
42 """ |
45 """ |
43 super().__init__(microPythonWidget, deviceType, parent) |
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) |
44 |
52 |
45 def setButtons(self): |
53 def setButtons(self): |
46 """ |
54 """ |
47 Public method to enable the supported action buttons. |
55 Public method to enable the supported action buttons. |
48 """ |
56 """ |
132 The default returns True. |
140 The default returns True. |
133 |
141 |
134 @return flag indicating support for time commands |
142 @return flag indicating support for time commands |
135 @rtype bool |
143 @rtype bool |
136 """ |
144 """ |
|
145 if ( |
|
146 self.microPython.isConnected() |
|
147 and self.checkDeviceData() |
|
148 and self._deviceData["mpy_name"] == "circuitpython" |
|
149 ): |
|
150 return True |
|
151 |
137 return False |
152 return False |
|
153 |
|
154 def __isMicroBitV1(self): |
|
155 """ |
|
156 Private method to check, if the device is a BBC micro:bit v1. |
|
157 |
|
158 @return falg indicating a BBC micro:bit v1 |
|
159 @rtype bool |
|
160 """ |
|
161 return self.__boardId in (0x9900, 0x9901) |
|
162 |
|
163 def __isMicroBitV2(self): |
|
164 """ |
|
165 Private method to check, if the device is a BBC micro:bit v2. |
|
166 |
|
167 @return falg indicating a BBC micro:bit v2 |
|
168 @rtype bool |
|
169 """ |
|
170 return self.__boardId in (0x9903, 0x9904, 0x9905, 0x9906) |
|
171 |
|
172 def __isCalliope(self): |
|
173 """ |
|
174 Private method to check, if the device is a Calliope mini. |
|
175 |
|
176 @return flag indicating a Calliope mini |
|
177 @rtype bool |
|
178 """ |
|
179 return self.__boardId in (0x12A0,) |
138 |
180 |
139 def addDeviceMenuEntries(self, menu): |
181 def addDeviceMenuEntries(self, menu): |
140 """ |
182 """ |
141 Public method to add device specific entries to the given menu. |
183 Public method to add device specific entries to the given menu. |
142 |
184 |
298 def __showFirmwareVersions(self): |
340 def __showFirmwareVersions(self): |
299 """ |
341 """ |
300 Private slot to show the firmware version of the connected device and the |
342 Private slot to show the firmware version of the connected device and the |
301 available firmware version. |
343 available firmware version. |
302 """ |
344 """ |
303 if self.microPython.isConnected(): |
345 if self.microPython.isConnected() and self.checkDeviceData(): |
304 interface = self.microPython.commandsInterface() |
346 if self._deviceData["mpy_name"] not in ("micropython", "circuitpython"): |
305 if interface is not None: |
347 EricMessageBox.critical( |
306 impInfo = interface.getImplementation() |
348 None, |
307 versionInfo = interface.version() |
349 self.tr("Show MicroPython Versions"), |
308 if impInfo["name"] != "micropython": |
350 self.tr( |
309 EricMessageBox.critical( |
351 """The firmware of the connected device cannot be""" |
310 None, |
352 """ determined or the board does not run MicroPython""" |
311 self.tr("Show MicroPython Versions"), |
353 """ or CircuitPython. Aborting...""" |
312 self.tr( |
354 ), |
313 """The firmware of the connected device cannot be""" |
355 ) |
314 """ determined or the board does not run MicroPython.""" |
356 else: |
315 """ Aborting...""" |
357 if self.getDeviceType() == "bbc_microbit": |
316 ), |
358 if self._deviceData["mpy_name"] == "micropython": |
317 ) |
359 if self.__isMicroBitV1(): |
318 else: |
|
319 impInfo["version"] = versionInfo["release"] |
|
320 if self.getDeviceType() == "bbc_microbit": |
|
321 if "nRF51" in versionInfo["machine"]: |
|
322 url = QUrl(FirmwareGithubUrls["microbit_v1"]) |
360 url = QUrl(FirmwareGithubUrls["microbit_v1"]) |
323 elif "nRF52" in versionInfo["machine"]: |
361 elif self.__isMicroBitV2(): |
324 url = QUrl(FirmwareGithubUrls["microbit_v2"]) |
362 url = QUrl(FirmwareGithubUrls["microbit_v2"]) |
325 else: |
363 else: |
326 EricMessageBox.critical( |
364 EricMessageBox.critical( |
327 None, |
365 None, |
328 self.tr("Show MicroPython Versions"), |
366 self.tr("Show MicroPython Versions"), |
330 """<p>The BBC micro:bit generation cannot be""" |
368 """<p>The BBC micro:bit generation cannot be""" |
331 """ determined. Aborting...</p>""" |
369 """ determined. Aborting...</p>""" |
332 ), |
370 ), |
333 ) |
371 ) |
334 return |
372 return |
335 else: |
373 elif self._deviceData["mpy_name"] == "circuitpython": |
336 EricMessageBox.critical( |
374 url = QUrl(FirmwareGithubUrls["circuitpython"]) |
337 None, |
375 else: |
338 self.tr("Show MicroPython Versions"), |
376 EricMessageBox.critical( |
339 self.tr( |
377 None, |
340 """<p>The firmware URL for the device type <b>{0}</b>""" |
378 self.tr("Show MicroPython Versions"), |
341 """ is not known. Aborting...</p>""" |
379 self.tr( |
342 ).format(self.getDeviceType()), |
380 """<p>The firmware URL for the device type <b>{0}</b>""" |
343 ) |
381 """ is not known. Aborting...</p>""" |
344 return |
382 ).format(self.getDeviceType()), |
345 |
|
346 ui = ericApp().getObject("UserInterface") |
|
347 request = QNetworkRequest(url) |
|
348 reply = ui.networkAccessManager().head(request) |
|
349 reply.finished.connect( |
|
350 lambda: self.__firmwareVersionResponse(reply, impInfo) |
|
351 ) |
383 ) |
352 |
384 return |
353 def __firmwareVersionResponse(self, reply, implementation): |
385 |
|
386 ui = ericApp().getObject("UserInterface") |
|
387 request = QNetworkRequest(url) |
|
388 reply = ui.networkAccessManager().head(request) |
|
389 reply.finished.connect(lambda: self.__firmwareVersionResponse(reply)) |
|
390 |
|
391 def __firmwareVersionResponse(self, reply): |
354 """ |
392 """ |
355 Private method handling the response of the latest version request. |
393 Private method handling the response of the latest version request. |
356 |
394 |
357 @param reply reference to the reply object |
395 @param reply reference to the reply object |
358 @type QNetworkReply |
396 @type QNetworkReply |
359 @param implementation dictionary containing the implementation data of the |
|
360 connected device |
|
361 @type dict |
|
362 """ |
397 """ |
363 latestUrl = reply.url().toString() |
398 latestUrl = reply.url().toString() |
364 tag = latestUrl.rsplit("/", 1)[-1] |
399 tag = latestUrl.rsplit("/", 1)[-1] |
365 while tag and not tag[0].isdecimal(): |
400 while tag and not tag[0].isdecimal(): |
366 # get rid of leading non-decimal characters |
401 # get rid of leading non-decimal characters |
367 tag = tag[1:] |
402 tag = tag[1:] |
368 latestVersion = Globals.versionToTuple(tag) |
403 latestVersion = Globals.versionToTuple(tag) |
369 |
404 |
370 if implementation["version"] == "unknown": |
405 if self._deviceData["release"] == "unknown": |
371 currentVersionStr = self.tr("unknown") |
406 currentVersionStr = self.tr("unknown") |
372 currentVersion = (0, 0, 0) |
407 currentVersion = (0, 0, 0) |
373 else: |
408 else: |
374 currentVersionStr = implementation["version"] |
409 currentVersionStr = self._deviceData["release"] |
375 currentVersion = Globals.versionToTuple(currentVersionStr) |
410 currentVersion = Globals.versionToTuple(currentVersionStr) |
376 |
411 |
|
412 if self._deviceData["mpy_name"] == "circuitpython": |
|
413 kind = "CircuitPython" |
|
414 microbitVersion = "2" # only v2 device can run CircuitPython |
|
415 elif self._deviceData["mpy_name"] == "micropython": |
|
416 kind = "MicroPython" |
|
417 if self.__isMicroBitV1(): |
|
418 microbitVersion = "1" |
|
419 elif self.__isMicroBitV2(): |
|
420 microbitVersion = "2" |
|
421 else: |
|
422 kind = self.tr("Firmware") |
|
423 microbitVersion = "?" |
|
424 |
377 msg = self.tr( |
425 msg = self.tr( |
378 "<h4>MicroPython Version Information<br/>" |
426 "<h4>{0} Version Information<br/>" |
379 "(BBC micro:bit v{2})</h4>" |
427 "(BBC micro:bit v{1})</h4>" |
380 "<table>" |
428 "<table>" |
381 "<tr><td>Installed:</td><td>{0}</td></tr>" |
429 "<tr><td>Installed:</td><td>{2}</td></tr>" |
382 "<tr><td>Available:</td><td>{1}</td></tr>" |
430 "<tr><td>Available:</td><td>{3}</td></tr>" |
383 "</table>" |
431 "</table>" |
384 ).format(currentVersionStr, tag, currentVersionStr[0]) |
432 ).format(kind, microbitVersion, currentVersionStr, tag) |
385 if currentVersion < latestVersion: |
433 if currentVersion < latestVersion: |
386 msg += self.tr("<p><b>Update available!</b></p>") |
434 msg += self.tr("<p><b>Update available!</b></p>") |
387 |
435 |
388 EricMessageBox.information( |
436 EricMessageBox.information( |
389 None, |
437 None, |
390 self.tr("MicroPython Version"), |
438 self.tr("{0} Version").format(kind), |
391 msg, |
439 msg, |
392 ) |
440 ) |
393 |
441 |
394 @pyqtSlot() |
442 @pyqtSlot() |
395 def __saveMain(self): |
443 def __saveMain(self): |
515 @return list of tuples with menu text and URL to be opened for each |
566 @return list of tuples with menu text and URL to be opened for each |
516 entry |
567 entry |
517 @rtype list of tuple of (str, str) |
568 @rtype list of tuple of (str, str) |
518 """ |
569 """ |
519 if self.getDeviceType() == "bbc_microbit": |
570 if self.getDeviceType() == "bbc_microbit": |
520 return [ |
571 if self.__isMicroBitV1(): |
521 ( |
572 return [ |
522 self.tr("MicroPython Firmware for BBC micro:bit V1"), |
573 ( |
523 Preferences.getMicroPython("MicrobitMicroPythonUrl"), |
574 self.tr("MicroPython Firmware for BBC micro:bit V1"), |
524 ), |
575 Preferences.getMicroPython("MicrobitMicroPythonUrl"), |
525 ( |
576 ), |
526 self.tr("MicroPython Firmware for BBC micro:bit V2"), |
577 ( |
527 Preferences.getMicroPython("MicrobitV2MicroPythonUrl"), |
578 self.tr("DAPLink Firmware"), |
528 ), |
579 Preferences.getMicroPython("MicrobitFirmwareUrl"), |
529 ( |
580 ), |
530 self.tr("DAPLink Firmware"), |
581 ] |
531 Preferences.getMicroPython("MicrobitFirmwareUrl"), |
582 elif self.__isMicroBitV2(): |
532 ), |
583 return [ |
533 ] |
584 ( |
|
585 self.tr("MicroPython Firmware for BBC micro:bit V2"), |
|
586 Preferences.getMicroPython("MicrobitV2MicroPythonUrl"), |
|
587 ), |
|
588 ( |
|
589 self.tr("CircuitPython Firmware for BBC micro:bit V2"), |
|
590 "https://circuitpython.org/board/microbit_v2/", |
|
591 ), |
|
592 ( |
|
593 self.tr("DAPLink Firmware"), |
|
594 Preferences.getMicroPython("MicrobitFirmwareUrl"), |
|
595 ), |
|
596 ] |
|
597 else: |
|
598 return [] |
534 else: |
599 else: |
535 return [ |
600 return [ |
536 ( |
601 ( |
537 self.tr("MicroPython Firmware"), |
602 self.tr("MicroPython Firmware"), |
538 Preferences.getMicroPython("CalliopeMicroPythonUrl"), |
603 Preferences.getMicroPython("CalliopeMicroPythonUrl"), |