#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # AWL simulator - GUI # Copyright 2012 Michael Buesch # # Licensed under the terms of the GNU General Public License version 2. # import sys import os import time try: from PySide.QtCore import * from PySide.QtGui import * except ImportError as e: print("PLEASE INSTALL PySide (http://www.pyside.org/)") input("Press enter to continue.") sys.exit(1) from awlsim import * from awlinsntrans import * class StateWindow(QWidget): closed = Signal() cpuStateChanged = Signal() def __init__(self, sim, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) pixmap = QPixmap(16, 16) pixmap.fill(QColor(0, 0, 192)) self.setWindowIcon(QIcon(pixmap)) self.sim = sim def update(self): size, hint = self.size(), self.minimumSizeHint() if size.width() < hint.width() or\ size.height() < hint.height(): self.resize(self.minimumSizeHint()) def closeEvent(self, ev): self.closed.emit() ev.accept() class State_CPU(StateWindow): def __init__(self, sim, parent=None): StateWindow.__init__(self, sim, parent) self.setWindowTitle("CPU Details") self.label = QLabel(self) font = self.label.font() font.setFamily("Mono") font.setFixedPitch(True) font.setKerning(False) self.label.setFont(font) self.layout().addWidget(self.label, 0, 0) self.label.setText("No CPU status available, yet.") def update(self): newText = str(self.sim.getCPU()) if newText: self.label.setText(newText) StateWindow.update(self) class AbstractDisplayWidget(QWidget): ADDRSPACE_E = AwlOperator.MEM_E ADDRSPACE_A = AwlOperator.MEM_A ADDRSPACE_M = AwlOperator.MEM_M ADDRSPACE_L = AwlOperator.MEM_L addrspace2name = { ADDRSPACE_E : "E", ADDRSPACE_A : "A", ADDRSPACE_M : "M", ADDRSPACE_L : "L", } changed = Signal() def __init__(self, sim, addrSpace, addr, width, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.sim = sim self.addrSpace = addrSpace self.addr = addr self.width = width def get(self): pass def update(self): pass class BitDisplayWidget(AbstractDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): AbstractDisplayWidget.__init__(self, sim, addrSpace, addr, width, parent) self.cbs = [] for i in range(self.width): cb = QCheckBox(str(i), self) self.layout().addWidget(cb, 0, self.width - i - 1) self.cbs.append(cb) cb.stateChanged.connect(self.changed) self.update() def get(self): value = 0 for i in range(self.width): if self.cbs[i].checkState() == Qt.Checked: value |= (1 << i) return value def update(self): try: oper = AwlOperator(self.addrSpace, self.width, self.addr) value = self.sim.getCPU().fetch(oper) except AwlSimError as e: QMessageBox.critical(self, "Failed to fetch", "Failed to fetch memory:\n" + str(e)) return for i in range(self.width): if value & (1 << i): self.cbs[i].setCheckState(Qt.Checked) else: self.cbs[i].setCheckState(Qt.Unchecked) class NumberDisplayWidget(AbstractDisplayWidget): def __init__(self, sim, base, addrSpace, addr, width, parent=None): AbstractDisplayWidget.__init__(self, sim, addrSpace, addr, width, parent) self.base = base self.displayedValue = -1 self.line = QLineEdit(self) self.line.setAlignment(Qt.AlignRight) self.layout().addWidget(self.line) self.line.returnPressed.connect(self.__returnPressed) self.line.textChanged.connect(self.__textChanged) self.update() def __returnPressed(self): self.changed.emit() def __convertValue(self): try: value = int(self.line.text(), self.base) if self.base == 10: if value > (1 << (self.width - 1)) - 1 or\ value < -(1 << (self.width - 1)): raise ValueError else: if value > (1 << self.width) - 1: raise ValueError except ValueError as e: return None return value def __textChanged(self): value = self.__convertValue() if value is None: pal = self.palette() pal.setColor(QPalette.Text, Qt.red) self.setPalette(pal) else: pal = self.palette() pal.setColor(QPalette.Text, Qt.black) self.setPalette(pal) def get(self): value = self.__convertValue() if value is None: return self.displayedValue return value def update(self): try: oper = AwlOperator(self.addrSpace, self.width, self.addr) value = self.sim.getCPU().fetch(oper) except AwlSimError as e: QMessageBox.critical(self, "Failed to fetch", "Failed to fetch memory:\n" + str(e)) return if value == self.displayedValue: return self.displayedValue = value if self.base == 2: string = "".join( '1' if (value & (1 << bitnr)) else '0' for bitnr in range(self.width - 1, -1, -1) ) elif self.base == 10: if self.width == 8: value &= 0xFF if value & 0x80: value = -((~value + 1) & 0xFF) string = "%d" % value elif self.width == 16: value &= 0xFFFF if value & 0x8000: value = -((~value + 1) & 0xFFFF) string = "%d" % value elif self.width == 32: value &= 0xFFFFFFFF if value & 0x80000000: value = -((~value + 1) & 0xFFFFFFFF) string = "%d" % value else: assert(0) elif self.base == 16: if self.width == 8: string = "%02X" % (value & 0xFF) elif self.width == 16: string = "%04X" % (value & 0xFFFF) elif self.width == 32: string = "%08X" % (value & 0xFFFFFFFF) else: assert(0) else: assert(0) self.line.setText(string) class HexDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 16, addrSpace, addr, width, parent) class DecDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 10, addrSpace, addr, width, parent) class BinDisplayWidget(NumberDisplayWidget): def __init__(self, sim, addrSpace, addr, width, parent=None): NumberDisplayWidget.__init__(self, sim, 2, addrSpace, addr, width, parent) class State_Mem(StateWindow): def __init__(self, sim, addrSpace, parent=None): StateWindow.__init__(self, sim, parent) name = AbstractDisplayWidget.addrspace2name[addrSpace] self.setWindowTitle(name) self.addrSpace = addrSpace self.addrSpin = QSpinBox(self) self.addrSpin.setPrefix(name + " ") self.layout().addWidget(self.addrSpin, 0, 0) self.widthCombo = QComboBox(self) self.widthCombo.addItem("Byte", 8) self.widthCombo.addItem("Word", 16) self.widthCombo.addItem("DWord", 32) self.layout().addWidget(self.widthCombo, 0, 1) self.fmtCombo = QComboBox(self) self.fmtCombo.addItem("Checkboxes", "cb") self.fmtCombo.addItem("Hexadecimal", "hex") self.fmtCombo.addItem("Decimal", "dec") self.fmtCombo.addItem("Dual", "bin") self.layout().addWidget(self.fmtCombo, 0, 2) self.contentLayout = QGridLayout() self.contentLayout.setContentsMargins(QMargins()) self.layout().addLayout(self.contentLayout, 1, 0, 1, 3) self.contentWidget = None self.addrSpin.valueChanged.connect(self.rebuild) self.widthCombo.currentIndexChanged.connect(self.rebuild) self.fmtCombo.currentIndexChanged.connect(self.rebuild) self.__changeBlocked = 0 self.rebuild() def rebuild(self): if self.contentWidget: self.contentLayout.removeWidget(self.contentWidget) self.contentWidget.deleteLater() self.contentWidget = None addr = self.addrSpin.value() index = self.fmtCombo.currentIndex() fmt = self.fmtCombo.itemData(index) index = self.widthCombo.currentIndex() width = self.widthCombo.itemData(index) if fmt == "cb": self.contentWidget = BitDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "hex": self.contentWidget = HexDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "dec": self.contentWidget = DecDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) elif fmt == "bin": self.contentWidget = BinDisplayWidget(self.sim, self.addrSpace, addr, width, self) self.contentLayout.addWidget(self.contentWidget) else: assert(0) self.contentWidget.changed.connect(self.__changed) self.update() def __changed(self): if self.__changeBlocked or not self.contentWidget: return value = self.contentWidget.get() addr = self.addrSpin.value() index = self.widthCombo.currentIndex() width = self.widthCombo.itemData(index) try: oper = AwlOperator(self.addrSpace, width, addr) self.sim.getCPU().store(oper, value) except AwlSimError as e: QMessageBox.critical(self, "Failed to store", "Failed to store memory:\n" + str(e)) return self.cpuStateChanged.emit() def update(self): if self.contentWidget: self.__changeBlocked += 1 self.contentWidget.update() self.__changeBlocked -= 1 StateWindow.update(self) class StateWorkspace(QWorkspace): def __init__(self, parent=None): QWorkspace.__init__(self, parent) class CpuWidget(QWidget): runStateChanged = Signal(bool) STATE_STOP = 0 STATE_RUN = 1 def __init__(self, mainWidget, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.mainWidget = mainWidget self.state = self.STATE_STOP self.nextUpdate = 0.0 group = QGroupBox("CPU status", self) group.setLayout(QGridLayout(group)) self.runButton = QRadioButton("RUN", group) group.layout().addWidget(self.runButton, 0, 0) self.stopButton = QRadioButton("STOP", group) group.layout().addWidget(self.stopButton, 1, 0) self.layout().addWidget(group, 0, 0) group = QGroupBox("Add window", self) group.setLayout(QGridLayout(group)) self.newCpuStateButton = QPushButton("CPU details", group) group.layout().addWidget(self.newCpuStateButton, 0, 0) self.newEButton = QPushButton("E", group) group.layout().addWidget(self.newEButton, 0, 1) self.newAButton = QPushButton("A", group) group.layout().addWidget(self.newAButton, 0, 2) self.newMButton = QPushButton("M", group) group.layout().addWidget(self.newMButton, 0, 3) self.newLButton = QPushButton("L", group) group.layout().addWidget(self.newLButton, 0, 4) self.layout().addWidget(group, 0, 1) self.stateWs = StateWorkspace(self) self.stateWs.setScrollBarsEnabled(True) self.layout().addWidget(self.stateWs, 1, 0, 1, 2) self.stopButton.setChecked(Qt.Checked) self.runButton.toggled.connect(self.__runStateToggled) self.stopButton.toggled.connect(self.__runStateToggled) self.newCpuStateButton.released.connect(self.__newWin_CPU) self.newEButton.released.connect(self.__newWin_E) self.newAButton.released.connect(self.__newWin_A) self.newMButton.released.connect(self.__newWin_M) self.newLButton.released.connect(self.__newWin_L) self.__newWin_CPU() self.update() def __addWindow(self, win): win.cpuStateChanged.connect(self.update) self.stateWs.addWindow(win, Qt.Window) win.show() self.update() def __newWin_CPU(self): self.__addWindow(State_CPU(self.mainWidget.getSim(), self)) def __newWin_E(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_E, self)) def __newWin_A(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_A, self)) def __newWin_M(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_M, self)) def __newWin_L(self): self.__addWindow(State_Mem(self.mainWidget.getSim(), AbstractDisplayWidget.ADDRSPACE_L, self)) def update(self): for win in self.stateWs.windowList(): win.update() def mayUpdate(self): now = time.time() if now < self.nextUpdate: return self.nextUpdate = now + 0.1 self.update() def __blockExitCallback(self, unused): self.mayUpdate() def __run(self): sim = self.mainWidget.getSim() self.state = self.STATE_RUN self.runButton.setChecked(True) self.runButton.setEnabled(False) # Redraws the radio button self.runButton.setEnabled(True) ob1_awl = self.mainWidget.getCodeEditWidget().getCode() if not ob1_awl.strip(): QMessageBox.critical(self, "No AWL Code", "No AWL Code") self.stop() return try: parser = AwlParser() parser.parseData(ob1_awl) sim.load(parser.getParseTree()) sim.getCPU().setBlockExitCallback(self.__blockExitCallback) except AwlSimError as e: QMessageBox.critical(self, "Simulator exception", str(e)) self.stop() return self.runStateChanged.emit(True) while self.state == self.STATE_RUN: try: QApplication.processEvents(QEventLoop.AllEvents, 1) sim.runCycle() except AwlSimError as e: QMessageBox.critical(self, "Simulator exception", str(e)) self.stop() def stop(self): self.state = self.STATE_STOP self.stopButton.setChecked(True) self.runButton.setEnabled(True) self.runStateChanged.emit(False) def __runStateToggled(self): if self.runButton.isChecked(): if self.state != self.STATE_RUN: self.__run() if self.stopButton.isChecked(): if self.state != self.STATE_STOP: self.stop() class EditWidget(QTextEdit): codeChanged = Signal() def __init__(self, parent=None): QTextEdit.__init__(self, parent) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setLineWrapMode(QTextEdit.NoWrap) self.setTabStopWidth(self.tabStopWidth() // 2) self.setFontFamily("Mono") self.__textChangeBlocked = 0 self.textChanged.connect(self.__textChanged) def __textChanged(self): if self.__textChangeBlocked: return self.codeChanged.emit() def loadCode(self, code): self.__textChangeBlocked += 1 self.setPlainText(code) self.__textChangeBlocked -= 1 def getCode(self): return self.toPlainText() class MainWidget(QWidget): dirtyChanged = Signal(bool) runStateChanged = Signal(bool) def __init__(self, parent=None): QWidget.__init__(self, parent) self.setLayout(QGridLayout(self)) self.sim = AwlSim() self.splitter = QSplitter(Qt.Horizontal) self.layout().addWidget(self.splitter, 0, 0) self.codeEdit = EditWidget(self) self.splitter.addWidget(self.codeEdit) self.cpuWidget = CpuWidget(self, self) self.splitter.addWidget(self.cpuWidget) self.filename = None self.dirty = False self.codeEdit.codeChanged.connect(self.__codeChanged) self.cpuWidget.runStateChanged.connect(self.__runStateChanged) def __runStateChanged(self, running): self.codeEdit.setReadOnly(running) self.runStateChanged.emit(running) def getSim(self): return self.sim def getCodeEditWidget(self): return self.codeEdit def getCpuWidget(self): return self.cpuWidget def __codeChanged(self): self.dirty = True self.dirtyChanged.emit(self.dirty) def loadFile(self, filename): try: data = awlFileRead(filename) except AwlParserError as e: QMessageBox.critical(self, "File read failed", str(e)) return False self.codeEdit.loadCode(data) self.filename = filename return True def load(self): fn, fil = QFileDialog.getOpenFileName(self, "Open AWL source", "", "AWL source (*.awl);;" "All files (*)") if not fn: return self.loadFile(fn) def saveFile(self, filename): code = self.codeEdit.getCode() try: awlFileWrite(filename, code) except AwlParserError as e: QMessageBox.critical(self, "Failed to write file", str(e)) return False self.dirty = False self.dirtyChanged.emit(self.dirty) self.filename = filename return True def save(self, newFile=False): if newFile or not self.filename: fn, fil = QFileDialog.getSaveFileName(self, "AWL source save as", "", "AWL source (*.awl)", "*.awl") if not fn: return if not fn.endswith(".awl"): fn += ".awl" self.saveFile(fn) else: self.saveFile(self.filename) class MainWindow(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.setWindowTitle("S7 AWL simulator") self.setCentralWidget(MainWidget(self)) self.setMenuBar(QMenuBar(self)) menu = QMenu("&File", self) menu.addAction("&Open...", self.load) self.saveAct = menu.addAction("&Save", self.save) menu.addAction("&Save as...", self.saveAs) menu.addSeparator() menu.addAction("&Exit...", self.close) self.menuBar().addMenu(menu) menu = QMenu("Help", self) menu.addAction("&Instruction support...", self.aboutInsns) menu.addSeparator() menu.addAction("&About...", self.about) self.menuBar().addMenu(menu) self.tb = QToolBar(self) self.tb.addAction("Open", self.load) self.tbSaveAct = self.tb.addAction("Save", self.save) self.addToolBar(self.tb) self.__dirtyChanged(False) self.centralWidget().dirtyChanged.connect(self.__dirtyChanged) self.centralWidget().runStateChanged.connect(self.__runStateChanged) if len(sys.argv) > 1: self.centralWidget().loadFile(sys.argv[1]) def __runStateChanged(self, running): self.menuBar().setEnabled(not running) self.tb.setEnabled(not running) def __dirtyChanged(self, isDirty): self.saveAct.setEnabled(isDirty) self.tbSaveAct.setEnabled(isDirty) def closeEvent(self, ev): self.centralWidget().getCpuWidget().stop() ev.accept() def aboutInsns(self): suppTypes, unsuppTypes = [], [] for insnType in AwlInsnTranslator.type2class.keys(): if insnType == AwlInsn.TYPE_INVALID or\ insnType >= AwlInsn.TYPE_EXTENDED: continue cls = AwlInsnTranslator.type2class[insnType] if cls == AwlInsn_NotImplemented: unsuppTypes.append(insnType) else: suppTypes.append(insnType) text = [ "Supported instructions (%d):\n" % len(suppTypes) ] suppInsns = [ AwlInsn.type2name[t] for t in suppTypes ] suppInsns.sort() unsuppInsns = [ AwlInsn.type2name[t] for t in unsuppTypes ] unsuppInsns.sort() text.append(" , ".join(suppInsns) + "\n") text.append("\nUnsupported instructions (%d):\n" % len(unsuppTypes)) text.append(" , ".join(unsuppInsns) + "\n") QMessageBox.information(self, "About S7 AWL simulator instructions", "".join(text)) def about(self): QMessageBox.information(self, "About S7 AWL simulator", "awlsim\n\n" "Copyright 2012 Michael Büsch \n" "Licensed under the terms of the " "GNU GPL version 2.") def load(self): self.centralWidget().load() def save(self): self.centralWidget().save() def saveAs(self): self.centralWidget().save(True) def main(): app = QApplication(sys.argv) mainwnd = MainWindow() mainwnd.show() return app.exec_() if __name__ == "__main__": sys.exit(main())