13 import os |
13 import os |
14 import shutil |
14 import shutil |
15 |
15 |
16 from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot |
16 from PyQt6.QtCore import QStandardPaths, QUrl, pyqtSlot |
17 from PyQt6.QtNetwork import QNetworkRequest |
17 from PyQt6.QtNetwork import QNetworkRequest |
18 from PyQt6.QtWidgets import QInputDialog, QLineEdit, QMenu |
18 from PyQt6.QtWidgets import QMenu |
19 |
19 |
20 from eric7 import Globals, Preferences |
20 from eric7 import Globals, Preferences |
21 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
21 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
22 from eric7.EricWidgets.EricApplication import ericApp |
22 from eric7.EricWidgets.EricApplication import ericApp |
23 from eric7.SystemUtilities import FileSystemUtilities |
23 from eric7.SystemUtilities import FileSystemUtilities |
198 ) |
194 ) |
199 self.__flashDAPLinkAct = self.__microbitMenu.addAction( |
195 self.__flashDAPLinkAct = self.__microbitMenu.addAction( |
200 self.tr("Flash Firmware"), lambda: self.__flashMicroPython(firmware=True) |
196 self.tr("Flash Firmware"), lambda: self.__flashMicroPython(firmware=True) |
201 ) |
197 ) |
202 self.__microbitMenu.addSeparator() |
198 self.__microbitMenu.addSeparator() |
203 self.__saveScripAct = self.__microbitMenu.addAction( |
|
204 self.tr("Save Script"), self.__saveScriptToDevice |
|
205 ) |
|
206 self.__saveScripAct.setToolTip( |
|
207 self.tr("Save the current script to the selected device") |
|
208 ) |
|
209 self.__saveMainScriptAct = self.__microbitMenu.addAction( |
199 self.__saveMainScriptAct = self.__microbitMenu.addAction( |
210 self.tr("Save Script as 'main.py'"), self.__saveMain |
200 self.tr("Save Script as 'main.py'"), self.__saveMain |
211 ) |
201 ) |
212 self.__saveMainScriptAct.setToolTip( |
202 self.__saveMainScriptAct.setToolTip( |
213 self.tr("Save the current script as 'main.py' on the connected device") |
203 self.tr("Save the current script as 'main.py' on the connected device") |
225 @type QMenu |
215 @type QMenu |
226 """ |
216 """ |
227 connected = self.microPython.isConnected() |
217 connected = self.microPython.isConnected() |
228 linkConnected = self.microPython.isLinkConnected() |
218 linkConnected = self.microPython.isLinkConnected() |
229 |
219 |
|
220 aw = ericApp().getObject("ViewManager").activeWindow() |
|
221 canSaveMain = ( |
|
222 aw is not None |
|
223 and (aw.isPyFile() or aw.isMicroPythonFile()) |
|
224 and bool(aw.text().strip()) |
|
225 ) |
|
226 |
230 self.__showMpyAct.setEnabled(connected and self.getDeviceType() != "calliope") |
227 self.__showMpyAct.setEnabled(connected and self.getDeviceType() != "calliope") |
231 self.__flashMpyAct.setEnabled(not linkConnected) |
228 self.__flashMpyAct.setEnabled(not linkConnected) |
232 self.__flashDAPLinkAct.setEnabled(not linkConnected) |
229 self.__flashDAPLinkAct.setEnabled(not linkConnected) |
233 self.__saveScripAct.setEnabled(connected) |
230 self.__saveMainScriptAct.setEnabled(connected and canSaveMain) |
234 self.__saveMainScriptAct.setEnabled(connected) |
|
235 self.__resetAct.setEnabled(connected) |
231 self.__resetAct.setEnabled(connected) |
236 |
232 |
237 menu.addMenu(self.__microbitMenu) |
233 menu.addMenu(self.__microbitMenu) |
238 |
234 |
239 def hasFlashMenuEntry(self): |
235 def hasFlashMenuEntry(self): |
468 def __saveMain(self): |
464 def __saveMain(self): |
469 """ |
465 """ |
470 Private slot to copy the current script as 'main.py' onto the |
466 Private slot to copy the current script as 'main.py' onto the |
471 connected device. |
467 connected device. |
472 """ |
468 """ |
473 self.__saveScriptToDevice("main.py") |
|
474 |
|
475 @pyqtSlot() |
|
476 def __saveScriptToDevice(self, scriptName=""): |
|
477 """ |
|
478 Private method to save the current script onto the connected |
|
479 device. |
|
480 |
|
481 @param scriptName name of the file on the device |
|
482 @type str |
|
483 """ |
|
484 aw = ericApp().getObject("ViewManager").activeWindow() |
469 aw = ericApp().getObject("ViewManager").activeWindow() |
485 if not aw: |
470 if not aw: |
486 return |
471 return |
487 |
472 |
488 title = ( |
473 title = self.tr("Save Script as 'main.py'") |
489 self.tr("Save Script as '{0}'").format(scriptName) |
|
490 if scriptName |
|
491 else self.tr("Save Script") |
|
492 ) |
|
493 |
474 |
494 if not (aw.isPyFile() or aw.isMicroPythonFile()): |
475 if not (aw.isPyFile() or aw.isMicroPythonFile()): |
495 yes = EricMessageBox.yesNo( |
476 yes = EricMessageBox.yesNo( |
496 self.microPython, |
477 self.microPython, |
497 title, |
478 title, |
508 EricMessageBox.warning( |
489 EricMessageBox.warning( |
509 self.microPython, title, self.tr("""The script is empty. Aborting.""") |
490 self.microPython, title, self.tr("""The script is empty. Aborting.""") |
510 ) |
491 ) |
511 return |
492 return |
512 |
493 |
513 if not scriptName: |
494 self.putData("main.py", script.encode("utf-8")) |
514 scriptName = os.path.basename(aw.getFileName()) |
|
515 scriptName, ok = QInputDialog.getText( |
|
516 self.microPython, |
|
517 title, |
|
518 self.tr("Enter a file name on the device:"), |
|
519 QLineEdit.EchoMode.Normal, |
|
520 scriptName, |
|
521 ) |
|
522 if not ok or not bool(scriptName): |
|
523 return |
|
524 |
|
525 title = self.tr("Save Script as '{0}'").format(scriptName) |
|
526 |
|
527 commands = [ |
|
528 "fd = open('{0}', 'wb')".format(scriptName), |
|
529 "f = fd.write", |
|
530 ] |
|
531 for line in script.splitlines(): |
|
532 commands.append("f(" + repr(line + "\n") + ")") |
|
533 commands.append("fd.close()") |
|
534 out, err = self.microPython.deviceInterface().execute(commands) |
|
535 if err: |
|
536 EricMessageBox.critical( |
|
537 self.microPython, |
|
538 title, |
|
539 self.tr( |
|
540 """<p>The script could not be saved to the""" |
|
541 """ device.</p><p>Reason: {0}</p>""" |
|
542 ).format(err.decode("utf-8")), |
|
543 ) |
|
544 |
495 |
545 # reset the device |
496 # reset the device |
546 self.__resetDevice() |
497 self.__resetDevice() |
547 |
498 |
548 @pyqtSlot() |
499 @pyqtSlot() |
551 Private slot to reset the connected device. |
502 Private slot to reset the connected device. |
552 """ |
503 """ |
553 if self.getDeviceType() == "bbc_microbit": |
504 if self.getDeviceType() == "bbc_microbit": |
554 # BBC micro:bit |
505 # BBC micro:bit |
555 self.microPython.deviceInterface().execute( |
506 self.microPython.deviceInterface().execute( |
556 [ |
507 "import microbit\nmicrobit.reset()\n" |
557 "import microbit", |
|
558 "microbit.reset()", |
|
559 ] |
|
560 ) |
508 ) |
561 else: |
509 else: |
562 # Calliope mini |
510 # Calliope mini |
563 self.microPython.deviceInterface().execute( |
511 self.microPython.deviceInterface().execute( |
564 [ |
512 "import calliope_mini\ncalliope_mini.reset()\n" |
565 "import calliope_mini", |
|
566 "calliope_mini.reset()", |
|
567 ] |
|
568 ) |
513 ) |
569 |
514 |
570 def getDocumentationUrl(self): |
515 def getDocumentationUrl(self): |
571 """ |
516 """ |
572 Public method to get the device documentation URL. |
517 Public method to get the device documentation URL. |
645 @type str |
590 @type str |
646 @return tuple containg the directory listing |
591 @return tuple containg the directory listing |
647 @rtype tuple of str |
592 @rtype tuple of str |
648 @exception OSError raised to indicate an issue with the device |
593 @exception OSError raised to indicate an issue with the device |
649 """ |
594 """ |
650 # BBC micro:bit does not support directories |
595 if self.hasCircuitPython(): |
651 commands = [ |
596 return super().ls(dirname=dirname) |
652 "import os as __os_", |
597 else: |
653 "print(__os_.listdir())", |
598 # BBC micro:bit with MicroPython does not support directories |
654 "del __os_", |
599 command = """ |
655 ] |
600 import os as __os_ |
656 out, err = self._interface.execute(commands) |
601 print(__os_.listdir()) |
657 if err: |
602 del __os_ |
658 raise OSError(self._shortError(err)) |
603 """ |
659 return ast.literal_eval(out.decode("utf-8")) |
604 out, err = self._interface.execute(command) |
|
605 if err: |
|
606 raise OSError(self._shortError(err)) |
|
607 return ast.literal_eval(out.decode("utf-8")) |
660 |
608 |
661 def lls(self, dirname="", fullstat=False, showHidden=False): |
609 def lls(self, dirname="", fullstat=False, showHidden=False): |
662 """ |
610 """ |
663 Public method to get a long directory listing of the connected device |
611 Public method to get a long directory listing of the connected device |
664 including meta data. |
612 including meta data. |
674 false) or the complete stat() tuple. 'None' is returned in case the |
622 false) or the complete stat() tuple. 'None' is returned in case the |
675 directory doesn't exist. |
623 directory doesn't exist. |
676 @rtype tuple of (str, tuple) |
624 @rtype tuple of (str, tuple) |
677 @exception OSError raised to indicate an issue with the device |
625 @exception OSError raised to indicate an issue with the device |
678 """ |
626 """ |
679 # BBC micro:bit does not support directories |
627 if self.hasCircuitPython(): |
680 commands = [ |
628 return super().lls( |
681 "import os as __os_", |
629 dirname=dirname, fullstat=fullstat, showHidden=showHidden |
682 "\n".join( |
630 ) |
683 [ |
631 else: |
684 "def is_visible(filename, showHidden):", |
632 # BBC micro:bit with MicroPython does not support directories |
685 " return showHidden or " |
633 command = """ |
686 "(filename[0] != '.' and filename[-1] != '~')", |
634 import os as __os_ |
687 ] |
635 |
688 ), |
636 def is_visible(filename, showHidden): |
689 "\n".join( |
637 return showHidden or (filename[0] != '.' and filename[-1] != '~') |
690 [ |
638 |
691 "def stat(filename):", |
639 def stat(filename): |
692 " size = __os_.size(filename)", |
640 size = __os_.size(filename) |
693 " return (0, 0, 0, 0, 0, 0, size, 0, 0, 0)", |
641 return (0, 0, 0, 0, 0, 0, size, 0, 0, 0) |
694 ] |
642 |
695 ), |
643 def listdir_stat(showHidden): |
696 "\n".join( |
644 files = __os_.listdir() |
697 [ |
645 return list((f, stat(f)) for f in files if is_visible(f,showHidden)) |
698 "def listdir_stat(showHidden):", |
646 |
699 " files = __os_.listdir()", |
647 print(listdir_stat({0})) |
700 " return list((f, stat(f)) for f in files if" |
648 del __os_, stat, listdir_stat, is_visible |
701 " is_visible(f,showHidden))", |
649 """.format( |
702 ] |
650 showHidden |
703 ), |
651 ) |
704 "print(listdir_stat({0}))".format(showHidden), |
652 out, err = self._interface.execute(command) |
705 "del __os_, stat, listdir_stat, is_visible", |
653 if err: |
706 ] |
654 raise OSError(self._shortError(err)) |
707 out, err = self._interface.execute(commands) |
655 fileslist = ast.literal_eval(out.decode("utf-8")) |
708 if err: |
656 if fileslist is None: |
709 raise OSError(self._shortError(err)) |
657 return None |
710 fileslist = ast.literal_eval(out.decode("utf-8")) |
|
711 if fileslist is None: |
|
712 return None |
|
713 else: |
|
714 if fullstat: |
|
715 return fileslist |
|
716 else: |
658 else: |
717 return [(f, (s[0], s[6], s[8])) for f, s in fileslist] |
659 if fullstat: |
|
660 return fileslist |
|
661 else: |
|
662 return [(f, (s[0], s[6], s[8])) for f, s in fileslist] |
718 |
663 |
719 def pwd(self): |
664 def pwd(self): |
720 """ |
665 """ |
721 Public method to get the current directory of the connected device. |
666 Public method to get the current directory of the connected device. |
722 |
667 |
723 @return current directory |
668 @return current directory |
724 @rtype str |
669 @rtype str |
725 """ |
670 """ |
726 # BBC micro:bit does not support directories |
671 if self.hasCircuitPython(): |
727 return "" |
672 return super().pwd() |
|
673 else: |
|
674 # BBC micro:bit with MicroPython does not support directories |
|
675 return "" |
728 |
676 |
729 ################################################################## |
677 ################################################################## |
730 ## time related methods below |
678 ## time related methods below |
731 ################################################################## |
679 ################################################################## |
732 |
680 |
747 # rtc_time[4] - hour 0..23 |
695 # rtc_time[4] - hour 0..23 |
748 # rtc_time[5] - minute 0..59 |
696 # rtc_time[5] - minute 0..59 |
749 # rtc_time[6] - second 0..59 |
697 # rtc_time[6] - second 0..59 |
750 # rtc_time[7] - yearday 1..366 |
698 # rtc_time[7] - yearday 1..366 |
751 # rtc_time[8] - isdst 0, 1, or -1 |
699 # rtc_time[8] - isdst 0, 1, or -1 |
752 return "" |
700 if self.hasCircuitPython(): |
|
701 return super()._getSetTimeCode() |
|
702 else: |
|
703 return "" |
753 |
704 |
754 |
705 |
755 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): |
706 def createDevice(microPythonWidget, deviceType, vid, pid, boardName, serialNumber): |
756 """ |
707 """ |
757 Function to instantiate a MicroPython device object. |
708 Function to instantiate a MicroPython device object. |