# -*- coding: utf-8 -*- # # AWL simulator - GUI CPU widget # # Copyright 2012-2018 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from __future__ import division, absolute_import, print_function, unicode_literals from awlsim.common.compat import * from awlsim.gui.util import * from awlsim.gui.cpustate import * from awlsim.gui.awlsimclient import * from awlsim.gui.icons import * from awlsim.gui.linkconfig import * class RunState(QObject): # Signal: Emitted, if the state changed. # The parameter is 'self'. stateChanged = Signal(QObject) EnumGen.start STATE_OFFLINE = EnumGen.item STATE_ONLINE = EnumGen.item STATE_LOAD = EnumGen.item STATE_RUN = EnumGen.item STATE_EXCEPTION = EnumGen.item EnumGen.end def __init__(self): QObject.__init__(self) self.state = self.STATE_OFFLINE self.setCoreDetails() def __emitStateChanged(self): self.stateChanged.emit(self) QApplication.processEvents(QEventLoop.ExcludeUserInputEvents, 50) def setState(self, newState): if self.state != newState: self.state = newState self.__emitStateChanged() def setCoreDetails(self, spawned=True, host=None, port=None, haveTunnel=False): self.spawned = spawned self.host = host self.port = port self.haveTunnel = haveTunnel self.__emitStateChanged() def __eq__(self, other): if isinstance(self, RunState) and\ isinstance(other, RunState): return self.state == other.state if isinstance(self, RunState) and\ isInteger(other): return self.state == other raise RuntimeError def __ne__(self, other): return not self.__eq__(other) class OnlineSelectAction(QAction): def __init__(self, parent): QAction.__init__(self, getIcon("network"), "", parent) self.setCheckable(True) self.__handleToggle(self.isChecked()) self.toggled.connect(self.__handleToggle) def __handleToggle(self, checked): if checked: self.setText("Go offline") else: self.setText("Go online (Connect to a CPU)") class RunSelectAction(QAction): def __init__(self, parent): QAction.__init__(self, getIcon("run"), "", parent) self.setCheckable(True) self.__handleToggle(self.isChecked()) self.toggled.connect(self.__handleToggle) def __handleToggle(self, checked): if checked: self.setText("Stop CPU (STOP mode)") self.setIcon(getIcon("stop")) else: self.setText("Start CPU (RUN mode)") self.setIcon(getIcon("run")) class DiagSelectAction(QAction): def __init__(self, parent): QAction.__init__(self, getIcon("glasses"), "", parent) self.setCheckable(True) self.__handleToggle(self.isChecked()) self.toggled.connect(self.__handleToggle) def __handleToggle(self, checked): if checked: self.setText("Disable online diagnosis") else: self.setText("Enable online diagnosis") class CpuInspectToolBar(QToolBar): def __init__(self, parent=None): QToolBar.__init__(self, parent) self.setObjectName("CpuInspectToolBar") self.setWindowTitle("CPU inspection tool bar") self.blocksAction = QAction(getIcon("plugin"), "Add inspection: Online blocks", self) self.addAction(self.blocksAction) self.inputsAction = QAction(getIcon("inputs"), "Add inspection: Input memory (I / E)", self) self.addAction(self.inputsAction) self.outputsAction = QAction(getIcon("outputs"), "Add inspection: Output memory (Q / A)", self) self.addAction(self.outputsAction) self.flagsAction = QAction(getIcon("flags"), "Add inspection: Flag memory (M)", self) self.addAction(self.flagsAction) self.dbAction = QAction(getIcon("datablock"), "Add inspection: Data block (DB)", self) self.addAction(self.dbAction) self.timerAction = QAction(getIcon("timer"), "Add inspection: Timer (T)", self) self.addAction(self.timerAction) self.counterAction = QAction(getIcon("counter"), "Add inspection: Counter (C / Z)", self) self.addAction(self.counterAction) self.cpuAction = QAction(getIcon("cpu"), "Add inspection: CPU overview", self) self.addAction(self.cpuAction) self.lcdAction = QAction(getIcon("lcd"), "Add inspection: LCD", self) self.addAction(self.lcdAction) def connectToCpuWidget(self, cpuWidget): self.blocksAction.triggered.connect(cpuWidget.newWin_Blocks) self.inputsAction.triggered.connect(cpuWidget.newWin_E) self.outputsAction.triggered.connect(cpuWidget.newWin_A) self.flagsAction.triggered.connect(cpuWidget.newWin_M) self.dbAction.triggered.connect(cpuWidget.newWin_DB) self.timerAction.triggered.connect(cpuWidget.newWin_T) self.counterAction.triggered.connect(cpuWidget.newWin_Z) self.cpuAction.triggered.connect(cpuWidget.newWin_CPU) self.lcdAction.triggered.connect(cpuWidget.newWin_LCD) class CpuControlToolBar(QToolBar): def __init__(self, parent=None): QToolBar.__init__(self, parent) self.setObjectName("CpuControlToolBar") self.setWindowTitle("CPU control tool bar") self.onlineAction = OnlineSelectAction(self) self.addAction(self.onlineAction) self.resetAction = QAction(getIcon("doc_delete"), "Reset the CPU", self) self.addAction(self.resetAction) self.downloadAction = QAction(getIcon("download"), "Download all sources to CPU", self) self.addAction(self.downloadAction) self.downloadSingleAction = QAction(getIcon("download_one"), "Download single source to CPU", self) self.addAction(self.downloadSingleAction) self.runAction = RunSelectAction(self) self.addAction(self.runAction) self.diagAction = DiagSelectAction(self) self.addAction(self.diagAction) def connectToCpuWidget(self, cpuWidget): self.onlineAction.toggled.connect(cpuWidget._onlineToggled) self.resetAction.triggered.connect(cpuWidget.resetCpu) self.downloadAction.triggered.connect(cpuWidget.download) self.downloadSingleAction.triggered.connect(cpuWidget.downloadSingle) self.runAction.toggled.connect(cpuWidget._runStateToggled) self.diagAction.toggled.connect(cpuWidget._onlineDiagToggled) cpuWidget.reqRunButtonState.connect(self.__setRun) cpuWidget.reqOnlineButtonState.connect(self.__setOnline) cpuWidget.reqOnlineDiagButtonState.connect(self.__setOnlineDiag) def __setRun(self, en): if en != self.runAction.isChecked(): self.runAction.trigger() def __setOnline(self, en): if en != self.onlineAction.isChecked(): self.onlineAction.trigger() def __setOnlineDiag(self, en): if en != self.diagAction.isChecked(): self.diagAction.trigger() class CpuWidget(QWidget): # Signal: The CPU run-state changed runStateChanged = Signal(RunState) # Signal: The online-diag state changed onlineDiagChanged = Signal(bool) # Signal: Some configuration value changed configChanged = Signal() # Signal: Have a new instruction dump haveInsnDump = Signal(AwlSimMessage_INSNSTATE) # Signal: Have a new ident hashes message haveIdentsMsg = Signal(AwlSimMessage_IDENTS) # Signal: Request a new run button state. reqRunButtonState = Signal(bool) # Signal: Request a new online button state. reqOnlineButtonState = Signal(bool) # Signal: request a new online diagnosis button state. reqOnlineDiagButtonState = Signal(bool) def __init__(self, mainWidget, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.layout().setContentsMargins(QMargins(7, 0, 0, 0)) self.mainWidget = mainWidget self.state = RunState() self.__runStateChangeBlocked = Blocker() self.__onlineBtnPressed = False self.__runBtnPressed = False self.__onlineDiagBtnPressed = False self.__coreMsgTimer = QTimer(self) self.__coreMsgTimer.setSingleShot(False) self.__coreMsgTimer.timeout.connect(self.__processCoreMessages) self.__corePeriodicTimer = QTimer(self) self.__corePeriodicTimer.setSingleShot(False) self.__corePeriodicTimer.timeout.connect(self.__periodicCoreWork) client = self.getSimClient() client.haveException.connect(self.__handleCpuException) client.haveCpuDump.connect(self.__handleCpuDump) client.haveInsnDump.connect(self.haveInsnDump) client.haveMemoryUpdate.connect(self.__handleMemoryUpdate) client.haveIdentsMsg.connect(self.__handleIdentsMsg) self.stateMdi = StateMdiArea(self) self.stateMdi.setViewMode(QMdiArea.SubWindowView) self.stateMdi.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.stateMdi.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.layout().addWidget(self.stateMdi, 0, 0) self.state.stateChanged.connect(self.runStateChanged) self.newWin_Blocks() self.newWin_CPU() def getProject(self): return self.mainWidget.getProject() def getSimClient(self): return self.mainWidget.getSimClient() def __stateMdiWindowClosed(self, mdiWin): QTimer.singleShot(0, self.__uploadMemReadAreas) def __addWindow(self, win): mdiWin = StateMdiSubWindow(win) mdiWin.closed.connect(self.__stateMdiWindowClosed) self.stateMdi.addSubWindow(mdiWin, Qt.Window) win.configChanged.connect(self.__stateWinConfigChanged) win.show() self.__uploadMemReadAreas() def newWin_CPU(self): self.__addWindow(State_CPU(self.getSimClient(), self)) def newWin_DB(self): self.__addWindow(State_Mem(self.getSimClient(), AbstractDisplayWidget.ADDRSPACE_DB, self)) def newWin_E(self): self.__addWindow(State_Mem(self.getSimClient(), AbstractDisplayWidget.ADDRSPACE_E, self)) def newWin_A(self): self.__addWindow(State_Mem(self.getSimClient(), AbstractDisplayWidget.ADDRSPACE_A, self)) def newWin_M(self): self.__addWindow(State_Mem(self.getSimClient(), AbstractDisplayWidget.ADDRSPACE_M, self)) def newWin_T(self): self.__addWindow(State_Timer(self.getSimClient(), self)) def newWin_Z(self): self.__addWindow(State_Counter(self.getSimClient(), self)) def newWin_LCD(self): self.__addWindow(State_LCD(self.getSimClient(), self)) def newWin_Blocks(self): self.__addWindow(State_Blocks(self.getSimClient(), self)) def __stateWinConfigChanged(self, stateWin): self.__uploadMemReadAreas() # Upload the used memory area descriptors to the core. def __uploadMemReadAreas(self): client = self.getSimClient() wantDump = False memAreas = [] for mdiWin in self.stateMdi.subWindowList(): win = mdiWin.widget() memAreas.extend(win.getMemoryAreas()) if isinstance(win, State_CPU): wantDump = True try: client.setMemoryReadRequests(memAreas, repetitionPeriod = 0.1, sync = True) client.setPeriodicDumpInterval(300 if wantDump else 0) except AwlSimError as e: self.state.setState(RunState.STATE_EXCEPTION) MessageBox.handleAwlSimError(self, "Error in awlsim core", e) return False except MaintenanceRequest as e: self.__handleMaintenance(e) return False return True def __handleCpuException(self, exception): # The CPU is in an exception state. # Set our state to exception/stopped. # This will stop the CPU, if it wasn't already stopped. # Subsequent exception handlers might do additional steps. self.state.setState(RunState.STATE_EXCEPTION) self.stop() def __handleCpuDump(self, dumpText): for mdiWin in self.stateMdi.subWindowList(): win = mdiWin.widget() if isinstance(win, State_CPU): win.setDumpText(dumpText) def __handleMemoryUpdate(self, memAreas): for mdiWin in self.stateMdi.subWindowList(): win = mdiWin.widget() win.setMemories(memAreas) def __handleMaintenance(self, maintRequest): client = self.getSimClient() if maintRequest.requestType == MaintenanceRequest.TYPE_SHUTDOWN: res = QMessageBox.question(self, "Shut down application?", "The core server requested an " "application shutdown.\n" "Do you want to close Awlsim GUI?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if res == QMessageBox.Yes: print("Shutting down, as requested by server...") client.shutdown() QApplication.exit(0) else: self.stop() self.goOffline() elif maintRequest.requestType == MaintenanceRequest.TYPE_STOP or\ maintRequest.requestType == MaintenanceRequest.TYPE_RTTIMEOUT: self.stop() else: print("Unknown maintenance request %d" %\ maintRequest.requestType) self.stop() def __run(self, goOnlineFirst=True): client = self.getSimClient() # Make sure the button is pressed. with self.__runStateChangeBlocked: self.run() # If requested, go online first. if goOnlineFirst: self.goOnline() if not self.isOnline(): self.stop() return assert(self.isOnline()) # Put the CPU and the GUI into RUN state. try: # Put CPU into RUN mode, if it's not already there. client.setRunState(True) # Upload the GUI requests. if not self.__uploadMemReadAreas(): self.stop() return self.updateOnlineViewState() # Re-Start the message handler. self.__startCoreMessageHandler() # Put the GUI into RUN mode. self.state.setState(RunState.STATE_RUN) except AwlSimError as e: self.state.setState(RunState.STATE_EXCEPTION) MessageBox.handleAwlSimError(self, "Could not start CPU", e) self.stop() except MaintenanceRequest as e: self.__handleMaintenance(e) # Periodic timer for core message handling. def __processCoreMessages(self): client = self.getSimClient() try: # Receive messages, until we hit a timeout while client.processMessages(0.02): pass except AwlSimError as e: self.state.setState(RunState.STATE_EXCEPTION) MessageBox.handleAwlSimError(self, "Core server error", e) self.stop() self.__stopCoreMessageHandler() except MaintenanceRequest as e: self.__handleMaintenance(e) except Exception: with suppressAllExc: client.setRunState(False) client.shutdown() handleFatalException(self) def __startCoreMessageHandler(self): self.__stopCoreMessageHandler() # Check if the CPU is in RUN mode. client = self.getSimClient() inRunMode = False with suppressAllExc: inRunMode = client.getRunState() # Start the main message fetcher. self.__coreMsgTimer.start(0 if inRunMode else 50) # Start the periodic core work handler. self.__periodicCoreWork() self.__corePeriodicTimer.start(1000 if inRunMode else 300) def __stopCoreMessageHandler(self): # Stop the periodic core work handler. self.__corePeriodicTimer.stop() # Stop the main message fetcher. self.__coreMsgTimer.stop() # Periodic timer for core status work. def __periodicCoreWork(self): client = self.getSimClient() hasBlockTree = client.blockTreeModelActive() try: client.requestIdents(reqAwlSources = True, reqSymTabSources = True, reqHwModules = hasBlockTree, reqLibSelections = hasBlockTree) if hasBlockTree: client.requestBlockInfo(reqOBInfo = True, reqFCInfo = True, reqFBInfo = True, reqDBInfo = True) except AwlSimError as e: self.state.setState(RunState.STATE_EXCEPTION) MessageBox.handleAwlSimError(self, "Core server error", e) self.stop() self.__stopCoreMessageHandler() except MaintenanceRequest as e: self.__handleMaintenance(e) except Exception: with suppressAllExc: client.setRunState(False) client.shutdown() handleFatalException(self) def __handleIdentsMsg(self, identsMsg): self.haveIdentsMsg.emit(identsMsg) def __stop(self): # Make sure the button is released. with self.__runStateChangeBlocked: self.stop() if self.isOnline(): client = self.getSimClient() try: client.setRunState(False) except AwlSimError as e: MessageBox.handleAwlSimError(self, "Could not stop CPU", e) # Re-Start the message handler. self.__startCoreMessageHandler() self.state.setState(RunState.STATE_ONLINE) def stop(self): self.reqRunButtonState.emit(False) def run(self): self.reqRunButtonState.emit(True) def __goOnline(self): project = self.getProject() if LinkConfigWidget.askWhenConnecting(): dlg = LinkConfigDialog(project, self) dlg.settingsChanged.connect(self.configChanged) if dlg.exec_() != LinkConfigDialog.Accepted: dlg.deleteLater() self.goOffline() return dlg.deleteLater() linkConfig = project.getCoreLinkSettings() client = self.getSimClient() try: if linkConfig.getSpawnLocalEn(): portRange = linkConfig.getSpawnLocalPortRange() interp = linkConfig.getSpawnLocalInterpreterList() client.setMode_FORK(portRange = portRange, interpreterList = interp) host = port = None else: client.setMode_ONLINE(self, linkConfig) self.state.setCoreDetails( spawned = linkConfig.getSpawnLocalEn(), host = linkConfig.getConnectHost(), port = linkConfig.getConnectPort(), haveTunnel = (linkConfig.getTunnel() == linkConfig.TUNNEL_SSH)) self.state.setState(RunState.STATE_ONLINE) if client.getRunState(): # The core is already running. # Set the GUI to run state, too. self.__run(goOnlineFirst = False) # Re-Start the message handler. self.__startCoreMessageHandler() except AwlSimError as e: with suppressAllExc: self.__stopCoreMessageHandler() with suppressAllExc: client.setMode_OFFLINE() MessageBox.handleAwlSimError(self, "Error while trying to connect to CPU", e) self.goOffline() return except MaintenanceRequest as e: self.__handleMaintenance(e) def __goOffline(self): client = self.getSimClient() try: client.setMode_OFFLINE() except AwlSimError as e: MessageBox.handleAwlSimError(self, "Error while trying to disconnect from CPU", e) # Release the stop-button. # This will _not_ stop the CPU, as we're offline already. self.stop() self.state.setState(RunState.STATE_OFFLINE) self.__stopCoreMessageHandler() def _onlineToggled(self, onlineBtnPressed): self.__onlineBtnPressed = onlineBtnPressed if self.__onlineBtnPressed: self.__goOnline() else: self.__goOffline() def isOnline(self): return self.__onlineBtnPressed def goOnline(self): self.reqOnlineButtonState.emit(True) def goOffline(self): self.reqOnlineButtonState.emit(False) # Reset/clear the CPU and upload all sources. def download(self): # Make sure we are online. self.goOnline() if not self.isOnline(): return False client = self.getSimClient() project = self.getProject() try: self.state.setState(RunState.STATE_LOAD) client.setRunState(False) client.reset() client.loadProject(project) client.build() self.state.setState(RunState.STATE_ONLINE) except AwlParserError as e: self.state.setState(RunState.STATE_ONLINE) self.stop() MessageBox.handleAwlParserError(self, e) return False except AwlSimError as e: self.state.setState(RunState.STATE_ONLINE) self.stop() MessageBox.handleAwlSimError(self, "Error while loading code", e) return False except MaintenanceRequest as e: self.__handleMaintenance(e) return False except Exception: client.shutdown() handleFatalException(self) # If we were RUNning before download, put # the CPU into RUN state again. if self.__runBtnPressed: self.__run(goOnlineFirst = False) return True # Download the current source. def downloadSingle(self): editMdiArea = self.mainWidget.editMdiArea mdiSubWin = editMdiArea.activeOpenSubWindow source = libSelections = None if mdiSubWin: if mdiSubWin.TYPE in {mdiSubWin.TYPE_AWL, mdiSubWin.TYPE_FUP, mdiSubWin.TYPE_KOP, mdiSubWin.TYPE_SYMTAB,}: source = mdiSubWin.getSource() elif mdiSubWin.TYPE == mdiSubWin.TYPE_LIBSEL: libSelections = mdiSubWin.getLibSelections() else: assert(0) if not mdiSubWin or (not source and not libSelections): QMessageBox.critical(self, "No source selected.", "Cannot download a single source.\n" "No source has been opened in the edit area.", QMessageBox.Ok) return False # Make sure we are online. self.goOnline() if not self.isOnline(): return False client = self.getSimClient() project = self.getProject() try: self.state.setState(RunState.STATE_LOAD) if mdiSubWin.TYPE == mdiSubWin.TYPE_AWL: printVerbose("Single AWL download: %s/%s" %\ (source.name, source.identHashStr)) client.loadAwlSource(source) elif mdiSubWin.TYPE == mdiSubWin.TYPE_FUP: printVerbose("Single FUP download: %s/%s" %\ (source.name, source.identHashStr)) client.loadFupSource(source) elif mdiSubWin.TYPE == mdiSubWin.TYPE_KOP: printVerbose("Single KOP download: %s/%s" %\ (source.name, source.identHashStr)) client.loadKopSource(source) elif mdiSubWin.TYPE == mdiSubWin.TYPE_SYMTAB: printVerbose("Single sym download: %s/%s" %\ (source.name, source.identHashStr)) client.loadSymTabSource(source) elif mdiSubWin.TYPE == mdiSubWin.TYPE_LIBSEL: printVerbose("Single libSelections download.") client.loadLibraryBlocks(libSelections) else: assert(0) if self.__runBtnPressed: self.state.setState(RunState.STATE_RUN) else: self.state.setState(RunState.STATE_ONLINE) except AwlParserError as e: self.state.setState(RunState.STATE_ONLINE) self.stop() MessageBox.handleAwlParserError(self, e) return False except AwlSimError as e: self.state.setState(RunState.STATE_ONLINE) self.stop() MessageBox.handleAwlSimError(self, "Error while loading code (single source)", e) return False except MaintenanceRequest as e: self.__handleMaintenance(e) return False except Exception: client.shutdown() handleFatalException(self) return True def resetCpu(self): """Reset the CPU. """ # Make sure we are online. self.goOnline() if not self.isOnline(): return False client = self.getSimClient() try: client.setRunState(False) client.reset() except AwlParserError as e: MessageBox.handleAwlParserError(self, e) return False except AwlSimError as e: MessageBox.handleAwlSimError(self, "Error while reseting CPU", e) return False except MaintenanceRequest as e: self.__handleMaintenance(e) return False except Exception: client.shutdown() handleFatalException(self) return True def _runStateToggled(self, runBtnPressed): self.__runBtnPressed = runBtnPressed if not self.__runStateChangeBlocked: if self.__runBtnPressed: self.__run() else: self.__stop() def handleDirtyChange(self, dirty): if dirty: self.reqOnlineDiagButtonState.emit(False) def _onlineDiagToggled(self, onlineDiagBtnPressed): self.__onlineDiagBtnPressed = onlineDiagBtnPressed self.updateOnlineViewState() def updateOnlineViewState(self): self.onlineDiagChanged.emit(self.__onlineDiagBtnPressed) def updateVisibleLineRange(self, source, fromLine, toLine): try: client = self.getSimClient() if self.__onlineDiagBtnPressed and source: client.setInsnStateDump(enable=True, sourceId=source.identHash, fromLine=fromLine, toLine=toLine, sync=False) else: client.setInsnStateDump(enable=False, sync=False) except AwlSimError as e: MessageBox.handleAwlSimError(self, "Failed to setup instruction dumping", e) return except MaintenanceRequest as e: self.__handleMaintenance(e) return def sizeHint(self): return QSize(550, 200)