WIP
This commit is contained in:
537
tools/ecmcArrayStat.py
Normal file
537
tools/ecmcArrayStat.py
Normal 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
187
tools/ecmcGuiMain.py
Normal 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_())
|
||||
|
||||
@@ -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
782
tools/ecmcOneMotorGUI.py
Normal 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
177
tools/ecmcRTCanvas.py
Normal 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
199
tools/ecmcTrend.py
Normal 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
70
tools/ecmcTrendPv.py
Normal 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)
|
||||
Reference in New Issue
Block a user