From adb2700b03c7a68bb4e1994c2df904008a65a983 Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Sun, 29 Jul 2018 16:43:10 +0200 Subject: simulator: Add basic GUI Signed-off-by: Michael Buesch --- firmware/simulator/xytronic_simulator.py | 3 - firmware/simulator/xytronic_simulator_gui.py | 418 +++++++++++++++++++++++++++ 2 files changed, 418 insertions(+), 3 deletions(-) create mode 100755 firmware/simulator/xytronic_simulator_gui.py diff --git a/firmware/simulator/xytronic_simulator.py b/firmware/simulator/xytronic_simulator.py index b0315f6..68b7af8 100755 --- a/firmware/simulator/xytronic_simulator.py +++ b/firmware/simulator/xytronic_simulator.py @@ -172,7 +172,6 @@ class Simulator(object): self.dbg_filtCurr = 0 self.dbg_measTemp = 0 self.dbg_boostMode = 0 - self.dbg_calCurrPercent = 0 @classmethod def __parseInt(cls, valStr, valIdent): @@ -235,8 +234,6 @@ class Simulator(object): self.dbg_filtCurr = self.__parseInt(elems[1], "fc") elif elems[0] == "mt": self.dbg_measTemp = self.__parseInt(elems[1], "mt") - elif elems[0] == "cc": - self.dbg_calCurrPercent = self.__parseInt(elems[1], "cc") else: self.error("Unknown elem: %s" % elems[0]) return diff --git a/firmware/simulator/xytronic_simulator_gui.py b/firmware/simulator/xytronic_simulator_gui.py new file mode 100755 index 0000000..94f21ca --- /dev/null +++ b/firmware/simulator/xytronic_simulator_gui.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python3 +""" +# Xytronic LF-1600 +# Open Source firmware +# Simulator graphical user interface +# +# Copyright (c) 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. +""" + +import pyxytronic +from xytronic_simulator import Simulator, IronTempSimulator, IronCurrentSimulator +import sys +from copy import copy +import multiprocessing +import time +import queue +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtChart import * + + +class Measurements(object): + dbg_currentRealR = 0.0 + dbg_currentUsedR = 0.0 + dbg_currentRState = 0 + dbg_currentY = 0.0 + dbg_tempR = 0.0 + dbg_tempY1 = 0.0 + dbg_tempY2 = 0.0 + dbg_measCurr = 0 + dbg_filtCurr = 0 + dbg_measTemp = 0 + dbg_boostMode = 0 + +class SimulatorProcess(multiprocessing.Process): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, daemon=False) + + def __oneLoop(self): + self.__sim.run() +# time.sleep(0.0005) + + def __updateValues(self, temp): + self.__sim.setting_set("temp_setpoint_active", 0) + self.__sim.setting_set("temp_setpoint", temp) + + def setValues(self, temp=350.0): + self.__commands.put(("temp", temp)) + + def __buildMeasurements(self): + meas = Measurements() + meas.dbg_currentRealR = self.__sim.dbg_currentRealR + meas.dbg_currentUsedR = self.__sim.dbg_currentUsedR + meas.dbg_currentRState = self.__sim.dbg_currentRState + meas.dbg_currentY = self.__sim.dbg_currentY + meas.dbg_tempR = self.__sim.dbg_tempR + meas.dbg_tempY1 = self.__sim.dbg_tempY1 + meas.dbg_tempY2 = self.__sim.dbg_tempY2 + meas.dbg_measCurr = self.__sim.dbg_measCurr + meas.dbg_filtCurr = self.__sim.dbg_filtCurr + meas.dbg_measTemp = self.__sim.dbg_measTemp + meas.dbg_boostMode = self.__sim.dbg_boostMode + self.__responses.put(("meas", meas)) + + def requestMeasurements(self): + self.__commands.put(("reqMeas", None)) + + def getResponses(self): + try: + while True: + ident, args = self.__responses.get(False) + yield ident, args + except queue.Empty as e: + pass + + def start(self): + self.__commands = multiprocessing.Queue() + self.__responses = multiprocessing.Queue() + self.setValues() + super().start() + + def run(self): + print("Starting simulator process.") + pyxytronic.simulator_init() + try: + self.__sim = Simulator() + self.__simTemp = IronTempSimulator(self.__sim) + self.__simCurr = IronCurrentSimulator(self.__sim) + run = True + while run: + while True: + try: + (cmd, args) = self.__commands.get(False) + if cmd == "stop": + run = False + if cmd == "reqMeas": + self.__buildMeasurements() + if cmd == "temp": + self.__updateValues(temp=args) + except queue.Empty as e: + break + self.__oneLoop() + finally: + pyxytronic.simulator_exit() + print("Exiting simulator process.") + + def shutdown(self): + if self.is_alive(): + self.__commands.put(("stop", None)) + self.join() + +class MainChart(QChart): + def __init__(self): + super().__init__() + +class MainChartView(QChartView): + def __init__(self, chart, parent=None): + super().__init__(chart, parent) + +class MainWidget(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setLayout(QGridLayout()) + + self.simProc = None + + self.chart = MainChart() + self.chartView = MainChartView(self.chart, self) + self.chartView.setRenderHint(QPainter.Antialiasing) + self.layout().addWidget(self.chartView, 0, 0, 2, 1) + + self.timeAxis = None + self.tempAxis = None + self.currentAxis = None + self.adcAxis = None + self.stateAxis = None + + self.measGroup = QGroupBox(self) + self.measGroup.setLayout(QGridLayout()) + + self.enaLineCurrentRealR = QCheckBox("Current real R", self) + self.measGroup.layout().addWidget(self.enaLineCurrentRealR, 0, 0) + + self.enaLineCurrentUsedR = QCheckBox("Current used R", self) + self.measGroup.layout().addWidget(self.enaLineCurrentUsedR, 1, 0) + + self.enaLineCurrentRState = QCheckBox("Current R state", self) + self.measGroup.layout().addWidget(self.enaLineCurrentRState, 2, 0) + + self.enaLineCurrentY = QCheckBox("Current-Y", self) + self.measGroup.layout().addWidget(self.enaLineCurrentY, 3, 0) + + self.enaLineTempR = QCheckBox("Temp R", self) + self.enaLineTempR.setCheckState(Qt.Checked) + self.measGroup.layout().addWidget(self.enaLineTempR, 0, 1) + + self.enaLineTempY1 = QCheckBox("Temp Y1", self) + self.enaLineTempY1.setCheckState(Qt.Checked) + self.measGroup.layout().addWidget(self.enaLineTempY1, 1, 1) + + self.enaLineTempY2 = QCheckBox("Temp Y2", self) + self.measGroup.layout().addWidget(self.enaLineTempY2, 2, 1) + + self.enaLineMeasCurr = QCheckBox("Current ADC", self) + self.measGroup.layout().addWidget(self.enaLineMeasCurr, 0, 2) + + self.enaLineFiltCurr = QCheckBox("Filtered current ADC", self) + self.measGroup.layout().addWidget(self.enaLineFiltCurr, 1, 2) + + self.enaLineMeasTemp = QCheckBox("Temp ADC", self) + self.measGroup.layout().addWidget(self.enaLineMeasTemp, 2, 2) + + self.enaLineBoostMode = QCheckBox("Boost mode", self) + self.measGroup.layout().addWidget(self.enaLineBoostMode, 3, 2) + + self.layout().addWidget(self.measGroup, 0, 1) + + self.controlsGroup = QGroupBox(self) + self.controlsGroup.setLayout(QGridLayout()) + + self.resetButton = QPushButton("Reset", self) + self.controlsGroup.layout().addWidget(self.resetButton, 0, 0, 1, 2) + + label = QLabel("Temp sp:", self) + self.controlsGroup.layout().addWidget(label, 1, 0) + self.tempSp = QSlider(Qt.Horizontal, self) + self.tempSp.setRange(-50, 550) + self.tempSp.setValue(350) + self.controlsGroup.layout().addWidget(self.tempSp, 1, 1) + + self.controlsGroup.layout().setRowStretch(99, 1) + self.layout().addWidget(self.controlsGroup, 1, 1) + + self.chartTimer = QTimer(self) + self.chartTimer.timeout.connect(self.__chartUpdate) + self.chartTimer.setSingleShot(False) + self.chartTimer.setTimerType(Qt.PreciseTimer) + self.chartTimer.start(100) + + self.resetButton.released.connect(self.__handleReset) + self.enaLineCurrentRealR.stateChanged.connect(self.__handleChartChange) + self.enaLineCurrentUsedR.stateChanged.connect(self.__handleChartChange) + self.enaLineCurrentRState.stateChanged.connect(self.__handleChartChange) + self.enaLineCurrentY.stateChanged.connect(self.__handleChartChange) + self.enaLineTempR.stateChanged.connect(self.__handleChartChange) + self.enaLineTempY1.stateChanged.connect(self.__handleChartChange) + self.enaLineTempY2.stateChanged.connect(self.__handleChartChange) + self.enaLineMeasCurr.stateChanged.connect(self.__handleChartChange) + self.enaLineFiltCurr.stateChanged.connect(self.__handleChartChange) + self.enaLineMeasTemp.stateChanged.connect(self.__handleChartChange) + self.enaLineBoostMode.stateChanged.connect(self.__handleChartChange) + self.tempSp.valueChanged.connect(self.__handleValueChange) + + self.__handleReset() + + def __handleReset(self): + self.__handleChartChange() + + if self.simProc: + self.simProc.shutdown() + self.simProc = SimulatorProcess() + self.simProc.start() + + self.__handleValueChange() + + def __handleChartChange(self): + self.chart.removeAllSeries() + + if self.timeAxis is None: + self.timeAxis = QValueAxis() + self.chart.addAxis(self.timeAxis, Qt.AlignBottom) + self.timeAxis.setRange(0, 100) + self.timeAxis.setLabelFormat("%d s") + self.timeAxis.setTickCount(10) + + if self.currentAxis is None: + self.currentAxis = QValueAxis() + self.chart.addAxis(self.currentAxis, Qt.AlignLeft) + self.currentAxis.setLabelFormat("%.1f A") + self.currentAxis.setRange(0, 5) + + if self.tempAxis is None: + self.tempAxis = QValueAxis() + self.chart.addAxis(self.tempAxis, Qt.AlignLeft) + self.tempAxis.setLabelFormat("%d *C") + self.tempAxis.setRange(0, 600) + + if self.adcAxis is None: + self.adcAxis = QValueAxis() + self.chart.addAxis(self.adcAxis, Qt.AlignRight) + self.adcAxis.setLabelFormat("0x%X") + self.adcAxis.setRange(0, 0x3FF) + + if self.stateAxis is None: + self.stateAxis = QValueAxis() + self.chart.addAxis(self.stateAxis, Qt.AlignRight) + self.stateAxis.setTickCount(3) + self.stateAxis.setLabelFormat("%d") + self.stateAxis.setRange(0, 2) + + self.chartLineCurrentRealR = None + self.chartLineCurrentUsedR = None + self.chartLineCurrentRState = None + self.chartLineCurrentY = None + self.chartLineTempR = None + self.chartLineTempY1 = None + self.chartLineTempY2 = None + self.chartLineMeasCurr = None + self.chartLineFiltCurr = None + self.chartLineMeasTemp = None + self.chartLineBoostMode = None + + if self.enaLineCurrentRealR.checkState() == Qt.Checked: + self.chartLineCurrentRealR = QLineSeries() + self.chartLineCurrentRealR.setName("Current real R (A)") + self.chart.addSeries(self.chartLineCurrentRealR) + self.chartLineCurrentRealR.attachAxis(self.timeAxis) + self.chartLineCurrentRealR.attachAxis(self.currentAxis) + + if self.enaLineCurrentUsedR.checkState() == Qt.Checked: + self.chartLineCurrentUsedR = QLineSeries() + self.chartLineCurrentUsedR.setName("Current used R (A)") + self.chart.addSeries(self.chartLineCurrentUsedR) + self.chartLineCurrentUsedR.attachAxis(self.timeAxis) + self.chartLineCurrentUsedR.attachAxis(self.currentAxis) + + if self.enaLineCurrentRState.checkState() == Qt.Checked: + self.chartLineCurrentRState = QLineSeries() + self.chartLineCurrentRState.setName("Current R state") + self.chart.addSeries(self.chartLineCurrentRState) + self.chartLineCurrentRState.attachAxis(self.timeAxis) + self.chartLineCurrentRState.attachAxis(self.stateAxis) + + if self.enaLineCurrentY.checkState() == Qt.Checked: + self.chartLineCurrentY = QLineSeries() + self.chartLineCurrentY.setName("Current Y (A)") + self.chart.addSeries(self.chartLineCurrentY) + self.chartLineCurrentY.attachAxis(self.timeAxis) + self.chartLineCurrentY.attachAxis(self.currentAxis) + + if self.enaLineTempR.checkState() == Qt.Checked: + self.chartLineTempR = QLineSeries() + self.chartLineTempR.setName("Temp R (*C)") + self.chart.addSeries(self.chartLineTempR) + self.chartLineTempR.attachAxis(self.timeAxis) + self.chartLineTempR.attachAxis(self.tempAxis) + + if self.enaLineTempY1.checkState() == Qt.Checked: + self.chartLineTempY1 = QLineSeries() + self.chartLineTempY1.setName("Temp Y1 (*C)") + self.chart.addSeries(self.chartLineTempY1) + self.chartLineTempY1.attachAxis(self.timeAxis) + self.chartLineTempY1.attachAxis(self.tempAxis) + + if self.enaLineTempY2.checkState() == Qt.Checked: + self.chartLineTempY2 = QLineSeries() + self.chartLineTempY2.setName("Temp Y2 (A)") + self.chart.addSeries(self.chartLineTempY2) + self.chartLineTempY2.attachAxis(self.timeAxis) + self.chartLineTempY2.attachAxis(self.currentAxis) + + if self.enaLineMeasCurr.checkState() == Qt.Checked: + self.chartLineMeasCurr = QLineSeries() + self.chartLineMeasCurr.setName("Current ADC (hex)") + self.chart.addSeries(self.chartLineMeasCurr) + self.chartLineMeasCurr.attachAxis(self.timeAxis) + self.chartLineMeasCurr.attachAxis(self.adcAxis) + + if self.enaLineFiltCurr.checkState() == Qt.Checked: + self.chartLineFiltCurr = QLineSeries() + self.chartLineFiltCurr.setName("Filtered current ADC (hex)") + self.chart.addSeries(self.chartLineFiltCurr) + self.chartLineFiltCurr.attachAxis(self.timeAxis) + self.chartLineFiltCurr.attachAxis(self.adcAxis) + + if self.enaLineMeasTemp.checkState() == Qt.Checked: + self.chartLineMeasTemp = QLineSeries() + self.chartLineMeasTemp.setName("Temp ADC (hex)") + self.chart.addSeries(self.chartLineMeasTemp) + self.chartLineMeasTemp.attachAxis(self.timeAxis) + self.chartLineMeasTemp.attachAxis(self.adcAxis) + + if self.enaLineBoostMode.checkState() == Qt.Checked: + self.chartLineBoostMode = QLineSeries() + self.chartLineBoostMode.setName("Boost mode") + self.chart.addSeries(self.chartLineBoostMode) + self.chartLineBoostMode.attachAxis(self.timeAxis) + self.chartLineBoostMode.attachAxis(self.stateAxis) + + self.chartXCount = 0 + + def __handleValueChange(self): + temp = float(self.tempSp.value()) + self.simProc.setValues(temp=temp) + + def __chartUpdate(self): + for ident, args in self.simProc.getResponses(): + if ident == "meas": + meas = args + self.chartXCount += 1 + for chartLine, value in ( + (self.chartLineCurrentRealR, meas.dbg_currentRealR), + (self.chartLineCurrentUsedR, meas.dbg_currentUsedR), + (self.chartLineCurrentRState, meas.dbg_currentRState), + (self.chartLineCurrentY, meas.dbg_currentY), + (self.chartLineTempR, meas.dbg_tempR), + (self.chartLineTempY1, meas.dbg_tempY1), + (self.chartLineTempY2, meas.dbg_tempY2), + (self.chartLineMeasCurr, meas.dbg_measCurr), + (self.chartLineFiltCurr, meas.dbg_filtCurr), + (self.chartLineMeasTemp, meas.dbg_measTemp), + (self.chartLineBoostMode, meas.dbg_boostMode)): + if chartLine is None: + continue + chartLine.append(self.chartXCount - 1, value) + if self.chartXCount > 100: + chartLine.remove(0) + if self.chartXCount > 100: + self.chart.axisX().setRange(self.chartXCount - 100, + self.chartXCount) + self.simProc.requestMeasurements() + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + QMainWindow.__init__(self, parent) + self.setWindowTitle("xytronic-lf simulator") + + self.mainWidget = MainWidget(self) + self.setCentralWidget(self.mainWidget) + + self.resize(1100, 500) + + def closeEvent(self, ev): + self.mainWidget.simProc.shutdown() + +def main(): + qapp = QApplication(sys.argv) + mainwnd = MainWindow() + mainwnd.show() + return qapp.exec_() + +if __name__ == "__main__": + sys.exit(main()) -- cgit v1.2.3