20 try: |
20 try: |
21 from PyQt5.QtSerialPort import QSerialPort |
21 from PyQt5.QtSerialPort import QSerialPort |
22 HAS_QTSERIALPORT = True |
22 HAS_QTSERIALPORT = True |
23 except ImportError: |
23 except ImportError: |
24 HAS_QTSERIALPORT = False |
24 HAS_QTSERIALPORT = False |
|
25 |
|
26 from E5Gui.E5ZoomWidget import E5ZoomWidget |
|
27 from E5Gui import E5MessageBox, E5FileDialog |
|
28 from E5Gui.E5Application import e5App |
|
29 |
|
30 from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget |
|
31 |
|
32 from . import MicroPythonDevices |
25 try: |
33 try: |
26 from PyQt5.QtChart import QChart # __IGNORE_WARNING__ |
34 from .MicroPythonGraphWidget import MicroPythonGraphWidget |
27 HAS_QTCHART = True |
35 HAS_QTCHART = True |
28 except ImportError: |
36 except ImportError: |
29 HAS_QTCHART = False |
37 HAS_QTCHART = False |
30 |
|
31 from E5Gui.E5ZoomWidget import E5ZoomWidget |
|
32 from E5Gui import E5MessageBox, E5FileDialog |
|
33 from E5Gui.E5Application import e5App |
|
34 |
|
35 from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget |
|
36 |
|
37 from . import MicroPythonDevices |
|
38 |
38 |
39 import Globals |
39 import Globals |
40 import UI.PixmapCache |
40 import UI.PixmapCache |
41 |
41 |
42 |
42 |
95 self.__zoomWidget.setMinimum(self.ZoomMin) |
95 self.__zoomWidget.setMinimum(self.ZoomMin) |
96 self.__zoomWidget.setMaximum(self.ZoomMax) |
96 self.__zoomWidget.setMaximum(self.ZoomMax) |
97 self.__zoomWidget.valueChanged.connect(self.__doZoom) |
97 self.__zoomWidget.valueChanged.connect(self.__doZoom) |
98 self.__currentZoom = 0 |
98 self.__currentZoom = 0 |
99 |
99 |
|
100 self.__ui = None |
|
101 |
100 self.__serial = None |
102 self.__serial = None |
101 self.__device = None |
103 self.__device = None |
102 self.setConnected(False) |
104 self.setConnected(False) |
103 |
105 |
104 self.__replRunning = False |
106 self.__replRunning = False |
180 |
182 |
181 def setActionButtons(self, **kwargs): |
183 def setActionButtons(self, **kwargs): |
182 """ |
184 """ |
183 Public method to set the enabled state of the various action buttons. |
185 Public method to set the enabled state of the various action buttons. |
184 |
186 |
185 @param kwargs keyword arguments containg the enabled states (keys are |
187 @keyparam kwargs keyword arguments containg the enabled states (keys |
186 'run', 'repl', 'files', 'chart', 'open', 'save' |
188 are 'run', 'repl', 'files', 'chart', 'open', 'save' |
187 @type dict |
189 @type dict |
188 """ |
190 """ |
189 if "open" in kwargs: |
191 if "open" in kwargs: |
190 self.openButton.setEnabled(kwargs["open"]) |
192 self.openButton.setEnabled(kwargs["open"]) |
191 if "save" in kwargs: |
193 if "save" in kwargs: |
200 self.chartButton.setEnabled(kwargs["chart"]) |
202 self.chartButton.setEnabled(kwargs["chart"]) |
201 |
203 |
202 @pyqtSlot(QPoint) |
204 @pyqtSlot(QPoint) |
203 def __showContextMenu(self, pos): |
205 def __showContextMenu(self, pos): |
204 """ |
206 """ |
205 Privat slot to show the REPL context menu. |
207 Private slot to show the REPL context menu. |
206 |
208 |
207 @param pos position to show the menu at |
209 @param pos position to show the menu at |
208 @type QPoint |
210 @type QPoint |
209 """ |
211 """ |
210 if Globals.isMacPlatform(): |
212 if Globals.isMacPlatform(): |
257 self.__showNoDeviceMessage() |
259 self.__showNoDeviceMessage() |
258 return |
260 return |
259 |
261 |
260 if self.__replRunning: |
262 if self.__replRunning: |
261 self.dataReceived.disconnect(self.__processData) |
263 self.dataReceived.disconnect(self.__processData) |
262 self.__disconnect() |
264 if not self.__plotterRunning: |
|
265 self.__disconnectSerial() |
263 self.__replRunning = False |
266 self.__replRunning = False |
264 self.__device.setRepl(False) |
267 self.__device.setRepl(False) |
265 else: |
268 else: |
266 ok, reason = self.__device.canStartRepl() |
269 ok, reason = self.__device.canStartRepl() |
267 if not ok: |
270 if not ok: |
269 self, |
272 self, |
270 self.tr("Start REPL"), |
273 self.tr("Start REPL"), |
271 self.tr("""<p>The REPL cannot be started.</p><p>Reason:""" |
274 self.tr("""<p>The REPL cannot be started.</p><p>Reason:""" |
272 """ {0}</p>""").format(reason)) |
275 """ {0}</p>""").format(reason)) |
273 return |
276 return |
274 |
277 |
|
278 self.replEdit.clear() |
|
279 self.dataReceived.connect(self.__processData) |
|
280 |
275 if not self.__serial: |
281 if not self.__serial: |
276 self.replEdit.clear() |
|
277 self.dataReceived.connect(self.__processData) |
|
278 self.__openSerialLink() |
282 self.__openSerialLink() |
279 if self.__serial: |
283 if self.__serial: |
280 if self.__device.forceInterrupt(): |
284 if self.__device.forceInterrupt(): |
281 # send a Ctrl-B (exit raw mode) |
285 # send a Ctrl-B (exit raw mode) |
282 self.__serial.write(b'\x02') |
286 self.__serial.write(b'\x02') |
292 Private slot to disconnect from the currently connected device. |
296 Private slot to disconnect from the currently connected device. |
293 """ |
297 """ |
294 if self.__replRunning: |
298 if self.__replRunning: |
295 self.on_replButton_clicked() |
299 self.on_replButton_clicked() |
296 |
300 |
|
301 if self.__plotterRunning: |
|
302 self.on_chartButton_clicked() |
|
303 |
297 # TODO: add more |
304 # TODO: add more |
298 |
305 |
299 def __disconnect(self): |
306 def __disconnectSerial(self): |
300 """ |
307 """ |
301 Private slot to disconnect the serial connection. |
308 Private slot to disconnect the serial connection. |
302 """ |
309 """ |
303 self.__closeSerialLink() |
310 self.__closeSerialLink() |
304 self.setConnected(False) |
311 self.setConnected(False) |
451 def __doZoom(self, value): |
458 def __doZoom(self, value): |
452 """ |
459 """ |
453 Private slot to zoom the REPL pane. |
460 Private slot to zoom the REPL pane. |
454 |
461 |
455 @param value zoom value |
462 @param value zoom value |
456 @param int |
463 @type int |
457 """ |
464 """ |
458 if value < self.__currentZoom: |
465 if value < self.__currentZoom: |
459 self.replEdit.zoomOut(self.__currentZoom - value) |
466 self.replEdit.zoomOut(self.__currentZoom - value) |
460 elif value > self.__currentZoom: |
467 elif value > self.__currentZoom: |
461 self.replEdit.zoomIn(value - self.__currentZoom) |
468 self.replEdit.zoomIn(value - self.__currentZoom) |
471 portName = self.deviceTypeComboBox.itemData( |
478 portName = self.deviceTypeComboBox.itemData( |
472 self.deviceTypeComboBox.currentIndex(), |
479 self.deviceTypeComboBox.currentIndex(), |
473 self.DevicePortRole) |
480 self.DevicePortRole) |
474 |
481 |
475 if Globals.isWindowsPlatform(): |
482 if Globals.isWindowsPlatform(): |
476 # return unchanged |
483 # return it unchanged |
477 return portName |
484 return portName |
478 else: |
485 else: |
479 # return with device path prepended |
486 # return with device path prepended |
480 return "/dev/{0}".format(portName) |
487 return "/dev/{0}".format(portName) |
481 |
488 |
525 Public method to execute a series of commands over a period of time. |
532 Public method to execute a series of commands over a period of time. |
526 |
533 |
527 @param commandsList list of commands to be execute on the device |
534 @param commandsList list of commands to be execute on the device |
528 @type list of bytes |
535 @type list of bytes |
529 """ |
536 """ |
|
537 def remainingTask(commands): |
|
538 self.execute(commands) |
|
539 |
530 if commandsList: |
540 if commandsList: |
531 command = commandsList[0] |
541 command = commandsList[0] |
532 self.__serial.write(command) |
542 self.__serial.write(command) |
533 remainder = commandsList[1:] |
543 remainder = commandsList[1:] |
534 remainingTask = lambda commands=remainder: self.execute(commands) |
544 QTimer.singleShot(2, lambda: remainingTask(remainder)) |
535 QTimer.singleShot(2, remainingTask) |
|
536 |
545 |
537 @pyqtSlot() |
546 @pyqtSlot() |
538 def on_runButton_clicked(self): |
547 def on_runButton_clicked(self): |
539 """ |
548 """ |
540 Private slot to execute the script of the active editor on the |
549 Private slot to execute the script of the active editor on the |
603 return |
612 return |
604 |
613 |
605 workspace = self.__device.getWorkspace() |
614 workspace = self.__device.getWorkspace() |
606 aw = e5App().getObject("ViewManager").activeWindow() |
615 aw = e5App().getObject("ViewManager").activeWindow() |
607 aw.saveFileAs(workspace) |
616 aw.saveFileAs(workspace) |
|
617 |
|
618 @pyqtSlot() |
|
619 def on_chartButton_clicked(self): |
|
620 """ |
|
621 Private slot to open a chart view to plot data received from the |
|
622 connected device. |
|
623 """ |
|
624 if not HAS_QTCHART: |
|
625 # QtChart not available => fail silently |
|
626 return |
|
627 |
|
628 if not self.__device: |
|
629 self.__showNoDeviceMessage() |
|
630 return |
|
631 |
|
632 if self.__ui is None: |
|
633 self.__ui = e5App().getObject("UserInterface") |
|
634 |
|
635 if self.__plotterRunning: |
|
636 if self.__chartWidget.hasData(): |
|
637 res = E5MessageBox.okToClearData( |
|
638 self, |
|
639 self.tr("Unsaved Chart Data"), |
|
640 self.tr("""The chart contains unsaved data."""), |
|
641 self.__chartWidget.saveData) |
|
642 if not res: |
|
643 # abort |
|
644 return |
|
645 |
|
646 self.dataReceived.disconnect(self.__chartWidget.processData) |
|
647 self.__chartWidget.dataFlood.disconnect(self.handleDataFlood) |
|
648 |
|
649 if not self.__replRunning: |
|
650 self.__disconnectSerial() |
|
651 |
|
652 self.__plotterRunning = False |
|
653 self.__device.setPlotter(False) |
|
654 self.__ui.removeSideWidget(self.__chartWidget) |
|
655 else: |
|
656 ok, reason = self.__device.canStartPlotter() |
|
657 if not ok: |
|
658 E5MessageBox.warning( |
|
659 self, |
|
660 self.tr("Start Chart"), |
|
661 self.tr("""<p>The Chart cannot be started.</p><p>Reason:""" |
|
662 """ {0}</p>""").format(reason)) |
|
663 return |
|
664 |
|
665 self.__chartWidget = MicroPythonGraphWidget(self) |
|
666 self.dataReceived.connect(self.__chartWidget.processData) |
|
667 self.__chartWidget.dataFlood.connect(self.handleDataFlood) |
|
668 |
|
669 self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget, |
|
670 UI.PixmapCache.getIcon("chart"), |
|
671 self.tr("Chart")) |
|
672 self.__ui.showSideWidget(self.__chartWidget) |
|
673 |
|
674 if not self.__serial: |
|
675 self.__openSerialLink() |
|
676 if self.__serial: |
|
677 if self.__device.forceInterrupt(): |
|
678 # send a Ctrl-B (exit raw mode) |
|
679 self.__serial.write(b'\x02') |
|
680 # send Ctrl-C (keyboard interrupt) |
|
681 self.__serial.write(b'\x03') |
|
682 |
|
683 self.__plotterRunning = True |
|
684 self.__device.setPlotter(True) |
|
685 |
|
686 @pyqtSlot() |
|
687 def handleDataFlood(self): |
|
688 """ |
|
689 Public slot handling a data flood from the device. |
|
690 """ |
|
691 self.on_disconnectButton_clicked() |
|
692 self.__device.handleDataFlood() |