diff --git a/GNUmakefile b/GNUmakefile index 0425d2a..b773253 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ BUILDCLASSES = Linux ARCH_FILTER = deb10% # Run 7.0.6 for now -EXCLUDE_VERSIONS+=3 7.0.5 7.0.7 +EXCLUDE_VERSIONS+=3 7.0.5 7.0.6 IGNORE_MODULES += asynMotor IGNORE_MODULES += motorBase @@ -17,7 +17,7 @@ OPT_CXXFLAGS_YES = -O3 # dependencies ECmasterECMC_VERSION = v1.1.0 motorECMC_VERSION = 7.0.7-ESS -ecmc_VERSION = v9.0.1_RC1 +ecmc_VERSION = 9.5.0 ################################################################################ # THIS RELATES TO THE EtherCAT MASTER LIBRARY diff --git a/README.md b/README.md index 0706d0d..802eb9b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Plugin designed for commisioning and troubleshooting of motion axes. Motion data are sampled, buffered and exposed to epics as waveforms. +python3 ecmcMotionMainPyQtGraph.py c6025a-04 0 diff --git a/ecmc_plugin_motion/src/ecmcMotionPlg.cpp b/ecmc_plugin_motion/src/ecmcMotionPlg.cpp index 5cf7fd7..2aea597 100644 --- a/ecmc_plugin_motion/src/ecmcMotionPlg.cpp +++ b/ecmc_plugin_motion/src/ecmcMotionPlg.cpp @@ -906,7 +906,7 @@ asynStatus ecmcMotionPlg::writeInt32(asynUser *pasynUser, epicsInt32 value) { } else if( function == asynAxisId_){ return setAxis(value) > 0 ? asynSuccess : asynError; } else if( function == asynModeId_){ - return setMode((TRIGG_MODE)value) > 0 ? asynSuccess : asynError; + return setMode((TRIGG_MODE)value) == 0 ? asynSuccess : asynError; } else if( function == asynModeId_){ return setTrigg(value) ? asynSuccess :asynError; } diff --git a/ecmc_plugin_motion/src/ecmcPluginMotion.c b/ecmc_plugin_motion/src/ecmcPluginMotion.c index e240feb..413f03a 100644 --- a/ecmc_plugin_motion/src/ecmcPluginMotion.c +++ b/ecmc_plugin_motion/src/ecmcPluginMotion.c @@ -48,10 +48,10 @@ int motionConstruct(char *configStr) **/ void motionDestruct(void) { - deleteAllMotionObjs(); - if(lastConfStr){ - free(lastConfStr); - } + //deleteAllMotionObjs(); + //if(lastConfStr){ + // free(lastConfStr); + //} } /** Optional function. diff --git a/tools/README.md b/tools/README.md index 9d714a3..62bcad4 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,8 +1,2 @@ -# FFT tools - -## GUI - -A python gui for vizualization and control of the FFT plugin can be found in the ecmccomgui repo: -https://github.com/anderssandstrom/ecmccomgui - -![ecmcFFTMainGui.py](docs/gui/ecmcFFTMainGui.png) +# Tool for visualization +python3 ecmcMotionMainPyQtGraph.py c6025a-04 0 diff --git a/tools/ecmcMotionMainGui.py b/tools/ecmcMotionMainGui.py index 21d6699..64acf81 100644 --- a/tools/ecmcMotionMainGui.py +++ b/tools/ecmcMotionMainGui.py @@ -166,12 +166,14 @@ class ecmcMtnMainGui(QtWidgets.QDialog): self.pauseBtn.setFixedSize(100, 50) self.pauseBtn.clicked.connect(self.pauseBtnAction) self.pauseBtn.setStyleSheet("background-color: green") - self.openBtn = QPushButton(text = 'open data') + self.openBtn = QPushButton(text = 'open data') self.openBtn.setFixedSize(100, 50) + self.openBtn.setEnabled(False) # Not yet supported self.openBtn.clicked.connect(self.openBtnAction) self.saveBtn = QPushButton(text = 'save data') self.saveBtn.setFixedSize(100, 50) self.saveBtn.clicked.connect(self.saveBtnAction) + self.saveBtn.setEnabled(False) # Not yet supported self.enableBtn = QPushButton(text = 'enable Mtn') self.enableBtn.setFixedSize(100, 50) self.enableBtn.clicked.connect(self.enableBtnAction) @@ -181,11 +183,13 @@ class ecmcMtnMainGui(QtWidgets.QDialog): self.zoomBtn = QPushButton(text = 'auto zoom') self.zoomBtn.setFixedSize(100, 50) self.zoomBtn.clicked.connect(self.zoomBtnAction) + self.zoomBtn.setEnabled(False) # Not yet supported self.modeCombo = QComboBox() self.modeCombo.setFixedSize(100, 50) self.modeCombo.currentIndexChanged.connect(self.newModeIndexChanged) self.modeCombo.addItem("CONT") - self.modeCombo.addItem("TRIGG") + self.modeCombo.addItem("TRIGG" + self.modeCombo.setEnabled(False)) # Not yet supported self.progressBar = QProgressBar() self.progressBar.reset() self.progressBar.setMinimum(0) diff --git a/tools/ecmcMotionMainPyQtGraph.py b/tools/ecmcMotionMainPyQtGraph.py index 6241e66..0f6ab38 100644 --- a/tools/ecmcMotionMainPyQtGraph.py +++ b/tools/ecmcMotionMainPyQtGraph.py @@ -24,8 +24,12 @@ import pyqtgraph as pg from ecmcPvDataItem import * from ecmcParseAxisStatusWord import * +import os + # Allow buffering of 10s data, need to add setting for this xMaxTime = 10 +caqtdmAxisExpertPanelName = "ecmcAxisExpert_v1.ui" +caqtdmExe ="/usr/local/bin/caqtdm" # List of pv names pvlist = [ 'BuffSze', @@ -58,14 +62,12 @@ pvAnalog = ['PosAct-Arr', 'Stat-Arr'] pvAnaPLotsDefaultEnabled = ['PosAct-Arr', - 'PosSet-Arr', - 'PosErr-Arr'] + 'PosSet-Arr'] pvBinPLotsDefaultEnabled = ['enable', 'enabled', 'busy', - 'attarget', - 'moving'] + 'attarget'] pvBinBlock = ['instartup', 'inrealtime', 'axisType', @@ -83,7 +85,7 @@ pvFistAxisIndexName = 'MCU-Cfg-AX-FrstObjId' pvNextAxisIndexNamePart1 = 'MCU-Cfg-AX' pvNextAxisIndexNamePart2 = '-NxtObjId' -pvmiddlestring='Plg-Mtn' +pvmiddlestring=':Plg-Mtn' class ecmcMtnMainGui(QtWidgets.QDialog): def __init__(self,prefix="IOC_TEST:",mtnPluginId=0): @@ -188,7 +190,8 @@ class ecmcMtnMainGui(QtWidgets.QDialog): # Check connection and read sample rate pvSampleRate = epics.PV(self.pvPrefixStr + pvmiddlestring + str(int(self.mtnPluginId))+ '-SmpHz-RB') - connected = pvSampleRate.wait_for_connection(timeout = 2) + print(self.pvPrefixStr + pvmiddlestring + str(int(self.mtnPluginId))+ '-SmpHz-RB') + connected = pvSampleRate.wait_for_connection(timeout = 3) if connected: print('Connected to ecmc') self.offline = False @@ -200,7 +203,8 @@ class ecmcMtnMainGui(QtWidgets.QDialog): else: print('Not Connected') self.offline = True - self.pause = True + self.pause = True + self.sampleRate = 1000 self.sampleRateValid = True @@ -242,37 +246,58 @@ class ecmcMtnMainGui(QtWidgets.QDialog): self.plotItemBinary.setFixedHeight(150) self.plotItemBinary.setMouseEnabled(y=False) self.plotItemBinary.setLabel('bottom', 'Time [s]') + self.pauseBtn = QPushButton(text = 'pause') self.pauseBtn.setFixedSize(100, 50) self.pauseBtn.clicked.connect(self.pauseBtnAction) self.pauseBtn.setStyleSheet("background-color: green") + #self.pauseBtn.setEnabled(False) + #self.pauseBtn.setVisible(False) + self.openBtn = QPushButton(text = 'open data') self.openBtn.setFixedSize(100, 50) self.openBtn.clicked.connect(self.openBtnAction) + self.openBtn.setEnabled(False) + self.openBtn.setVisible(False) + self.saveBtn = QPushButton(text = 'save data') self.saveBtn.setFixedSize(100, 50) self.saveBtn.clicked.connect(self.saveBtnAction) + self.saveBtn.setEnabled(False) + self.saveBtn.setVisible(False) + self.enableBtn = QPushButton(text = 'enable Mtn') self.enableBtn.setFixedSize(100, 50) self.enableBtn.clicked.connect(self.enableBtnAction) self.triggBtn = QPushButton(text = 'trigg Mtn') self.triggBtn.setFixedSize(100, 50) + self.triggBtn.setEnabled(False) + self.triggBtn.setVisible(False) + self.triggBtn.clicked.connect(self.triggBtnAction) self.zoomBtn = QPushButton(text = 'auto zoom') self.zoomBtn.setFixedSize(100, 50) self.zoomBtn.clicked.connect(self.zoomBtnAction) + self.zoomBtn.setEnabled(False) + self.zoomBtn.setVisible(False) + self.modeCombo = QComboBox() self.modeCombo.setFixedSize(100, 50) self.modeCombo.currentIndexChanged.connect(self.newModeIndexChanged) self.modeCombo.addItem("CONT") self.modeCombo.addItem("TRIGG") + self.modeCombo.setEnabled(False) + self.modeCombo.setVisible(False) + self.progressBar = QProgressBar() self.progressBar.reset() self.progressBar.setMinimum(0) self.progressBar.setMaximum(100) #100% self.progressBar.setValue(0) self.progressBar.setFixedHeight(20) - + self.progressBar.setEnabled(False) + self.progressBar.setVisible(False) + # Fix layout self.setGeometry(300, 300, 1200, 900) @@ -367,17 +392,23 @@ class ecmcMtnMainGui(QtWidgets.QDialog): frameMotion = QFrame(self) layoutMotionGrid = QGridLayout() frameMotion.setLayout(layoutMotionGrid) + self.btnMotorRecord = QPushButton(text = 'Motor Record') self.btnMotorRecord.clicked.connect(self.openMotorRecordPanel) - self.btnMotorRecord.setFixedSize(100, 50) + self.btnMotorRecord.setFixedSize(150, 50) layoutMotionGrid.addWidget(self.btnMotorRecord,0,0) + self.btnCaQTDmAxisExpert = QPushButton(text = 'caqtdm ecmc Axis') + self.btnCaQTDmAxisExpert.clicked.connect(self.openCaQTDmAxisExpert) + self.btnCaQTDmAxisExpert.setFixedSize(150, 50) + layoutMotionGrid.addWidget(self.btnCaQTDmAxisExpert,1,0) + label = QLabel('Axis id:') self.cmbBxSelectAxis = QComboBox() self.cmbBxSelectAxis.currentIndexChanged.connect(self.changeAxisIndex) - layoutMotionGrid.addWidget(label,1,0) - layoutMotionGrid.addWidget(self.cmbBxSelectAxis,1,1) + layoutMotionGrid.addWidget(label,2,0) + layoutMotionGrid.addWidget(self.cmbBxSelectAxis,2,1) layoutVertMain.addWidget(frameMotion) @@ -432,10 +463,14 @@ class ecmcMtnMainGui(QtWidgets.QDialog): QCoreApplication.processEvents() def sig_cb_PosAct_Arr(self,value): + if self.pause: + return if(np.size(value)) > 0: self.MtnYDataValid = True def sig_cb_Time_Arr(self,value): + if self.pause: + return if(np.size(value)) > 0: self.MtnXDataValid = True self.plotAll() @@ -467,13 +502,13 @@ class ecmcMtnMainGui(QtWidgets.QDialog): if id >= 0: self.cmbBxSelectAxis.setCurrentIndex(id) - axisPrefixPvName = self.pvPrefixStr + pvAxisPrefixNamePart1 + str(int(value)) + pvAxisPrefixNamePart2 + axisPrefixPvName = self.pvPrefixStr + ":" + pvAxisPrefixNamePart1 + str(int(value)) + pvAxisPrefixNamePart2 prefixPV = epics.PV(axisPrefixPvName) axisPrefix = prefixPV.get() if axisPrefix is not None: - self.axisPrefix = axisPrefix + self.axisPrefix = axisPrefix.rstrip(':') - axisNamePvName = self.pvPrefixStr + pvAxisNamePart1 + str(int(value)) + pvAxisNamePart2 + axisNamePvName = self.pvPrefixStr + ":" + pvAxisNamePart1 + str(int(value)) + pvAxisNamePart2 namePV = epics.PV(axisNamePvName) axisName = namePV.get() if axisName is not None: @@ -488,6 +523,8 @@ class ecmcMtnMainGui(QtWidgets.QDialog): return def sig_cb_Stat_Arr(self,value): + if self.pause: + return data = self.parseAxisStatWd.convert(value) self.addStatWdData(data) @@ -521,7 +558,7 @@ class ecmcMtnMainGui(QtWidgets.QDialog): self.plotBinary() def readAxisList(self): - axIdPV = epics.PV(self.pvPrefixStr + pvFistAxisIndexName) + axIdPV = epics.PV(self.pvPrefixStr + ":" + pvFistAxisIndexName) axId = axIdPV.get() if axId is None: print('ERROR: First Axis Index PV not found.') @@ -532,7 +569,7 @@ class ecmcMtnMainGui(QtWidgets.QDialog): while axId >= 0: # Get next axis id - pvName = self.pvPrefixStr + pvNextAxisIndexNamePart1 + str(int(axId)) + pvNextAxisIndexNamePart2 + pvName = self.pvPrefixStr + ":" + pvNextAxisIndexNamePart1 + str(int(axId)) + pvNextAxisIndexNamePart2 axIdPV = epics.PV(pvName) axId = axIdPV.get() @@ -544,10 +581,22 @@ class ecmcMtnMainGui(QtWidgets.QDialog): self.pvItems['AxCmd-RB'].pvPut(self.cmbBxSelectAxis.currentData(), use_complete=True) def openMotorRecordPanel(self,xxx): - self.dialog = MotorPanel(self,self.axisPrefix ,self.axisName) - self.dialog.resize(500, 900) + self.dialog = MotorPanel(self,self.axisPrefix + ':' ,self.axisName) + self.dialog.resize(500, 900) self.dialog.show() + def openCaQTDmAxisExpert(self,xxx): + # caqtdm -macro "IOC=$I,SYS=$S,Axis=$A" ecmcAxisExpert_v1.ui + caqtdmString = caqtdmExe + " -macro " + caqtdmMacros = "SYS=" + self.axisPrefix + caqtdmMacros += ",IOC=" + self.axisPrefix + caqtdmMacros += ",Axis=" + self.axisName + + caqtdmString+= "\"" + caqtdmMacros + "\"" + " " + caqtdmAxisExpertPanelName + print(caqtdmString) + #subprocess.call(caqtdmString) + os.system(caqtdmString) + ###### Widget callbacks def pauseBtnAction(self): self.pause = not self.pause @@ -815,9 +864,9 @@ class ecmcMtnMainGui(QtWidgets.QDialog): def printOutHelp(): print("ecmcMtnMainGui: Plots waveforms of Mtn data (updates on Y data callback). ") print("python ecmcMtnMainGui.py ") - print(": Ioc prefix ('IOC_TEST:')") + print(": Ioc prefix ('IOC_TEST')") print(" : Id of mtn plugin ('0')") - print("example : python ecmcMotionMainGui.py 'IOC_TEST:' '0'") + print("example : python ecmcMotionMainGui.py 'IOC_TEST' '0'") print("Will connect to Pvs: Plg-Mtn-*") if __name__ == "__main__": diff --git a/tools/old/ecmcMotionMainGui.py b/tools/old/ecmcMotionMainGui.py new file mode 100644 index 0000000..21d6699 --- /dev/null +++ b/tools/old/ecmcMotionMainGui.py @@ -0,0 +1,848 @@ +#************************************************************************* +# Copyright (c) 2020 European Spallation Source ERIC +# ecmc is distributed subject to a Software License Agreement found +# in file LICENSE that is included with this distribution. +# +# ecmcMtnMainGui.py +# +# Created on: October 6, 2020 +# Author: Anders Sandström +# +# Plots two waveforms (x vs y) updates for each callback on the y-pv +# +#************************************************************************* + +import sys +import os +import epics +from PyQt5.QtWidgets import * +from PyQt5 import QtWidgets +from PyQt5.QtCore import * +from PyQt5.QtGui import * +import numpy as np +import matplotlib +matplotlib.use("Qt5Agg") +from matplotlib.figure import Figure +from matplotlib.animation import TimedAnimation +from matplotlib.lines import Line2D +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +import matplotlib.pyplot as plt +import threading + +# Allow buffering of 10s data, need to add setting for this +xMaxTime = 10 + +# List of pv names +pvlist = [ 'BuffSze', + 'ElmCnt', + 'PosAct-Arr', + 'PosSet-Arr', + 'PosErr-Arr', + 'Time-Arr', + 'Ena-Arr', + 'EnaAct-Arr', + 'Bsy-Arr', + 'Exe-Arr', + 'TrjSrc-Arr', + 'EncSrc-Arr', + 'AtTrg-Arr', + 'ErrId-Arr', + 'Mde-RB', + 'Cmd-RB', + 'Stat', + 'AxCmd-RB', + 'SmpHz-RB', + 'TrgCmd-RB', + 'EnaCmd-RB' ] + +pvAnalog = ['PosAct-Arr', + 'PosSet-Arr', + 'PosErr-Arr', + 'ErrId-Arr'] + +pvBinary = ['Ena-Arr', + 'EnaAct-Arr', + 'Bsy-Arr', + 'Exe-Arr', + 'TrjSrc-Arr', + 'EncSrc-Arr', + 'AtTrg-Arr'] + +pvmiddlestring='Plg-Mtn' + +class comSignal(QObject): + data_signal = pyqtSignal(object) + +class ecmcMtnMainGui(QtWidgets.QDialog): + def __init__(self,prefix=None,mtnPluginId=None): + super(ecmcMtnMainGui, self).__init__() + + self.pvnames = {} + self.pvs = {} + self.pv_signal_cbs = {} + self.data = {} + self.datalength = {} + self.plottedLineAnalog = {} + self.plottedLineBinary = {} + + for pv in pvAnalog: + self.plottedLineAnalog[pv] = None + + for pv in pvBinary: + self.plottedLineBinary[pv] = None + + for pv in pvlist: + self.data[pv] = None + self.datalength[pv] = 0 + + #Set some default plot colours + self.plotColor={} + # Analog + self.plotColor['PosAct-Arr']='g' + self.plotColor['PosSet-Arr']='b' + self.plotColor['PosErr-Arr']='k' + self.plotColor['ErrId-Arr']='r' + + # Binary + self.plotColor['Ena-Arr']='b' + self.plotColor['EnaAct-Arr']='c' + self.plotColor['Bsy-Arr']='r' + self.plotColor['Exe-Arr']='m' + self.plotColor['TrjSrc-Arr']='y' + self.plotColor['EncSrc-Arr']='k' + self.plotColor['AtTrg-Arr']='g' + + self.offline = False + self.pvPrefixStr = prefix + self.pvPrefixOrigStr = prefix # save for restore after open datafile + self.mtnPluginId = mtnPluginId + self.mtnPluginOrigId = mtnPluginId + self.allowSave = False + self.path = '.' + self.unitAnalogY = "[]" + self.unitBinaryY = "[]" + self.labelBinaryY = "Binary" + self.labelAnalogY = "Analog" + self.title = "" + #self.NMtn = 1024 + self.sampleRate = 1000 + self.sampleRateValid = False + self.MtnYDataValid = False + self.MtnXDataValid = False + + if prefix is None or mtnPluginId is None: + self.offline = True + self.pause = True + self.data['EnaCmd-RB'] = False + else: + #Check for connection else go offline + self.buildPvNames() + connected = self.pvs['BuffSze'].wait_for_connection(timeout=2) + if connected: + self.offline = False + self.pause = False + else: + self.offline = True + self.pause = True + self.data['EnaCmd-RB'] = False + + self.startupDone=False + self.pause = 0 + self.createWidgets() + self.setStatusOfWidgets() + self.resize(1000,850) + return + + def createWidgets(self): + self.figure = plt.figure() + #self.plottedLineAnalog = None + #self.plottedLineBinary = None + self.axAnalog = None + self.axBinary = None + self.canvas = FigureCanvas(self.figure) + self.toolbar = NavigationToolbar(self.canvas, self) + self.pauseBtn = QPushButton(text = 'pause') + self.pauseBtn.setFixedSize(100, 50) + self.pauseBtn.clicked.connect(self.pauseBtnAction) + self.pauseBtn.setStyleSheet("background-color: green") + self.openBtn = QPushButton(text = 'open data') + self.openBtn.setFixedSize(100, 50) + self.openBtn.clicked.connect(self.openBtnAction) + self.saveBtn = QPushButton(text = 'save data') + self.saveBtn.setFixedSize(100, 50) + self.saveBtn.clicked.connect(self.saveBtnAction) + self.enableBtn = QPushButton(text = 'enable Mtn') + self.enableBtn.setFixedSize(100, 50) + self.enableBtn.clicked.connect(self.enableBtnAction) + self.triggBtn = QPushButton(text = 'trigg Mtn') + self.triggBtn.setFixedSize(100, 50) + self.triggBtn.clicked.connect(self.triggBtnAction) + self.zoomBtn = QPushButton(text = 'auto zoom') + self.zoomBtn.setFixedSize(100, 50) + self.zoomBtn.clicked.connect(self.zoomBtnAction) + self.modeCombo = QComboBox() + self.modeCombo.setFixedSize(100, 50) + self.modeCombo.currentIndexChanged.connect(self.newModeIndexChanged) + self.modeCombo.addItem("CONT") + self.modeCombo.addItem("TRIGG") + self.progressBar = QProgressBar() + self.progressBar.reset() + self.progressBar.setMinimum(0) + self.progressBar.setMaximum(100) #100% + self.progressBar.setValue(0) + self.progressBar.setFixedHeight(20) + + # Fix layout + self.setGeometry(300, 300, 900, 700) + + layoutVert = QVBoxLayout() + layoutVert.addWidget(self.toolbar) + layoutVert.addWidget(self.canvas) + + layoutControl = QHBoxLayout() + layoutControl.addWidget(self.pauseBtn) + layoutControl.addWidget(self.enableBtn) + layoutControl.addWidget(self.triggBtn) + layoutControl.addWidget(self.modeCombo) + layoutControl.addWidget(self.zoomBtn) + layoutControl.addWidget(self.saveBtn) + layoutControl.addWidget(self.openBtn) + + frameControl = QFrame(self) + frameControl.setFixedHeight(70) + frameControl.setLayout(layoutControl) + + + layoutVert.addWidget(frameControl) + layoutVert.addWidget(self.progressBar) + self.setLayout(layoutVert) + + def setStatusOfWidgets(self): + self.saveBtn.setEnabled(self.allowSave) + if self.offline: + self.enableBtn.setStyleSheet("background-color: grey") + self.enableBtn.setEnabled(False) + self.pauseBtn.setStyleSheet("background-color: grey") + self.pauseBtn.setEnabled(False) + self.modeCombo.setEnabled(False) + self.triggBtn.setEnabled(False) + self.setWindowTitle("ecmc Mtn Main plot: Offline") + else: + self.modeCombo.setEnabled(True) + # Check actual value of pvs + enable = self.pvs['EnaCmd-RB'].get() + if enable is None: + print("pvs['EnaCmd-RB'].get() failed") + return + if(enable>0): + self.enableBtn.setStyleSheet("background-color: green") + self.data['EnaCmd-RB'] = True + else: + self.enableBtn.setStyleSheet("background-color: red") + self.data['EnaCmd-RB'] = False + + #self.sourceStr = self.pvSource.get(as_string=True) + #if self.sourceStr is None: + # print("pvSource.get() failed") + # return + + self.sampleRate = self.pvs['SmpHz-RB'].get() + if self.sampleRate is None: + print("pvs['SmpHz-RB'].get() failed") + return + self.sampleRateValid = True + + # calc x Array + step=1/self.sampleRate + + self.x = np.arange(-xMaxTime-step,0+step,step) + print('x') + print(self.x) + + self.data['Mde-RB'] = self.pvs['Mde-RB'].get() + if self.data['Mde-RB'] is None: + print("pvs['Mde-RB'].get() failed") + return + + self.modeStr = "NO_MODE" + self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2 + if self.data['Mde-RB'] == 1: + self.modeStr = "CONT" + self.modeCombo.setCurrentIndex(self.data['Mde-RB']-1) # Index starta t zero + + if self.data['Mde-RB'] == 2: + self.modeStr = "TRIGG" + self.triggBtn.setEnabled(True) + self.modeCombo.setCurrentIndex(self.data['Mde-RB']-1) # Index starta t zero + + self.setWindowTitle("ecmc Mtn Main plot: prefix=" + self.pvPrefixStr + " , mtnId=" + str(self.mtnPluginId) + + ", rate=" + str(self.sampleRate)) + + def addData(self, pvName, values): + # Check if first assignment + if self.data[pvName] is None: + self.data[pvName] = values + return + + self.data[pvName]=np.append(self.data[pvName],values) + # check if delete in beginning is needed + currcount = len(self.data[pvName]) + if self.sampleRateValid: + allowedcount = int(xMaxTime * self.sampleRate) + else: + print('Warning sample rate not defined, fallback to max 10000 values') + allowedcount = 10000 + + # remove if needed + if currcount > allowedcount: + self.data[pvName]=self.data[pvName][currcount-allowedcount:] + + self.datalength[pvName] = len(self.data[pvName]) + + + def buildPvNames(self): + # Pv names based on structure: Plugin-Mtn- + for pv in pvlist: + self.pvnames[pv]=self.buildPvName(pv) + if self.pvnames[pv] is None: + raise RuntimeError("pvname must not be 'None'") + if len(self.pvnames[pv])==0: + raise RuntimeError("pvname must not be ''") + self.pvs[pv] = epics.PV(self.pvnames[pv]) + self.pv_signal_cbs[pv] = comSignal() + + # Signal callbacks (update gui) + # replace any '-' with '_' since '-' not allowed in funcion names + sig_cb_func=getattr(self,'sig_cb_' + pv.replace('-','_')) + self.pv_signal_cbs[pv].data_signal.connect(sig_cb_func) + + # Pv monitor callbacks + mon_cb_func=getattr(self,'on_change_' + pv.replace('-','_')) + self.pvs[pv].add_callback(mon_cb_func) + + QCoreApplication.processEvents() + + def buildPvName(self, suffixname): + return self.pvPrefixStr + pvmiddlestring + str(self.mtnPluginId) + '-' + suffixname + + ###### Pv monitor callbacks + def on_change_BuffSze(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['BuffSze'].data_signal.emit(value) + + def on_change_ElmCnt(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['ElmCnt'].data_signal.emit(value) + + def on_change_PosAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['PosAct-Arr'].data_signal.emit(value) + + def on_change_PosSet_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['PosSet-Arr'].data_signal.emit(value) + + def on_change_PosErr_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['PosErr-Arr'].data_signal.emit(value) + + def on_change_Time_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Time-Arr'].data_signal.emit(value) + + def on_change_Ena_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Ena-Arr'].data_signal.emit(value) + + def on_change_EnaAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['EnaAct-Arr'].data_signal.emit(value) + + def on_change_Bsy_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Bsy-Arr'].data_signal.emit(value) + + def on_change_Exe_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Exe-Arr'].data_signal.emit(value) + + def on_change_TrjSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['TrjSrc-Arr'].data_signal.emit(value) + + def on_change_EncSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['EncSrc-Arr'].data_signal.emit(value) + + def on_change_AtTrg_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['AtTrg-Arr'].data_signal.emit(value) + + def on_change_ErrId_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['ErrId-Arr'].data_signal.emit(value) + + def on_change_Mde_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Mde-RB'].data_signal.emit(value) + + def on_change_Cmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Cmd-RB'].data_signal.emit(value) + + def on_change_Stat(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['Stat'].data_signal.emit(value) + + def on_change_AxCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['AxCmd-RB'].data_signal.emit(value) + + def on_change_SmpHz_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['SmpHz-RB'].data_signal.emit(value) + + def on_change_TrgCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['TrgCmd-RB'].data_signal.emit(value) + + def on_change_EnaCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): + self.pv_signal_cbs['EnaCmd-RB'].data_signal.emit(value) + +# def onChangePvMode(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalMode.data_signal.emit(value) +# +# def onChangePvEnable(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalEnable.data_signal.emit(value) +# +# def onChangeX(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalX.data_signal.emit(value) +# +# def onChangePvSpectY(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalSpectY.data_signal.emit(value) +# +# def onChangePvrawData(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalRawData.data_signal.emit(value) +# +# def onChangePvBuffIdAct(self,pvname=None, value=None, char_value=None,timestamp=None, **kw): +# if self.pause: +# return +# self.comSignalBuffIdAct.data_signal.emit(value) + + ###### Signal callbacks + def sig_cb_BuffSze(self,value): + self.data['BuffSze'] = value + + def sig_cb_ElmCnt(self,value): + self.data['ElmCnt'] = value + + def sig_cb_PosAct_Arr(self,value): + if(np.size(value)) > 0: + self.MtnYDataValid = True + self.addData('PosAct-Arr', value) + + def sig_cb_PosSet_Arr(self,value): + self.addData('PosSet-Arr', value) + + def sig_cb_PosErr_Arr(self,value): + self.addData('PosErr-Arr', value) + + def sig_cb_Time_Arr(self,value): + if(np.size(value)) > 0: + self.addData('Time-Arr', value) + self.MtnXDataValid = True + self.plotAll() + return + + def sig_cb_Ena_Arr(self,value): + self.addData('Ena-Arr', value) + + def sig_cb_EnaAct_Arr(self,value): + self.addData('EnaAct-Arr', value) + + def sig_cb_Bsy_Arr(self,value): + self.addData('Bsy-Arr', value) + + def sig_cb_Exe_Arr(self,value): + self.addData('Exe-Arr', value) + + def sig_cb_TrjSrc_Arr(self,value): + self.addData('TrjSrc-Arr', value) + + def sig_cb_EncSrc_Arr(self,value): + self.addData('EncSrc-Arr', value) + + def sig_cb_AtTrg_Arr(self,value): + self.addData('AtTrg-Arr', value) + + def sig_cb_ErrId_Arr(self,value): + self.addData('ErrId-Arr', value) + + def sig_cb_Mde_RB(self,value): + if value < 1 or value> 2: + self.modeStr = "NO_MODE" + print('callbackFuncMode: Error Invalid mode.') + return + + self.data['Mde-RB'] = value + self.modeCombo.setCurrentIndex(self.data['Mde-RB']-1) # Index starta t zero + + if self.data['Mde-RB'] == 1: + self.modeStr = "CONT" + self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2 + + if self.data['Mde-RB'] == 2: + self.modeStr = "TRIGG" + self.triggBtn.setEnabled(True) + return + + def sig_cb_Cmd_RB(self,value): + self.data['Cmd-RB'] = value + + def sig_cb_Stat(self,value): + self.data['Stat'] = value + + def sig_cb_AxCmd_RB(self,value): + self.data['AxCmd-RB'] = value + + def sig_cb_SmpHz_RB(self,value): + self.data['SmpHz-RB'] = value + + def sig_cb_TrgCmd_RB(self,value): + self.data['TrgCmd-RB'] = value + + def sig_cb_EnaCmd_RB(self,value): + self.data['EnaCmd-RB'] = value + + self.data['EnaCmd-RB'] = value + if self.data['EnaCmd-RB']: + self.enableBtn.setStyleSheet("background-color: green") + else: + self.enableBtn.setStyleSheet("background-color: red") + self.data['EnaCmd-RB'] = value + return + + +# def callbackFuncSpectY(self, value): +# if(np.size(value)) > 0: +# +# self.spectY = value +# self.MtnYDataValid = self.RawXDataValid +# self.plotAll() +# return +# +# def callbackFuncrawData(self, value): +# if(np.size(value)) > 0: +# if (self.data['Time-Arr'] is None or np.size(value) != np.size(self.rawdataY)) and self.sampleRateValid: +# self.data['Time-Arr'] = np.arange(-np.size(value)/self.sampleRate, 0, 1/self.sampleRate) +# self.RawXDataValid = True +# +# self.rawdataY = value +# self.RawYDataValid = True +# self.plotAll() +# return +# +# def callbackFuncBuffIdAct(self, value): +# if self.NMtn is None: +# return +# if(self.NMtn>0): +# self.progressBar.setValue(value/self.NMtn*100) +# if value/self.NMtn*100 < 80 and value/self.NMtn*100 >1: +# self.MtnYDataValid = False +# self.RawYDataValid = False +# return + + ###### Widget callbacks + def pauseBtnAction(self): + self.pause = not self.pause + if self.pause: + self.pauseBtn.setStyleSheet("background-color: red") + else: + self.pvPrefixStr = self.pvPrefixOrigStr # Restore if dataset was opened + self.mtnPluginId = self.mtnPluginOrigId # Restore if dataset was opened + self.buildPvNames() + + self.pauseBtn.setStyleSheet("background-color: green") + # Retrigger plots with newest values + #self.comSignalSpectY.data_signal.emit(self.spectY) + #self.comSignalRawData.data_signal.emit(self.rawdataY) + return + + def enableBtnAction(self): + self.data['EnaCmd-RB'] = not self.data['EnaCmd-RB'] + self.pvs['EnaCmd-RB'].put(self.data['EnaCmd-RB']) + if self.data['EnaCmd-RB']: + self.enableBtn.setStyleSheet("background-color: green") + else: + self.enableBtn.setStyleSheet("background-color: red") + return + + def triggBtnAction(self): + self.pvTrigg.put(True) + return + + def zoomBtnAction(self): + if self.data['Time-Arr'] is None: + return + + if self.data['PosAct-Arr'] is None: + return + + self.plotAnalog(True) + self.plotBinary(True) + return + + def newModeIndexChanged(self,index): + if index==0 or index==1: + if not self.offline and self.pvs['Mde-RB'] is not None: + self.pvs['Mde-RB'].put(index+1) + return + + def openBtnAction(self): + #if not self.offline: + # self.pause = 1 # pause while open if online + # self.pauseBtn.setStyleSheet("background-color: red") + # QCoreApplication.processEvents() + # + #fname = QFileDialog.getOpenFileName(self, 'Open file', self.path, "Data files (*.npz)") + #if fname is None: + # return + #if np.size(fname) != 2: + # return + #if len(fname[0])<=0: + # return + #self.path = os.path.dirname(os.path.abspath(fname[0])) + # + #npzfile = np.load(fname[0]) +# + ## verify scope plugin + #if npzfile['plugin'] != "Mtn": + # print ("Invalid data type (wrong plugin type)") + # return + # + ## File valid + #self.data['Time-Arr'] = npzfile['rawdataX'] + #self.rawdataY = npzfile['rawdataY'] + #self.dataX = npzfile['spectX'] + #self.spectY = npzfile['spectY'] + #self.sampleRate = npzfile['sampleRate'] + #self.NMtn = npzfile['NMtn'] + #self.data['Mde-RB'] = npzfile['mode'] + #self.pvPrefixStr = str(npzfile['pvPrefixStr']) + #self.mtnPluginId = npzfile['mtnPluginId'] + #if 'unitRawY' in npzfile: + # self.unitAnalogY = str(npzfile['unitRawY']) + #if 'unitSpectY' in npzfile: + # self.unitSpectY = str(npzfile['unitSpectY']) + #if 'labelRawY' in npzfile: + # self.labelAnalogY = str(npzfile['labelRawY']) + #if 'labelSpectY' in npzfile: + # self.labelSpectY = str(npzfile['labelSpectY']) + #if 'title' in npzfile: + # self.title = str(npzfile['title']) +# + #self.buildPvNames() + # + ## trigg draw + #self.MtnYDataValid = True + #self.MtnXDataValid = True + #self.RawYDataValid = True + #self.RawXDataValid = True + #self.sampleRateValid = True + + + #self.comSignalMode.data_signal.emit(self.data['Mde-RB']) + #self.comSignalX.data_signal.emit(self.dataX) + #self.comSignalSpectY.data_signal.emit(self.spectY) + #self.comSignalRawData.data_signal.emit(self.rawdataY) + + #self.setStatusOfWidgets() +# + #self.startupDone=True + #self.zoomBtnAction() + return + + def saveBtnAction(self): + #fname = QFileDialog.getSaveFileName(self, 'Save file', self.path, "Data files (*.npz)") + #if fname is None: + # return + #if np.size(fname) != 2: + # return + #if len(fname[0])<=0: + # return + ## Save all relevant data + #np.savez(fname[0], + # plugin = "Mtn", + # rawdataX = self.data['Time-Arr'], + # rawdataY = self.rawdataY, + # spectX = self.dataX, + # spectY = self.spectY, + # sampleRate = self.sampleRate, + # NMtn = self.NMtn, + # mode = self.data['Mde-RB'], + # pvPrefixStr = self.pvPrefixStr, + # mtnPluginId = self.mtnPluginId, + # unitRawY = self.unitAnalogY, + # unitSpectY = self.unitSpectY, + # labelRawY = self.labelAnalogY, + # labelSpectY = self.labelSpectY, + # title = self.title + # ) +# + #self.path = os.path.dirname(os.path.abspath(fname[0])) + + return + + def plotAll(self): + if self.MtnYDataValid and self.MtnXDataValid: + self.plotAnalog() + self.plotBinary() + self.MtnYDataValid = False + self.RawYDataValid = False + + def plotAnalog(self, autozoom=False): + if self.data['Time-Arr'] is None: + return + + if self.data['PosAct-Arr'] is None: + return + + # create an axis + if self.axAnalog is None: + self.axAnalog = self.figure.add_subplot(211) + self.axAnalog.set_xlim(-10,0) + + minimum_x=0 + # plot data + for pv in pvAnalog: + if self.plottedLineAnalog[pv] is not None: + self.plottedLineAnalog[pv].remove() + if self.data[pv] is not None: + y = self.data[pv] + y_len=len(y) + x_len=len(self.x) + self.plottedLineAnalog[pv], = self.axAnalog.plot(self.x[x_len-y_len:],y,self.plotColor[pv]) + + minimum_x_temp=-y_len/self.sampleRate + if minimum_x_temp < minimum_x: + minimum_x = minimum_x_temp + + else: + print("Data null for pv: " + pv) + + self.axAnalog.grid(True) + self.axAnalog.set_xlabel('Time [s]') + self.axAnalog.set_ylabel(self.labelAnalogY + ' ' + self.unitAnalogY) + self.axAnalog.set_title(self.title) + + if autozoom: + ymin = np.min(self.data['PosAct-Arr']) + ymax = np.max(self.data['PosAct-Arr']) + # ensure different values + if ymin == ymax: + ymin=ymin-1 + ymax=ymax+1 + range = ymax - ymin + ymax += range * 0.1 + ymin -= range * 0.1 + #xmin = np.min(self.data['Time-Arr']) + xmin=minimum_x + #xmax = np.max(self.data['Time-Arr']) + xmax = 0 + if xmin == xmax: + xmin = xmin - 1 + xmax = xmax + 1 + range = xmax - xmin + xmax += range * 0.02 + xmin -= range * 0.02 + self.axAnalog.set_ylim(ymin,ymax) + self.axAnalog.set_xlim(xmin,xmax) + + # refresh canvas + self.canvas.draw() + self.allowSave = True + self.saveBtn.setEnabled(True) + self.axAnalog.autoscale(enable=False) + + def plotBinary(self, autozoom=False): + if self.data['Time-Arr'] is None: + return + + #if self.data['PosAct-Arr'] is None: + # return + + # create an axis + if self.axBinary is None: + self.axBinary = self.figure.add_subplot(212) + self.axBinary.set_xlim(-10,0) + + + # plot data + minimum_x = 0 + for pv in pvBinary: + if self.plottedLineBinary[pv] is not None: + self.plottedLineBinary[pv].remove() + if self.data[pv] is not None: + y = self.data[pv] + y_len=len(y) + x_len=len(self.x) + self.plottedLineBinary[pv], = self.axBinary.plot(self.x[x_len-y_len:],y,self.plotColor[pv]) + + minimum_x_temp=-y_len/self.sampleRate + if minimum_x_temp < minimum_x: + minimum_x = minimum_x_temp + + else: + print("Data null for pv: " + pv) + + + self.axBinary.grid(True) + self.axBinary.set_xlabel('Time [s]') + self.axBinary.set_ylabel(self.labelBinaryY + ' ' + self.unitBinaryY) + self.axBinary.set_title(self.title) + + if autozoom: + ymin = -0.1 + ymax = 1.1 + # ensure different values + if ymin == ymax: + ymin=ymin-1 + ymax=ymax+1 + range = ymax - ymin + ymax += range * 0.1 + ymin -= range * 0.1 + #xmin = np.min(self.data['Time-Arr']) + xmin=minimum_x + #xmax = np.max(self.data['Time-Arr']) + xmax = 0 + if xmin == xmax: + xmin = xmin - 1 + xmax = xmax + 1 + range = xmax - xmin + xmax += range * 0.02 + xmin -= range * 0.02 + self.axBinary.set_ylim(ymin,ymax) + self.axBinary.set_xlim(xmin,xmax) + + # refresh canvas + self.canvas.draw() + self.allowSave = True + self.saveBtn.setEnabled(True) + self.axBinary.autoscale(enable=False) + +def printOutHelp(): + print("ecmcMtnMainGui: Plots waveforms of Mtn data (updates on Y data callback). ") + print("python ecmcMtnMainGui.py ") + print(": Ioc prefix ('IOC_TEST:')") + print(" : Id of mtn plugin ('0')") + print("example : python ecmcMotionMainGui.py 'IOC_TEST:' '0'") + print("Will connect to Pvs: Plg-Mtn-*") + +if __name__ == "__main__": + import sys + prefix = None + mtnid = None + if len(sys.argv) == 1: + prefix = None + mtnid = None + elif len(sys.argv) == 3: + prefix = sys.argv[1] + mtnid = int(sys.argv[2]) + else: + printOutHelp() + sys.exit() + app = QtWidgets.QApplication(sys.argv) + window=ecmcMtnMainGui(prefix=prefix,mtnPluginId=mtnid) + window.show() + sys.exit(app.exec_()) diff --git a/tools/test.png b/tools/test.png new file mode 100644 index 0000000..cde96a0 Binary files /dev/null and b/tools/test.png differ