src/eric7/MicroPython/MicrobitDevices.py

branch
eric7
changeset 9751
606ac0e26533
parent 9750
4958dd72c937
child 9752
2b9546c0cbd9
equal deleted inserted replaced
9750:4958dd72c937 9751:606ac0e26533
6 """ 6 """
7 Module implementing the device interface class for BBC micro:bit and 7 Module implementing the device interface class for BBC micro:bit and
8 Calliope mini boards. 8 Calliope mini boards.
9 """ 9 """
10 10
11 import contextlib
11 import os 12 import os
12 import shutil 13 import shutil
13 14
14 from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot 15 from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot
15 from PyQt6.QtNetwork import QNetworkRequest 16 from PyQt6.QtNetwork import QNetworkRequest
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):
501 @return documentation URL of the device 549 @return documentation URL of the device
502 @rtype str 550 @rtype str
503 """ 551 """
504 if self.getDeviceType() == "bbc_microbit": 552 if self.getDeviceType() == "bbc_microbit":
505 # BBC micro:bit 553 # BBC micro:bit
506 return Preferences.getMicroPython("MicrobitDocuUrl") 554 if self._deviceData and self._deviceData["mpy_name"] == "circuitpython":
555 return Preferences.getMicroPython("CircuitPythonDocuUrl")
556 else:
557 return Preferences.getMicroPython("MicrobitDocuUrl")
507 else: 558 else:
508 # Calliope mini 559 # Calliope mini
509 return Preferences.getMicroPython("CalliopeDocuUrl") 560 return Preferences.getMicroPython("CalliopeDocuUrl")
510 561
511 def getDownloadMenuEntries(self): 562 def getDownloadMenuEntries(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"),
542 Preferences.getMicroPython("CalliopeDAPLinkUrl"), 607 Preferences.getMicroPython("CalliopeDAPLinkUrl"),
543 ), 608 ),
544 ] 609 ]
545 610
546 611
547 def createDevice(microPythonWidget, deviceType, vid, pid, boardName): 612 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber):
548 """ 613 """
549 Function to instantiate a MicroPython device object. 614 Function to instantiate a MicroPython device object.
550 615
551 @param microPythonWidget reference to the main MicroPython widget 616 @param microPythonWidget reference to the main MicroPython widget
552 @type MicroPythonWidget 617 @type MicroPythonWidget
556 @type int 621 @type int
557 @param pid product ID 622 @param pid product ID
558 @type int 623 @type int
559 @param boardName name of the board 624 @param boardName name of the board
560 @type str 625 @type str
626 @param serialNumber serial number of the board
627 @type str
561 @return reference to the instantiated device object 628 @return reference to the instantiated device object
562 @rtype MicrobitDevice 629 @rtype MicrobitDevice
563 """ 630 """
564 return MicrobitDevice(microPythonWidget, deviceType) 631 return MicrobitDevice(microPythonWidget, deviceType, serialNumber)

eric ide

mercurial