This commit is contained in:
2023-08-16 15:04:06 +02:00
parent ac23c6ac88
commit 1887952f23
7 changed files with 1978 additions and 7 deletions

537
tools/ecmcArrayStat.py Normal file
View File

@@ -0,0 +1,537 @@
#!/usr/bin/python3.6
import sys
import epics
import numpy as np
from PyQt5 import QtCore,QtWidgets, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtCore import QObject
from PyQt5.QtGui import *
import random
import ecmcTrend
#import ecmcGraphWrapper as graphWrap
#import matplotlib as mpl
#mpl.use('Qt5Agg')
#import matplotlib.pyplot as plt
#import matplotlib.lines
from datetime import datetime
import threading
PARSE_ERROR_ELEMENT_COUNT_OUT_OF_RANGE = 1000
ELEMENT_COUNT = 30
TIMESTAMP_INDEX = 30
TREND_DEFAULT_INDEX = 2
ECMC_COMMAND = {
'MOVE_VEL': 1,
'MOVE_REL' :2,
'MOVE_ABS' :3,
'MOVE_HOME' :10,
}
DATASOURCE = {
'AX_ID': 0,
'POS_SET' :1,
'POS_ACT' :2,
'POS_ERR' :3,
'POS_TARG' :4,
'POS_ERR_TARG' :5,
'POS_RAW' :6,
'CNTRL_OUT' :7,
'VEL_SET' :8,
'VEL_ACT' :9,
'VEL_FF_RAW' :10,
'VEL_RAW' :11,
'CYCLE_COUNTER' :12,
'ERROR' :13,
'COMMAND' :14,
'CMD_DATA' :15,
'SEQ_STATE' :16,
'ILOCK' :17,
'ILOCK_LAST_ACTIVE' :18,
'TRAJ_SOURCE' :19,
'ENC_SOURCE' :20,
'ENABLE' :21,
'ENABLED' :22,
'EXECUTE' :23,
'BUSY' :24,
'AT_TAGEY' :25,
'HOMED' :26,
'LOW_LIM' :27,
'HIGH_LIM' :28,
'HOME_SENSOR' :29
}
DESCRIPTION = [
'axId',
'posSet',
'posAct',
'posErr',
'posTarg',
'posErrTarg',
'posRaw',
'cntrlOut',
'velSet',
'velAct',
'velFFraw',
'velRaw',
'cycleCounter',
'error',
'command',
'cmdData',
'seqState',
'ilock',
'ilockLastActive',
'trajSource',
'encSource',
'enable',
'enabled',
'execute',
'busy',
'atTarget',
'homed',
'lowLim',
'highLim',
'homeSensor',
]
STYLES={
'ArrayStat': '''
QTableView{
background-color: white;
foreground-color: black;
font: bold;
width: 430px;
min-width: 430px;
max-width: 430px;
font-size:10pt;
height:750px;
min-height:750px;
max-height:750px;
}
'''
}
# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class comTable(QObject):
data_signal = pyqtSignal(str,float)
''' End Class '''
# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class comTrend(QObject):
data_signal = pyqtSignal(float)
''' End Class '''
class ecmcArrayStat(QtWidgets.QTableView):
def __init__(self,parent=None):
super(ecmcArrayStat, self).__init__(parent)
self.background=None
self.startToPlot=False;
self.axId=0
self.posSet=0
self.posAct=0
self.posErr=0
self.posTarg=0
self.posErrTarg=0
self.posRaw=0
self.cntrlOut=0
self.velSet=0
self.velAct=0
self.velFFraw=0
self.velRaw=0
self.cycleCounter=0
self.error=0
self.errorCode=0
self.strTime=0
self.command=0
self.cmdData=0
self.seqState=0
self.ilock=0
self.ilockLastActive=0
self.trajSourc=0
self.encSource=0
self.enable=0
self.enabled=0
self.execute=0
self.busy=0
self.atTarget=0
self.homed=0
self.lowLim=0
self.highLim=0
self.homeSensor=0
self.trendValue = 0
self.trendDataIndex = TREND_DEFAULT_INDEX # Pos-Act
self.dataList=[]
self.dataSourceConvFuncPoint = {
0 :self.defaultStrFunc,
1 :self.defaultStrFunc,
2 :self.defaultStrFunc,
3 :self.defaultStrFunc,
4 :self.defaultStrFunc,
5 :self.defaultStrFunc,
6 :self.defaultStrFunc,
7 :self.defaultStrFunc,
8 :self.defaultStrFunc,
9 :self.defaultStrFunc,
10 :self.defaultStrFunc,
11 :self.defaultStrFunc,
12 :self.defaultStrFunc,
13 :self.errorFunc,
14 :self.commandStrFunc,
15 :self.cmdDataStrFunc,
16 :self.defaultStrFunc,
17 :self.iLockFunc,
18 :self.iLockFunc,
19 :self.sourceFunc,
20 :self.sourceFunc,
21 :self.defaultStrFunc,
22 :self.defaultStrFunc,
23 :self.defaultStrFunc,
24 :self.busyFunc,
25 :self.highOKLowNotOKFunc,
26 :self.highOKLowNotOKFunc,
27 :self.highOKLowNotOKFunc,
28 :self.highOKLowNotOKFunc,
29 :self.defaultStrFunc,
}
self.axisDiagPvName=""
self.stdItemArrayName=[]
self.stdItemArrayData=[]
self.stdItemArraySelect=[]
self.create_GUI()
self.comTable = comTable()
self.comTable.data_signal.connect(self.updateGUI)
self.comTrend = comTrend()
self.comTrend.data_signal.connect(self.trend.addData_callbackFunc)
return
def create_GUI(self):
self.table = QtWidgets.QTableView(self) # SELECTING THE VIEW
self.model = QtGui.QStandardItemModel(self) # SELECTING THE MODEL - FRAMEWORK THAT HANDLES QUERIES AND EDITS
self.table.setModel(self.model) # SETTING THE MODEL
self.populate()
self.model.setHorizontalHeaderLabels(['Parameter', 'Value', ''])
self.btnPlot=QtWidgets.QPushButton('Plot',default=False, autoDefault=False)
self.trend=ecmcTrend.ecmcTrend()
# Disable put button
self.trend.enablePut(False)
self.trend.setTitle("ecmc plot")
self.show()
def populate(self):
for i in range(0,ELEMENT_COUNT):
row= []
cell=QtGui.QStandardItem(DESCRIPTION[i])
cell.setFlags(QtCore.Qt.ItemIsEditable)
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setForeground(QtGui.QBrush(QtCore.Qt.black))
self.stdItemArrayName.append(cell)
row.append(cell)
cell=QtGui.QStandardItem('value'+str(i))
cell.setFlags(QtCore.Qt.ItemIsEditable)
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setForeground(QtGui.QBrush(QtCore.Qt.black))
self.stdItemArrayData.append(cell)
row.append(cell)
cell=QtGui.QStandardItem(True)
cell.setCheckable(True)
cell.setCheckState(QtCore.Qt.Unchecked)
row.append(cell)
self.stdItemArraySelect.append(cell)
self.model.appendRow(row)
#Timestamp
row= []
cell=QtGui.QStandardItem("Timestamp")
cell.setFlags(QtCore.Qt.ItemIsEditable)
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setForeground(QtGui.QBrush(QtCore.Qt.black))
self.stdItemArrayName.append(cell)
row.append(cell)
cell=QtGui.QStandardItem('empty')
cell.setFlags(QtCore.Qt.ItemIsEditable)
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setForeground(QtGui.QBrush(QtCore.Qt.black))
self.stdItemArrayData.append(cell)
row.append(cell)
cell=QtGui.QStandardItem(True)
cell.setCheckable(True)
cell.setCheckState(QtCore.Qt.Unchecked)
row.append(cell)
self.stdItemArraySelect.append(cell)
self.model.appendRow(row)
self.formatTableView()
def formatTableView(self):
self.table.resizeRowsToContents()
#self.table.resizeColumnToContents(0)
self.table.setColumnWidth(0,100)
self.table.setColumnWidth(1,200)
self.table.setColumnWidth(2,20)
#self.table.setHorizontalHeaderLabels(['Parameter', 'Value','Select'])
def parseAxisStatArray(self,charData):
self.dataList=charData.split(',')
if len(self.dataList)!=ELEMENT_COUNT:
return PARSE_ERROR_ELEMENT_COUNT_OUT_OF_RANGE
#Update table view
for i in range(0,ELEMENT_COUNT):
if self.dataList[i] is not None:
if len(self.dataList[i])>0:
func=self.dataSourceConvFuncPoint[i]
func(self.dataList[i],self.stdItemArrayData[i])
self.covertStringToData(self.dataList)
self.update()
if self.startToPlot:
self.updateDataPlot()
def defaultStrFunc(self,strValue,cell):
cell.setData(strValue,role=QtCore.Qt.DisplayRole)
def sourceFunc(self,strValue,cell):
if int(strValue)==0:
strToSet="Internal"
else:
strToSet="PLC"
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
def commandStrFunc(self,strValue,cell):
switcher = {
1: "Move Vel",
2: "Move Rel",
3: "Move Abs",
10: "Move Home",
}
strToSet=switcher.get(int(strValue),strValue)
if strToSet==strValue:
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
else:
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
def highOKLowNotOKFunc(self,strValue,cell):
if int(strValue)==1:
strToSet="OK"
cell.setBackground(QtGui.QBrush(QtCore.Qt.green))
else:
strToSet="Not OK"
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
def busyFunc(self,strValue,cell):
if int(strValue)==0:
strToSet="Ready"
cell.setBackground(QtGui.QBrush(QtCore.Qt.green))
else:
strToSet="Busy"
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
def errorFunc(self,strValue,cell):
if int(strValue,16)==0:
cell.setBackground(QtGui.QBrush(QtCore.Qt.green))
else:
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
cell.setData(strValue,role=QtCore.Qt.DisplayRole)
def lowOKHighNotOKShowValFunc(self,strValue,cell):
if int(strValue)==0:
cell.setBackground(QtGui.QBrush(QtCore.Qt.green))
else:
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
cell.setData(strValue,role=QtCore.Qt.DisplayRole)
def iLockFunc(self,strValue,cell):
if int(strValue)==0:
cell.setBackground(QtGui.QBrush(QtCore.Qt.green))
cell.setData("OK",role=QtCore.Qt.DisplayRole)
return
switcher = {
1: "Soft Bwd",
2: "Soft Fwd",
3: "Hard Bwd",
4: "Hard Fwd",
5: "No Execute",
6: "Pos. Lag",
7: "Both Lim.",
8: "External",
9: "Transform",
10: "Max Vel.",
11: "Cntrl High Lim.",
12: "Cntrl Inc at Lim.",
13: "Axis Error",
14: "Unexp. Lim.",
15: "Vel. diff",
16: "Hardware",
17: "PLC",
18: "PLC Bwd",
19: "PLC Fwd",
}
strToSet=switcher.get(int(strValue),strValue)
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
def cmdDataStrFunc(self,strValue,cell):
if self.command!=ECMC_COMMAND['MOVE_HOME']:
self.defaultStrFunc(strValue,cell)
return
switcher = {
0: "Not defined",
1: "Low Lim",
2: "High Lim",
3: "Low Lim, Home Sens",
4: "High Lim, Home Sens",
5: "Low Lim, Center Home Sens",
6: "High Lim, Center Home Sens",
7: "Bwd, Home sens",
8: "Fwd, Home sens",
9: "Bwd, Center Home Sens",
10: "Fwd, Center Home Sens",
11: "Low Lim, Enc index",
12: "High Lim, Enc index",
15: "Home Direct",
21: "Low Lim Part Abs",
22: "High Lim Part Abs",
}
strToSet=switcher.get(int(strValue),strValue)
if strToSet==strValue:
cell.setBackground(QtGui.QBrush(QtCore.Qt.red))
else:
cell.setBackground(QtGui.QBrush(QtCore.Qt.white))
cell.setData(strToSet,role=QtCore.Qt.DisplayRole)
def covertStringToData(self,dataList):
self.axId=int(dataList[0])
self.posSet=float(dataList[1])
self.posAct=float(dataList[2])
self.posErr=float(dataList[3])
self.posTarg=float(dataList[4])
self.posErrTarg=float(dataList[5])
self.posRaw=float(dataList[6])
self.cntrlOut=float(dataList[7])
self.velSet=float(dataList[8])
self.velAct=float(dataList[9])
self.velFFraw=float(dataList[10])
self.velRaw=float(dataList[11])
self.cycleCounter=int(dataList[12])
self.error=int(dataList[13],16)
self.command=int(dataList[14])
self.cmdData=int(dataList[15])
self.seqState=int(dataList[16])
self.ilock=int(dataList[17])
self.ilockLastActive=int(dataList[18])
self.trajSource=int(dataList[19])
self.encSource=int(dataList[20])
self.enable=int(dataList[21])
self.enabled=int(dataList[22])
self.execute=int(dataList[23])
self.busy=int(dataList[24])
self.atTarget=int(dataList[25])
self.homed=int(dataList[26])
self.lowLim=int(dataList[27])
self.highLim=int(dataList[28])
self.homeSensor=int(dataList[29])
self.trendValue = float(dataList[self.trendDataIndex])
def onChangeAxisDiagPv(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
self.comTable.data_signal.emit(char_value,timestamp)
# All update of GUI here..
def updateGUI(self, char_value, timestamp):
self.errorCode=self.parseAxisStatArray(char_value)
if self.errorCode:
print("Parse failed with error code: " + str(self.errorCode))
self.strTime=datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S%f')
self.stdItemArrayData[TIMESTAMP_INDEX].setData(self.strTime,role=QtCore.Qt.DisplayRole)
def connect(self, pvname):
if pvname is None:
raise RuntimeError("pvname must not be 'None'")
if len(pvname)==0:
raise RuntimeError("pvname must not be ''")
self.axisDiagPvName = pvname
self.axisDiagPv = epics.PV(self.axisDiagPvName)
self.axisDiagPv.add_callback(self.onChangeAxisDiagPv)
self.trend.setTitle(self.axisDiagPvName)
def disconnect(self):
if self.axisDiagPv is not None:
self.axisDiagPv.clear_callbacks()
def printInfo(self):
print("axId : " + str(self.axId))
print("posSet : " + str(self.posSet))
print("posAct : " + str(self.posAct))
print("posErr : " + str(self.posErr))
print("posTarg : " + str(self.posTarg))
print("posErrTarg : " + str(self.posErrTarg))
print("posRaw : " + str(self.posRaw))
print("cntrlOut : " + str(self.cntrlOut))
print("velSet : " + str(self.velSet))
print("velAct : " + str(self.velAct))
print("velFFraw : " + str(self.velFFraw))
print("velRaw : " + str(self.velRaw))
print("cycleCounter : " + str(self.cycleCounter))
print("error : " + str(self.error))
print("command : " + str(self.command))
print("cmdData : " + str(self.cmdData))
print("seqState : " + str(self.seqState))
print("ilock : " + str(self.ilock))
print("ilockLastActive : " + str(self.ilockLastActive))
print("trajSource : " + str(self.trajSource))
print("encSource : " + str(self.encSource))
print("enable : " + str(self.enable))
print("enabled : " + str(self.enabled))
print("execute : " + str(self.execute))
print("busy : " + str(self.busy))
print("atTarget : " + str(self.atTarget))
print("homed : " + str(self.homed))
print("lowLim : " + str(self.lowLim))
print("highLim : " + str(self.highLim))
print("homeSensor : " + str(self.homeSensor))
return
def startPlot(self):
self.trendDataIndex, label = self.checkPlotVar()
self.trend.setYLabel(label)
self.trend.show()
self.startToPlot=True;
def updateDataPlot(self):
if self.startToPlot:
self.comTrend.data_signal.emit(self.trendValue)
# return first index of selected data (only support one var currently)
def checkPlotVar(self):
for i in range(0,ELEMENT_COUNT):
if self.stdItemArraySelect[i].checkState():
return i, self.stdItemArrayName[i].text()
return TREND_DEFAULT_INDEX, self.stdItemArrayName[TREND_DEFAULT_INDEX].text()

187
tools/ecmcGuiMain.py Normal file
View File

@@ -0,0 +1,187 @@
#!/usr/bin/python3.6
# coding: utf-8
from PyQt5 import QtWidgets,uic
import numpy as np
import epics
from ecmcArrayStat import *
from ecmcOneMotorGUI import *
from ecmcMainWndDesigner import Ui_MainWindow
from ecmcFFTMainGui import *
from ecmcScopeMainGui import *
import ecmcTrendPv
import time
# Needed packages:
# 1. sudo yum -y install https://rhel7.iuscommunity.org/ius-release.rpm
# 2. sudo pip3.6 install pyqt5
# 3. sudo yum install qt5-qtbase-devel
# 4. sudo python3.6 -m pip install numpy scipy matplotlib
# 5. sudo pip3 install pyepics
# 6. sudo yum install python3-matplotlib
# Regenerate py from ui file:
# pyuic5 -x ecmcMainWndDesigner.ui -o ecmcMainWndDesigner.py
class ecmcMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(ecmcMainWindow,self).__init__()
self.prefix=""
self.pvName=""
self.pv=None
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pbStartGUI.clicked.connect(self.showGUI)
self.ui.pbStartGUI.setToolTip("Start GUI for ioc-prefix + pv-name")
self.ui.lineIOCPrefix.textChanged.connect(self.newIOCPrefix)
self.ui.lineIOCPrefix.setToolTip("Enter ioc-prefix to to use.")
self.ui.linepvName.textChanged.connect(self.newIOCpvName)
self.ui.linepvName.setToolTip("Enter pv-name to plot/trend (or control)")
self.ui.comboPrefix.currentIndexChanged.connect(self.newPrefixComboIndex)
self.ui.comboPrefix.addItem("IOC_TEST:")
self.ui.comboPrefix.addItem("IOC:")
self.ui.comboPrefix.addItem("IOC2:")
self.ui.comboPrefix.addItem("IOC_SLIT:")
self.ui.comboPrefix.addItem("TEST")
self.ui.comboPrefix.setToolTip("Predefined ioc-prefix. Choose one to use..")
self.ui.comboPvName.currentIndexChanged.connect(self.newPvComboIndex)
self.ui.comboPvName.addItem("Axis1")
self.ui.comboPvName.addItem("Axis2")
self.ui.comboPvName.addItem("MCU-ThdLatMax")
self.ui.comboPvName.addItem("MCU-ThdLatMin")
self.ui.comboPvName.addItem("MCU-ThdPrdMax")
self.ui.comboPvName.addItem("MCU-ThdPrdMin")
self.ui.comboPvName.addItem("MCU-ThdSndMax")
self.ui.comboPvName.addItem("MCU-ThdSndMin")
self.ui.comboPvName.addItem("m0-DomFailCntrTot")
self.ui.comboPvName.addItem("MCU-ErrId")
self.ui.comboPvName.addItem("m0s001-BI01")
self.ui.comboPvName.addItem("m0s001-BI02")
self.ui.comboPvName.addItem("m0s003-Enc01-PosAct")
self.ui.comboPvName.addItem("FFT-0")
self.ui.comboPvName.addItem("Scope-0")
self.ui.comboPvName.setToolTip("Predefined pv-names. Choose one to use..")
if len(sys.argv)>1:
self.prefix=sys.argv[1]
self.ui.lineIOCPrefix.setText(self.prefix)
if len(sys.argv)>2:
self.pvName=sys.argv[2]
self.ui.linepvName.setText(self.pvName)
if (len(sys.argv)>2):
for i in range(2,len(sys.argv)):
self.ui.linepvName.setText(str(sys.argv[i]))
self.showGUI()
def showGUI(self):
#Check and start FFT gui
if self.showGuiFFT(self.prefix, self.pvName):
return
#Check and start Scope gui
if self.showGuiScope(self.prefix, self.pvName):
return
# See if scalar or motor
self.ui.pbStartGUI.setText("Connecting to: " + self.prefix + self.pvName + "...")
self.ui.pbStartGUI.setEnabled(False)
self.ui.pbStartGUI.update()
QtCore.QCoreApplication.processEvents()
self.prefix=self.ui.lineIOCPrefix.text()
self.pvName=self.ui.linepvName.text()
entirePvName = self.prefix+self.pvName
pos = entirePvName.rfind('.')
# ensure record/pv exist
pvtest = epics.PV(entirePvName)
connected = pvtest.wait_for_connection(timeout=2)
self.ui.pbStartGUI.setEnabled(True)
self.ui.pbStartGUI.setText("Start GUI for: " + self.prefix + self.pvName)
self.ui.pbStartGUI.update()
if not(connected):
print("Timeout. Could not connect to: " + entirePvName + ". Probably not a valid PV name.")
return
del(pvtest)
# Check if motor
if pos < 0:
pv = epics.PV(entirePvName + '.RTYP')
if pv.get() == 'motor':
self.showMotorGUI(self.prefix, self.pvName)
return
# Normal PV
self.showGuiPv(self.prefix+self.pvName)
def showMotorGUI(self,prefix,pvName):
self.dialog = MotorPanel(self,prefix,pvName)
self.dialog.resize(500, 900)
self.dialog.show()
def showGuiPv(self, pvName):
dialog = ecmcTrendPv.ecmcTrendPv(pvName)
dialog.show()
def showGuiFFT(self, prefix, pvName):
# Check if FFT gui
if pvName.find('FFT-') == 0 and len(prefix) > 0:
pvNameTemp = pvName.split('-')
if np.size(pvNameTemp)==2:
if pvNameTemp[1].isdigit():
self.dialog = ecmcFFTMainGui(prefix,int(pvNameTemp[1]))
self.dialog.show()
return 1
return 0
def showGuiScope(self, prefix, pvName):
# Check if FFT gui
if pvName.find('Scope-') == 0 and len(prefix) > 0:
pvNameTemp = pvName.split('-')
if np.size(pvNameTemp)==2:
if pvNameTemp[1].isdigit():
self.dialog = ecmcScopeMainGui(prefix,int(pvNameTemp[1]))
self.dialog.show()
return 1
return 0
def newIOCPrefix(self,iocPrefix):
self.prefix=iocPrefix
self.ui.pbStartGUI.setText("Start GUI for: " + self.prefix + self.pvName)
def newIOCpvName(self,pvName):
self.pvName=pvName
self.ui.pbStartGUI.setText("Start GUI for: " + self.prefix + self.pvName)
def newPrefixComboIndex(self,index):
self.prefix=self.ui.comboPrefix.itemText(index)
self.ui.lineIOCPrefix.setText(self.prefix)
self.ui.pbStartGUI.setText("Start GUI for: " + self.prefix + self.pvName)
def newPvComboIndex(self,index):
self.pvName=self.ui.comboPvName.itemText(index)
self.ui.linepvName.setText(self.pvName)
self.ui.pbStartGUI.setText("Start GUI for: " + self.prefix + self.pvName)
def quit(self):
self.close()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
window=ecmcMainWindow();
window.show()
sys.exit(app.exec_())

View File

@@ -21,6 +21,7 @@ from PyQt5.QtCore import *
from PyQt5.QtGui import *
import numpy as np
import time
from ecmcOneMotorGUI import *
import pyqtgraph as pg
import threading
@@ -65,8 +66,12 @@ pvBinary = ['Ena-Arr',
'AtTrg-Arr']
# MCU info PVs
pvAxisCompleteNamePart1 ='MCU-Cfg-AX'
pvAxisCompleteNamePart2 ='-PfxNam'
pvAxisPrefixNamePart1 ='MCU-Cfg-AX'
pvAxisPrefixNamePart2 ='-Pfx'
pvAxisNamePart1 ='MCU-Cfg-AX'
pvAxisNamePart2 ='-Nam'
pvFistAxisIndexName = 'MCU-Cfg-AX-FrstObjId'
pvNextAxisIndexNamePart1 = 'MCU-Cfg-AX'
pvNextAxisIndexNamePart2 = '-NxtObjId'
@@ -297,6 +302,7 @@ class ecmcMtnMainGui(QtWidgets.QDialog):
layoutMotionGrid = QGridLayout()
frameMotion.setLayout(layoutMotionGrid)
btn = QPushButton(text = 'Test')
btn.clicked.connect(self.openMotorRecordPanel)
btn.setFixedSize(100, 50)
layoutMotionGrid.addWidget(btn,0,0)
@@ -575,11 +581,19 @@ class ecmcMtnMainGui(QtWidgets.QDialog):
if id >= 0:
self.cmbBxSelectAxis.setCurrentIndex(id)
name = self.pvPrefixStr + pvAxisCompleteNamePart1 + str(int(value)) + pvAxisCompleteNamePart2
namePV = epics.PV(name)
newName = namePV.get()
if newName is not None:
print('PV name of axis:' + newName)
axisPrefixPvName = self.pvPrefixStr + pvAxisPrefixNamePart1 + str(int(value)) + pvAxisPrefixNamePart2
prefixPV = epics.PV(axisPrefixPvName)
axisPrefix = prefixPV.get()
if axisPrefix is not None:
print('prefix of axis:' + axisPrefix)
self.axisPrefix = axisPrefix
axisNamePvName = self.pvPrefixStr + pvAxisNamePart1 + str(int(value)) + pvAxisNamePart2
namePV = epics.PV(axisNamePvName)
axisName = namePV.get()
if axisName is not None:
print('name of axis:' + axisName)
self.axisName = axisName
def sig_cb_SmpHz_RB(self,value):
self.data['SmpHz-RB'] = value
@@ -633,6 +647,11 @@ class ecmcMtnMainGui(QtWidgets.QDialog):
if self.cmbBxSelectAxis.currentData() is not None:
self.pvs['AxCmd-RB'].put(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.show()
###### Widget callbacks
def pauseBtnAction(self):
self.pause = not self.pause

782
tools/ecmcOneMotorGUI.py Normal file
View File

@@ -0,0 +1,782 @@
#!/usr/bin/env python3.6
import epics
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
from ecmcArrayStat import *
#Define pvs
# Axis
ECMC_PV_AXIS_DIAG_ARRAY_SUFFIX = '-Arr-Stat'
ECMC_PV_AXIS_ERROR_RESET_SUFFIX = '-ErrRst'
# Controller
ECMC_PV_CNTROLLER_ERROR_ID_SUFFIX = 'MCU-ErrId'
ECMC_PV_CNTROLLER_ERROR_MSG_SUFFIX = 'MCU-ErrMsg'
ECMC_PV_CNTROLLER_ERROR_RESET_SUFFIX = 'MCU-ErrRst'
ECMC_PV_CNTROLLER_ERROR_CND_SUFFIX = 'MCU-Cmd'
BLANK = ' '*4
BACKGROUND_DEFAULT = '#efefef'
#BACKGROUND_DONE_MOVING = 'beige'
BACKGROUND_DONE_MOVING = BACKGROUND_DEFAULT
BACKGROUND_MOVING = 'lightgreen'
BACKGROUND_LVIO_ON = 'yellow'
BACKGROUND_LIMIT_ON = 'red'
TOOLTIPS = {
'DESC': 'DESC: short description',
'NAME': 'NAME: EPICS PV name',
'RBV': 'RBV: motor readback value',
'VAL': 'VAL: motor target value',
'EGU': 'EGU: engineering units',
'STOP': 'STOP: command this motor to stop moving',
'TWV': 'TWV: tweak value',
'TWF': 'TWF: increment motor by tweak value',
'TWR': 'TWR: decrement motor by tweak value',
'*10': 'multiply tweak value by 10',
'/10': 'divide tweak value by 10',
'CNEN': 'enable/disable drive',
'ArrayStat': 'axis Status',
'ErrRst': 'reset axis error',
'JOGR': 'JOGR: jog backward',
'JOGF': 'JOGF: jog forward',
'HOMR': 'HOMR: Home reverse',
'HOMF': 'HOMF: Home forward',
'MSTA': 'MSTA: Status',
'JVEL': 'JVEL: Jog velocity',
'VELO': 'VELO: Velocity (positioning)'
}
STYLES = { ### http://doc.qt.digia.com/qt/stylesheet-reference.html
'self': '''
MotorPanel {
border-style: solid;
border-color: black;
border-width: 1px;
}
''',
'DESC': '''
QLabel {
qproperty-alignment: AlignRight;
font: bold;
}
''',
'NAME': '''
QLabel {
qproperty-alignment: AlignRight;
font: bold;
}
''',
'RBV': '''
QLabel {
qproperty-alignment: AlignRight;
}
''',
'EGU': '''
QLabel {
qproperty-alignment: AlignRight;
}
''',
'VAL': '''
QLineEdit {
background-color: white;
text-align: right;
}
''',
'STOP': '''
QPushButton {
background-color: red;
color: black;
text-align: center;
height: 50px;
min-height: 50px;
max-height: 50px;
}
QPushButton:hover {
background-color: red;
color: yellow;
font: bold;
text-align: center;
}
''',
'CNEN': '''
QPushButton {
background-color: red;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
QPushButton:hover {
background-color: red;
color: yellow;
font: bold;
text-align: center;
}
''',
'BUTTON_ON': '''
QPushButton {
background-color: green;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
QPushButton:hover {
background-color: green;
color: yellow;
font: bold;
text-align: center;
}
''',
'TWV': '''
QLineEdit {
background-color: white;
text-align: right;
width: 80px;
min-width: 80px;
max-width: 80px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'TWF': '''
QPushButton {
width: 40px;
min-width: 40px;
max-width: 40px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'TWR': '''
QPushButton {
width: 40px;
min-width: 40px;
max-width: 40px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'*10': '''
QPushButton {
width: 40px;
min-width: 40px;
max-width: 40px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'/10': '''
QPushButton {
width: 40px;
min-width: 40px;
max-width: 40px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'ArrayStat': '''
QTableView {
background-color: white;
font: bold;
width: 350px;
min-width: 350px;
max-width: 350px;
font-size:10pt;
height:770px;
min-height:770px;
max-height:770px;
}
''',
'ErrRst': '''
QPushButton {
background-color: red;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
QPushButton:hover {
background-color: red;
color: yellow;
font: bold;
text-align: center;
}
''',
'JOGR': '''
QPushButton {
background-color: grey;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'JOGF': '''
QPushButton {
background-color: grey;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'HOMR': '''
QPushButton {
background-color: grey;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'HOMF': '''
QPushButton {
background-color: grey;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'MSTA': '''
QLabel {
text-align: right;
}
''',
'PLOT': '''
QPushButton {
background-color: grey;
color: black;
text-align: center;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'VELO': '''
QLineEdit {
background-color: white;
text-align: right;
width: 80px;
min-width: 80px;
max-width: 80px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
'JVEL': '''
QLineEdit {
background-color: white;
text-align: right;
width: 80px;
min-width: 80px;
max-width: 80px;
height: 40px;
min-height: 40px;
max-height: 40px;
}
''',
}
class MotorPanel(QtWidgets.QDialog):
def __init__(self, parent=None,iocPrefix=None,axisName=None):
super(MotorPanel, self).__init__(parent)
QtWidgets.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
self.motorPv = None
self.motorPvName = ""
self.axisDiagPvName=""
self.axisErrorResetPv = None
self.axisErrorResetPvName = ""
self.cntrlErrorIdPv = None
self.cntrlErrorIdPvName = ""
self.cntrlErrorResetPv = None
self.cntrlErrorResetPvName = ""
self.cntrlErrorMsgPv = None
self.cntrlErrorMsgPvName = ""
self.cntrlErrorMsg=""
self.cntrlCmdPv = None
self.cntrlCmdPvName = ""
self.create_GUI()
self.apply_styles()
self.create_actions()
if isinstance(axisName, str) and isinstance(iocPrefix, str):
self.connect(iocPrefix,axisName)
def create_GUI(self):
'''define controls AND set the layout'''
self.controls = {}
for field in ['DESC', 'NAME', 'EGU', 'RBV']:
self.controls[field] = QtWidgets.QLabel(BLANK)
self.controls['VAL'] = QtWidgets.QLineEdit()
self.controls['STOP'] = QtWidgets.QPushButton('STOP',default=False, autoDefault=False)
self.controls['TWF'] = QtWidgets.QPushButton('>>',default=False, autoDefault=False)
self.controls['TWV'] = QtWidgets.QLineEdit()
self.controls['TWR'] = QtWidgets.QPushButton('<<',default=False, autoDefault=False)
self.controls['*10'] = QtWidgets.QPushButton('*10',default=False, autoDefault=False)
self.controls['/10'] = QtWidgets.QPushButton('/10',default=False, autoDefault=False)
self.controls['CNEN'] = QtWidgets.QPushButton('CNEN',default=False, autoDefault=False)
self.controls['ArrayStat']=ecmcArrayStat(self)
self.controls['JVEL'] = QtWidgets.QLineEdit()
self.controls['VELO'] = QtWidgets.QLineEdit()
self.controls['ErrRst'] = QtWidgets.QPushButton('Reset Error',default=False, autoDefault=False)
self.controls['JOGR'] = QtWidgets.QPushButton('JOGR',default=False, autoDefault=False)
self.controls['JOGF'] = QtWidgets.QPushButton('JOGF',default=False, autoDefault=False)
self.controls['HOMR'] = QtWidgets.QPushButton('HOMR',default=False, autoDefault=False)
self.controls['HOMF'] = QtWidgets.QPushButton('HOMF',default=False, autoDefault=False)
self.controls['PLOT'] = QtWidgets.QPushButton('Plot',default=False, autoDefault=False)
self.controls['MSTA'] = QtWidgets.QLabel()
self.controls['RBV'].setAutoFillBackground(True)
self.setLabelBackground(self.controls['RBV'], BACKGROUND_DONE_MOVING)
main_frame= QtWidgets.QFrame(self)
main_layout = QtWidgets.QHBoxLayout()
left_frame = QtWidgets.QFrame(self)
left_layout = QtWidgets.QVBoxLayout()
for field in ['NAME','DESC', 'EGU', 'RBV', 'VAL','VELO','MSTA']:
tmp_frame = QtWidgets.QFrame(self)
tmp_layout = QtWidgets.QHBoxLayout()
tmp_label=QtWidgets.QLabel()
tmp_label.setText(field+ ':')
tmp_layout.addWidget(tmp_label)
tmp_layout.addWidget(self.controls[field])
tmp_frame.setLayout(tmp_layout)
left_layout.addWidget(tmp_frame)
#Tweak
tweak_label= QtWidgets.QLabel()
tweak_label.setText('TWEAK:')
left_layout.addWidget(tweak_label)
tweak_frame = QtWidgets.QFrame(self)
tweak_layout = QtWidgets.QHBoxLayout()
for field in ['TWR', '/10', 'TWV', '*10', 'TWF']:
tweak_layout.addWidget(self.controls[field])
tweak_frame.setLayout(tweak_layout)
left_layout.addWidget(tweak_frame)
#Jog
jog_label= QtWidgets.QLabel()
jog_label.setText('JOG:')
left_layout.addWidget(jog_label)
jvel_frame = QtWidgets.QFrame(self)
jvel_layout = QtWidgets.QHBoxLayout()
jvel_label=QtWidgets.QLabel()
jvel_label.setText('JVEL' + ':')
jvel_layout.addWidget(jvel_label)
jvel_layout.addWidget(self.controls['JVEL'])
jvel_frame.setLayout(jvel_layout)
left_layout.addWidget(jvel_frame)
jog_frame = QtWidgets.QFrame(self)
jog_layout = QtWidgets.QHBoxLayout()
jog_layout.addWidget(self.controls['JOGR'])
jog_layout.addWidget(self.controls['JOGF'])
jog_frame.setLayout(jog_layout)
left_layout.addWidget(jog_frame)
#Home
home_label= QtWidgets.QLabel()
home_label.setText('HOME:')
left_layout.addWidget(home_label)
home_frame = QtWidgets.QFrame(self)
home_layout = QtWidgets.QHBoxLayout()
home_layout.addWidget(self.controls['HOMR'])
home_layout.addWidget(self.controls['HOMF'])
home_frame.setLayout(home_layout)
left_layout.addWidget(home_frame)
control_label= QtWidgets.QLabel()
control_label.setText('CONTROL:')
left_layout.addWidget(control_label)
left_layout.addWidget(self.controls['STOP'])
left_layout.addWidget(self.controls['CNEN'])
left_layout.addWidget(self.controls['ErrRst'])
left_frame.setLayout(left_layout)
main_layout.addWidget(left_frame)
right_frame = QtWidgets.QFrame(self)
right_layout = QtWidgets.QVBoxLayout()
right_layout.addWidget(self.controls['ArrayStat'])
right_layout.addWidget(self.controls['PLOT'])
right_frame.setLayout(right_layout);
main_layout.addWidget(right_frame)
main_frame.setLayout(main_layout)
self.setLayout(main_layout)
self.setWindowTitle("ecmc: axis control")
def apply_styles(self):
'''apply styles and tips'''
for field in ['DESC', 'NAME', 'EGU', 'RBV', 'VAL',
'STOP','CNEN','TWV', 'TWF', 'TWR', '*10',
'/10','ArrayStat','ErrRst','JOGR','JOGF',
'HOMR','HOMF','MSTA','JVEL','VELO']:
if field in STYLES:
self.controls[field].setStyleSheet(STYLES[field])
if field in TOOLTIPS:
self.controls[field].setToolTip(TOOLTIPS[field])
self.setStyleSheet(STYLES['self'])
def create_actions(self):
'''define actions'''
self.controls['VAL'].returnPressed.connect(self.onReturnVAL)
self.controls['VELO'].returnPressed.connect(self.onReturnVELO)
self.controls['TWV'].returnPressed.connect(self.onReturnTWV)
self.controls['TWR'].clicked.connect(self.onPushTWR)
self.controls['TWF'].clicked.connect(self.onPushTWF)
self.controls['*10'].clicked.connect(self.onPush10x)
self.controls['/10'].clicked.connect(self.onPush_1x)
self.controls['STOP'].clicked.connect(self.onPushSTOP)
self.controls['CNEN'].clicked.connect(self.onPushCNEN)
self.controls['ErrRst'].clicked.connect(self.onPushErrRst)
self.controls['JVEL'].returnPressed.connect(self.onReturnJVEL)
self.controls['JOGR'].clicked.connect(self.onPushJOGR)
self.controls['JOGF'].clicked.connect(self.onPushJOGF)
self.controls['HOMR'].clicked.connect(self.onPushHOMR)
self.controls['HOMF'].clicked.connect(self.onPushHOMF)
self.controls['PLOT'].clicked.connect(self.onPushPLOT)
def setPvNames(self,iocPrefix=None,axisName=None):
self.motorPvName = (iocPrefix + axisName).split('.')[0] # keep everything to left of first dot
self.axisDiagPvName = self.motorPvName + ECMC_PV_AXIS_DIAG_ARRAY_SUFFIX
self.axisErrorResetPvName = self.motorPvName + ECMC_PV_AXIS_ERROR_RESET_SUFFIX
self.cntrlErrorIdPvName = iocPrefix + ECMC_PV_CNTROLLER_ERROR_ID_SUFFIX
self.cntrlErrorResetPvName = iocPrefix + ECMC_PV_CNTROLLER_ERROR_RESET_SUFFIX
self.cntrlErrorMsgPvName = iocPrefix + ECMC_PV_CNTROLLER_ERROR_MSG_SUFFIX
self.cntrlCmdPvName = iocPrefix + ECMC_PV_CNTROLLER_ERROR_CND_SUFFIX
def connect(self, iocPrefix=None,axisName=None):
'''connect this panel with an EPICS motor PV'''
if iocPrefix is None:
raise RuntimeError("iocPrefix must not be 'None'")
if axisName is None:
raise RuntimeError("axisName must not be 'None'")
self.setPvNames(iocPrefix,axisName)
if len(iocPrefix) == 0 or len(axisName) == 0:
raise RuntimeError("iocPrefix or axisName must not be ''")
if self.motorPv is not None:
self.disconnect()
self.controls['NAME'].setText(self.motorPvName)
self.motorPv = epics.Motor(self.motorPvName) # verifies that self.motor_pv has RTYP='motor'
callback_dict = {
#field: callback function
'DESC': self.onChangeDESC,
'EGU': self.onChangeEGU,
'RBV': self.onChangeRBV,
'VAL': self.onChangeVAL,
'VELO': self.onChangeVELO,
'TWV': self.onChangeTWV,
'DMOV': self.onChangeDMOV,
'HLS': self.onChangeHLS,
'LLS': self.onChangeLLS,
'CNEN': self.onChangeCNEN,
'JOGR': self.onChangeJOGR,
'JOGF': self.onChangeJOGF,
'JVEL': self.onChangeJVEL,
'HOMR': self.onChangeHOMR,
'HOMF': self.onChangeHOMF,
'MSTA': self.onChangeMSTA,
}
for field, func in callback_dict.items():
self.motorPv.set_callback(attr=field, callback=func)
self.controls['DESC'].setText(self.motorPv.description)
self.controls['EGU'].setText(self.motorPv.units)
# display initial values
self.onChangeRBV(value=self.motorPv.get('RBV'))
self.onChangeVAL(value=self.motorPv.get('VAL'))
self.onChangeTWV(value=self.motorPv.get('TWV'))
self.onChangeDMOV(value=self.motorPv.get('DMOV'))
self.onChangeCNEN(value=self.motorPv.get('CNEN'))
self.onChangeJOGR(value=self.motorPv.get('JOGR'))
self.onChangeJOGF(value=self.motorPv.get('JOGF'))
self.onChangeHOMR(value=self.motorPv.get('HOMR'))
self.onChangeHOMF(value=self.motorPv.get('HOMF'))
self.onChangeMSTA(value=self.motorPv.get('MSTA'))
self.onChangeVELO(value=self.motorPv.get('VELO'))
self.onChangeJVEL(value=self.motorPv.get('JVEL'))
# additional records
self.axisErrorResetPv = epics.PV(self.axisErrorResetPvName)
self.cntrlErrorIdPv = epics.PV(self.cntrlErrorIdPvName)
self.cntrlErrorIdPv.add_callback(self.onChangeCntrlErrorIdPv)
self.cntrlErrorResetPv = epics.PV(self.cntrlErrorResetPvName)
self.cntrlErrorMsgPv = epics.PV(self.cntrlErrorMsgPvName)
self.cntrlCmdPv = epics.PV(self.cntrlCmdPvName)
self.cntrlCmdPv.add_callback(self.onChangeCntrlCmdPv)
if self.controls['ArrayStat'] is not None:
self.controls['ArrayStat'].connect(self.axisDiagPvName)
def disconnect(self):
'''disconnect this panel from EPICS'''
if self.motorPv is not None:
for field in ['VAL', 'RBV', 'DESC', 'EGU', 'TWV', 'DMOV', 'HLS', 'LLS']:
self.motorPv.clear_callback(attr=field)
self.motorPv = None
for field in ['DESC', 'NAME', 'EGU', 'RBV', 'VAL', 'TWV']:
self.controls[field].setText(BLANK)
if self.cntrlErrorIdPv is not None:
self.cntrlErrorIdPv.clear_callbacks()
if self.cntrlCmdPv is not None:
self.cntrlCmdPv.clear_callbacks()
if self.controls['ArrayStat'] is not None:
self.controls['ArrayStat'].disconnect()
def closeEvent(self, event):
'''be sure to disconnect from EPICS when closing'''
self.disconnect()
def onPushSTOP(self):
'''stop button was pressed'''
if self.motorPv is not None:
self.motorPv.stop()
def onPushCNEN(self):
'''cnen button was pressed'''
if self.motorPv is not None:
self.motorPv.put('CNEN',not self.motorPv.get('CNEN'))
def onPushErrRst(self):
'''ErrRst button was pressed'''
if self.axisErrorResetPv is not None:
self.axisErrorResetPv.put(1)
def onPushJOGR(self):
'''jogr button was pressed'''
if self.motorPv is not None:
self.motorPv.put('JOGR',not self.motorPv.get('JOGR'))
def onPushJOGF(self):
'''jogf button was pressed'''
if self.motorPv is not None:
self.motorPv.put('JOGF',not self.motorPv.get('JOGF'))
def onPushHOMR(self):
'''homr button was pressed'''
if self.motorPv is not None:
self.motorPv.put('HOMR',1)
def onPushHOMF(self):
'''homf button was pressed'''
if self.motorPv is not None:
self.motorPv.put('HOMF',1)
def onPushPLOT(self):
'''plot button was pressed'''
self.controls['ArrayStat'].startPlot()
def onPushTWF(self):
'''tweak forward button was pressed'''
if self.motorPv is not None:
self.motorPv.put('TWF', 1)
def onPushTWR(self):
'''tweak reverse button was pressed'''
if self.motorPv is not None:
self.motorPv.put('TWR', 1)
def onPush10x(self):
'''multiply TWV*10 button was pressed'''
if self.motorPv is not None:
self.motorPv.put('TWV', 10*self.motorPv.get('TWV'))
def onPush_1x(self):
'''multiply TWV*0.1 button was pressed'''
if self.motorPv is not None:
self.motorPv.put('TWV', 0.1*self.motorPv.get('TWV'))
def onReturnTWV(self):
'''new target value was entered in this panel'''
if self.motorPv is not None:
number = float(self.controls['TWV'].text())
self.motorPv.put('TWV', number)
def onReturnVAL(self):
'''new target value was entered in this panel'''
if self.motorPv is not None:
number = float(self.controls['VAL'].text())
#self.motorPv.move(number)
self.motorPv.put('VAL',number)
def onReturnVELO(self):
'''new target velocity was entered in this panel'''
if self.motorPv is not None:
number = float(self.controls['VELO'].text())
self.motorPv.put('VELO',number)
def onReturnJVEL(self):
'''new target jog velocity was entered in this panel'''
if self.motorPv is not None:
number = float(self.controls['JVEL'].text())
self.motorPv.put('JVEL',number)
def onChangeCNEN(self, value = None, **kws):
'''EPICS monitor on CNEN called this'''
field='CNEN'
if value:
self.controls[field].setStyleSheet(STYLES['BUTTON_ON'])
else:
self.controls[field].setStyleSheet(STYLES[field])
def onChangeJOGR(self, value = None, **kws):
'''EPICS monitor on JOGR called this'''
field='JOGR'
if value:
self.controls[field].setStyleSheet(STYLES['BUTTON_ON'])
else:
self.controls[field].setStyleSheet(STYLES[field])
def onChangeJOGF(self, value = None, **kws):
'''EPICS monitor on JOGF called this'''
field='JOGF'
if value:
self.controls[field].setStyleSheet(STYLES['BUTTON_ON'])
else:
self.controls[field].setStyleSheet(STYLES[field])
def onChangeHOMR(self, value = None, **kws):
'''EPICS monitor on HOMR called this'''
field='HOMR'
if value:
self.controls[field].setStyleSheet(STYLES['BUTTON_ON'])
else:
self.controls[field].setStyleSheet(STYLES[field])
def onChangeHOMF(self, value = None, **kws):
'''EPICS monitor on HOMF called this'''
field='HOMF'
if value:
self.controls[field].setStyleSheet(STYLES['BUTTON_ON'])
else:
self.controls[field].setStyleSheet(STYLES[field])
def onChangeMSTA(self, value = None, **kws):
'''EPICS monitor on MSTA called this'''
field='MSTA'
self.controls[field].setText(bin(int(value))[2:])
def onChangeDESC(self, char_value=None, **kws):
'''EPICS monitor on DESC called this'''
self.controls['DESC'].setText(char_value)
def onChangeDMOV(self, value = None, **kws):
'''EPICS monitor on DMOV called this, change the color of the RBV label'''
if value is not None:
color = {1: BACKGROUND_DONE_MOVING, 0: BACKGROUND_MOVING}[value]
self.setLabelBackground(self.controls['RBV'], color)
def onChangeHLS(self, value=None, **kws):
'''EPICS monitor on HLS called this, change the color of the TWF button'''
if value is not None:
color = {0: BACKGROUND_DEFAULT, 1: BACKGROUND_LIMIT_ON}[value]
self.setLabelBackground(self.controls['TWF'], color)
def onChangeLLS(self, value=None, **kws):
'''EPICS monitor on LLS called this, change the color of the TWR button'''
if value is not None:
color = {0: BACKGROUND_DEFAULT, 1: BACKGROUND_LIMIT_ON}[value]
self.setLabelBackground(self.controls['TWR'], color)
def onChangeEGU(self, char_value=None, **kws):
'''EPICS monitor on EGU called this'''
self.controls['EGU'].setText(char_value)
def onChangeRBV(self, value=None, **kws):
'''EPICS monitor on RBV called this'''
field='RBV'
if value is not None:
self.controls[field].setText(str(value))
def onChangeTWV(self, value=None, **kws):
'''EPICS monitor on TWV called this'''
field='TWV'
if value is not None:
self.controls[field].setText(str(value))
self.controls[field].setAlignment(QtCore.Qt.AlignRight)
def onChangeVAL(self, value=None, **kws):
'''EPICS monitor on VAL called this'''
field='VAL'
if value is not None:
self.controls[field].setText(str(value))
self.controls[field].setAlignment(QtCore.Qt.AlignRight)
def onChangeVELO(self, value=None, **kws):
'''EPICS monitor on VELO called this'''
field='VELO'
if value is not None:
self.controls[field].setText(str(value))
self.controls[field].setAlignment(QtCore.Qt.AlignRight)
def onChangeJVEL(self, value=None, **kws):
'''EPICS monitor on JVEL called this'''
field='JVEL'
if value is not None:
self.controls[field].setText(str(value))
self.controls[field].setAlignment(QtCore.Qt.AlignRight)
def onChangeCntrlErrorIdPv(self,pvname=None, value=None, char_value=None, **kw):
self.cntrlErrorMsg=self.cntrlErrorMsgPv.get(as_string=True)
print("new Error message: " +str(self.cntrlErrorMsg))
def onChangeCntrlErrorMsgPv(self,pvname=None, value=None, char_value=None, **kw):
print("onChangeCntrlErrorMsgPv:" + char_value)
def onChangeCntrlCmdPv(self,pvname=None, value=None, char_value=None, **kw):
print("onChangeCntrlCmdPv")
def setLabelBackground(self, widget = None, color = BACKGROUND_DEFAULT):
'''change the background color of a Qt widget'''
if widget is not None:
palette = QtGui.QPalette()
palette.setColor(widget.backgroundRole(), QtGui.QColor(color))
widget.setPalette(palette)
def main():
'''demo: display the named motors in a horizontal block'''
if len(sys.argv) != 2:
raise RuntimeError ("usage: %s motor".format(sys.argv[0]))
app = QtWidgets.QApplication(sys.argv)
panel = MotorPanel(pvname=sys.argv[1])
#panel.connect()
panel.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

177
tools/ecmcRTCanvas.py Normal file
View File

@@ -0,0 +1,177 @@
#*************************************************************************
# 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.
#
# ecmcRTCanvas.py
#
# Created on: July 6, 2020
# Author: Anders Sandström
#
# Heavily inspired by: https://exceptionshub.com/real-time-plotting-in-while-loop-with-matplotlib.html
#
#***************************************************************************
import sys
import os
import epics
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
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 time
import threading
class ecmcRTCanvas(FigureCanvas, TimedAnimation):
def __init__(self, title):
self.pause = 0
self.addedData = []
self.exceptCount = 0
self.autoZoom = False
print(matplotlib.__version__)
# The data
self.xlim = 1000
self.n = np.linspace(-(self.xlim - 1), 0, self.xlim)
self.y = (self.n * 0.0)
# The window
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax1 = self.fig.add_subplot(111)
# self.ax1 settings
self.ax1.set_xlabel('samples')
self.ax1.set_ylabel('data')
self.ax1.set_title(title)
self.line1 = Line2D([], [], color='blue')
self.line1_tail = Line2D([], [], color='red', linewidth=2)
self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
self.ax1.add_line(self.line1)
self.ax1.add_line(self.line1_tail)
self.ax1.add_line(self.line1_head)
self.ax1.set_xlim(-(self.xlim - 1),0)
self.ax1.set_ylim(-100, 100)
self.ax1.grid()
self.firstUpdatedData = True
FigureCanvas.__init__(self, self.fig)
TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)
return
def new_frame_seq(self):
return iter(range(self.n.size))
def setBufferSize(self, bufferSize):
if bufferSize<1000 :
print("Buffer size out of range: " + str(bufferSize))
return
fillValue = self.y[0]
oldSize = self.xlim
self.xlim = int(bufferSize)
self.n = np.linspace(-(self.xlim - 1),0,self.xlim)
if self.xlim > oldSize:
tempArray = np.full(self.xlim - oldSize,fillValue)
self.y = np.concatenate((tempArray, self.y))
else:
self.y = self.y[oldSize-self.xlim:-1]
self.ax1.set_xlim(-(self.xlim-1), 1)
self.draw()
def pauseUpdate(self):
if self.pause:
self.pause = 0
else:
self.pause = 1
def _init_draw(self):
lines = [self.line1, self.line1_tail, self.line1_head]
for l in lines:
l.set_data([], [])
return
def addData(self, value):
if self.pause == 0:
self.addedData.append(value)
return
def zoomAuto(self):
bottom = np.min(self.y)
top = np.max(self.y)
# ensure different values
if bottom == top:
top = bottom +1
self.ax1.clear()
self.ax1.grid(b=True)
range = top - bottom
top += range * 0.1
bottom -= range *0.1
self.ax1.set_ylim(bottom,top)
self.ax1.set_xlim(-(self.xlim-1), 1)
self.draw()
return
def zoomLow(self, value):
top = self.ax1.get_ylim()[1]
bottom = value
self.ax1.set_ylim(bottom,top)
self.draw()
return
def zoomHigh(self, value):
bottom = self.ax1.get_ylim()[0]
top = value
self.ax1.set_ylim(bottom,top)
self.draw()
return
def _step(self, *args):
# Extends the _step() method for the TimedAnimation class.
try:
TimedAnimation._step(self, *args)
except Exception as e:
self.exceptCount += 1
print(str(self.exceptCount))
TimedAnimation._stop(self)
pass
return
def getYLims(self):
return self.ax1.get_ylim()
def _draw_frame(self, framedata):
margin = 1
while(len(self.addedData) > 0):
self.y = np.roll(self.y, -1)
self.y[-1] = self.addedData[0]
if self.firstUpdatedData:
if len(self.addedData) > 0:
self.y[0:-1] = self.addedData[0] # Set entire array to start value
self.firstUpdatedData = False
self.zoomAuto()
del(self.addedData[0])
self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]
return
def setYLabel(self,label):
self.ax1.set_ylabel(label)
self.draw()
def setTitle(self,label):
self.ax1.set_title(label)
self.draw()

199
tools/ecmcTrend.py Normal file
View File

@@ -0,0 +1,199 @@
#*************************************************************************
# 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.
#
# ecmcTrend.py
#
# Created on: July 6, 2020
# Author: Anders Sandström
#
# Heavily inspired by: https://exceptionshub.com/real-time-plotting-in-while-loop-with-matplotlib.html
#
#*************************************************************************
import sys
import os
import ecmcRTCanvas
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
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 time
import threading
class ecmcTrend(QtWidgets.QDialog):
def __init__(self):
super(ecmcTrend, self).__init__()
# Define the geometry of the main window
self.setGeometry(300, 300, 900, 700)
self.setWindowTitle("ecmc plot")
self.main_frame= QtWidgets.QFrame(self)
self.main_layout = QtWidgets.QHBoxLayout()
self.left_frame = QFrame(self)
self.left_layout = QVBoxLayout()
self.right_frame = QFrame(self)
self.right_layout = QVBoxLayout()
# Manual zoom High
self.zoomHigh_frame = QFrame(self)
self.zoomHigh_layout = QGridLayout()
self.lblYMax= QLabel(text = "y-max:")
self.lineEditZoomHigh = QLineEdit(text = '100')
self.lineEditZoomHigh.setFixedSize(100, 50)
self.zoomHighBtn = QPushButton(text = '>')
self.zoomHighBtn.setFixedSize(10, 50)
self.zoomHighBtn.clicked.connect(self.zoomHighBtnAction)
self.zoomHigh_layout.addWidget(self.lblYMax,0,0,alignment = Qt.AlignRight | Qt.AlignBottom)
self.zoomHigh_layout.addWidget(self.lineEditZoomHigh,1,0,alignment = Qt.AlignRight | Qt.AlignTop)
self.zoomHigh_layout.addWidget(self.zoomHighBtn,1,1,alignment = Qt.AlignLeft | Qt.AlignTop)
self.zoomHigh_frame.setLayout(self.zoomHigh_layout)
# Auto zoom
self.zoomBtn = QPushButton(text = 'zoom auto')
self.zoomBtn.setFixedSize(100, 50)
self.zoomBtn.clicked.connect(self.zoomBtnAction)
# Pause
self.pauseBtn = QPushButton(text = 'pause')
self.pauseBtn.setFixedSize(100, 50)
self.pauseBtn.clicked.connect(self.pauseBtnAction)
# Manual zoom Low
self.zoomLow_frame = QFrame(self)
self.zoomLow_layout = QGridLayout()
self.lblYMin= QLabel(text = "y-min:")
self.lineEditZoomLow = QLineEdit(text = '-100')
self.lineEditZoomLow.setFixedSize(100, 50)
self.zoomLowBtn = QPushButton(text = '>')
self.zoomLowBtn.setFixedSize(10, 50)
self.zoomLowBtn.clicked.connect(self.zoomLowBtnAction)
self.zoomLow_layout.addWidget(self.lblYMin,0,0,alignment = Qt.AlignRight | Qt.AlignBottom)
self.zoomLow_layout.addWidget(self.lineEditZoomLow,1,0,alignment = Qt.AlignRight | Qt.AlignTop)
self.zoomLow_layout.addWidget(self.zoomLowBtn,1,1,alignment = Qt.AlignLeft | Qt.AlignTop)
self.zoomLow_frame.setLayout(self.zoomLow_layout)
# Write PV
self.pvPut_frame = QFrame(self)
self.pvPut_layout = QGridLayout()
self.lblYMin= QLabel(text = "Write PV:")
self.lineEditpvPut = QLineEdit(text = '0')
self.lineEditpvPut.setFixedSize(100, 50)
self.pvPutBtn = QPushButton(text = '>')
self.pvPutBtn.setFixedSize(10, 50)
self.pvPutBtn.clicked.connect(self.pvPutBtnAction)
self.pvPut_layout.addWidget(self.lblYMin,0,0,alignment = Qt.AlignRight | Qt.AlignBottom)
self.pvPut_layout.addWidget(self.lineEditpvPut,1,0,alignment = Qt.AlignRight | Qt.AlignTop)
self.pvPut_layout.addWidget(self.pvPutBtn,1,1,alignment = Qt.AlignLeft | Qt.AlignTop)
self.pvPut_frame.setLayout(self.pvPut_layout)
# Buffer size
self.bufferSize_frame = QFrame(self)
self.bufferSize_layout = QGridLayout()
self.lblBufferSize= QLabel(text = "Buffer size []")
self.lineBufferSize = QLineEdit(text = '1000')
self.lineBufferSize.setFixedSize(100, 50)
self.setBufferSizeBtn = QPushButton(text = '>')
self.setBufferSizeBtn.setFixedSize(10, 50)
self.setBufferSizeBtn.clicked.connect(self.setBufferSizeBtnAction)
self.bufferSize_layout.addWidget(self.lblBufferSize,0,0,alignment = Qt.AlignRight | Qt.AlignBottom) # row, col
self.bufferSize_layout.addWidget(self.lineBufferSize, 1,0,alignment = Qt.AlignRight | Qt.AlignTop)
self.bufferSize_layout.addWidget(self.setBufferSizeBtn, 1,1,alignment = Qt.AlignLeft | Qt.AlignTop)
self.bufferSize_frame.setLayout(self.bufferSize_layout)
self.spacerTop = QSpacerItem(100,50)
self.spacerZoomUpper = QSpacerItem(100,10)
self.spacerZoomLower = QSpacerItem(100,10)
self.left_layout.addWidget(self.zoomHigh_frame)
self.left_layout.addWidget(self.zoomBtn)
self.left_layout.addWidget(self.pauseBtn)
self.left_layout.addWidget(self.zoomLow_frame)
self.left_layout.addWidget(self.pvPut_frame)
# Place the matplotlib figure
self.myFig = ecmcRTCanvas.ecmcRTCanvas("ecmc plot")
self.myFig.setFixedSize(700,500 )
self.toolbar = NavigationToolbar(self.myFig, self)
self.right_layout.addWidget(self.toolbar)
self.right_layout.addWidget(self.myFig)
self.right_layout.addWidget(self.bufferSize_frame)
self.lineEditZoomLow.setText(str(self.myFig.getYLims()[0]))
self.lineEditZoomHigh.setText(str(self.myFig.getYLims()[1]))
self.left_frame.setLayout(self.left_layout)
self.right_frame.setLayout(self.right_layout)
self.main_layout.addWidget(self.left_frame)
self.main_layout.addWidget(self.right_frame)
self.main_frame.setLayout(self.main_layout)
return
def setBufferSizeBtnAction(self):
value = float(self.lineBufferSize.text())
self.myFig.setBufferSize(value)
def zoomBtnAction(self):
self.myFig.zoomAuto()
return
def zoomHighBtnAction(self):
value = float(self.lineEditZoomHigh.text())
self.myFig.zoomHigh(value)
return
def zoomLowBtnAction(self):
value = float(self.lineEditZoomLow.text())
self.myFig.zoomLow(value)
return
def pvPutBtnAction(self):
value = float(self.lineEditpvPut.text())
self.writePV(value)
return
def pauseBtnAction(self):
self.myFig.pauseUpdate()
return
def lineEditHighAction(self):
value = float(self.lineEditZoomHigh.text())
self.myFig.zoomHigh(value)
return
def lineEditLowAction(self):
value = float(self.lineEditZoomLow.text())
self.myFig.zoomLow(value)
return
def addData_callbackFunc(self, value):
self.myFig.addData(value)
return
def setYLabel(self,label):
self.myFig.setYLabel(label)
def setTitle(self,label):
self.myFig.setTitle(label)
self.setWindowTitle("ecmc plot: " + label)
def enablePut(self,enable):
self.pvPut_frame.setEnabled(enable)
self.pvPutBtn.setEnabled(enable)
self.lineEditpvPut.setEnabled(enable)
QCoreApplication.processEvents()

70
tools/ecmcTrendPv.py Normal file
View File

@@ -0,0 +1,70 @@
#*************************************************************************
# 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.
#
# ecmcTrendPv.py
#
# Created on: July 6, 2020
# Author: Anders Sandström
#
# Heavily inspired by: https://exceptionshub.com/real-time-plotting-in-while-loop-with-matplotlib.html
#
# Extends the ecmcTrend class will epics pv callbacks
#
#*************************************************************************
import sys
import os
import epics
import ecmcTrend
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
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 time
import threading
class comTrend(QObject):
data_signal = pyqtSignal(float)
class ecmcTrendPv(ecmcTrend.ecmcTrend):
def __init__(self,pvName=None):
super(ecmcTrendPv, self).__init__()
self.comTrend = comTrend()
self.comTrend.data_signal.connect(self.addData_callbackFunc) # update trend
self.startval = 0
self.pvName = pvName
self.connectPv(self.pvName) # Epics
self.setTitle(pvName)
return
def connectPv(self, pvname):
if pvname is None:
raise RuntimeError("pvname must not be 'None'")
if len(pvname)==0:
raise RuntimeError("pvname must not be ''")
self.pv = epics.PV(self.pvName)
self.startval = self.pv.get()
self.pv.add_callback(self.onChangePv)
self.myFig.addData(self.startval)
QCoreApplication.processEvents()
def onChangePv(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
self.comTrend.data_signal.emit(value)
def writePV(self,value):
self.pv.put(value)
self.myFig.addData(value)