diff --git a/ARESvis/ARESvis.py b/ARESvis/ARESvis.py index 8c5cddb..20c8cd4 100755 --- a/ARESvis/ARESvis.py +++ b/ARESvis/ARESvis.py @@ -35,14 +35,11 @@ bitmask for simulation: 0x80: """ - - from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QLineEdit,\ QCheckBox, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QComboBox from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QPolygon, QTransform -from PyQt5.QtCore import QPoint, QPointF, Qt +from PyQt5.QtCore import QPoint, QPointF, Qt,pyqtSignal -from pyqtgraph.Qt import QtCore, QtGui import PyQt5.QtGui as QtGui import PyQt5.QtCore as QtCore import PyQt5.QtWidgets as QtW @@ -52,8 +49,57 @@ import numpy as np import sys, logging, copy import epics +if sys.version_info[0] < 3 or sys.version_info[1] < 6: + print(f"Must be using Python 3.6 or newer. Try e.g. /opt/gfa/python-3.8/latest/bin/python /sf/furka/bin/ARESvis") + _log=logging.getLogger(__name__) +import logging + +class col: + d = '\033[0m' #default + r = '\033[31m' #red + g = '\033[32m' #green + y = '\033[33m' #yellow + rr= '\033[91m' #red(bright) + gg= '\033[92m' #green(bright) + yy= '\033[93m' #yellow(bright) + b = '\033[1m' #bold + u = '\033[4m' #underline + R = '\033[1;31m' #bold, red + G = '\033[1;32m' #bold, green + Y = '\033[1;33m' #bold, yellow + + +class logHandler(logging.StreamHandler): + def __init__(self): + logging.StreamHandler.__init__(self) + + def emit(self, record): + '''override function of base class''' + try: + msg=self.format(record) + # print(record.__dict__) + if record.levelno<=10: + c=col.g + elif record.levelno<=20: + c=col.y + elif record.levelno<=30: + c=col.yy + elif record.levelno<=40: + c=col.r + else: + c=col.rr+col.b + msg=c+msg+col.d + stream=self.stream + stream.write(msg+self.terminator) + self.flush() + except RecursionError: + raise + except Exception: + self.handleError(record) + + class ARESdevice(): _lutDifrBeamPaint=( # (number of difr beam,draw mode,alpha,width) (4, 2, 190, 0), @@ -141,11 +187,11 @@ class ARESdevice(): 'drx':drx}) if degArm>10: - raise(ValueError('angle arm > 10°')) + raise(ValueError('angle arm > 10deg')) elif degArm<1: - raise(ValueError('angle arm < 1°')) + raise(ValueError('angle arm < 1deg')) elif abs(dd)>15: - raise(ValueError('angle bellow to detector > 15°')) + raise(ValueError('angle bellow to detector > 15deg')) def containsPoint(self,point): try: @@ -330,6 +376,14 @@ class ARESdevice(): # qp.setTransform(tf);self.plotOrig(qp) class WndVisualize(QWidget): + _pv2key={ + 'SATES30-RIXS:MOT_RY.RBV' :'aArm', + 'SATES30-ARES:MOT_JFRY.RBV':'aJFr', + 'SATES30-ARES:MOT_2TRY.RBV':'a2Th', + 'SATES30-ARES:MOT_DRY.RBV' :'aDet', + 'SATES30-ARES:MOT_SRY.RBV' :'aTrg' + } + def __init__(self): super().__init__() self.initUI() @@ -354,7 +408,8 @@ class WndVisualize(QWidget): ('sclA', ( -8, 8), 1), ('sclD', (-8, 8), 1),): - wLb=QLabel(key) + wLb=QLabel(f"{key}",objectName=key) #MOST BE with as it is changed later and else creates a seg fault + wSl=QSlider(QtCore.Qt.Horizontal,objectName=key) wSl.setFixedWidth(200);wSl.setMinimum(rng[0]);wSl.setMaximum(rng[1]) if key.startswith('scl'): @@ -371,35 +426,61 @@ class WndVisualize(QWidget): lg.addWidget(wLb, row, 0) lg.addWidget(wSl, row, 1);row+=1 + + #self.event_update.connect(self.cb_update) self.show() + def cb_update(self,*args,**kwargs): + _log.debug(f'{args} {kwargs}') + def connectEPICS(self): - self._pv=pv=set() - for rec_name in ( - 'SATES30-RIXS:MOT_RY', - 'SATES30-ARES:MOT_JFRY', - 'SATES30-ARES:MOT_2TRY', - 'SATES30-ARES:MOT_DRY', - 'SATES30-ARES:MOT_SRY',): + _log.info('connect PVs') + self._pvDict=pvd=dict() + self._pvConnected=0 + for pvn in self._pv2key.keys(): + pv=epics.get_pv(pvn,connection_callback=self.OnConnectionChange,callback=self.OnValueChange) + pvd[pvn]=pv + _log.info(f'{pv}') + #epics.Motor checks the record type and will fail if the record is not online + #therefore use epics.Device + #epics.Device will force to connect the PV in Device.add_callback + #therefore use epics.PV.add_callback to acc callback + #as soon as the devices are online, they are connected + #but with epics.Device creating PV is not fully flexible. epics.get_pv proviles connection and value change callbacks that is way more flexible. + #therefore the lowest level of the library (only pvs is the best suited + #m=epics.Motor(rec_name) + #m=epics.Device(rec_name, delim='.',with_poll=False,attrs=('VAL', 'RBV', 'DESC', 'RVAL','LVIO', 'HLS', 'LLS')) + #m.add_callback('RBV', self.OnChangedRBV) + #pv=m.PV('RBV',connect=False) + #pv.add_callback(self.OnChangedRBV) + #pv.connection_callbacks + #if not pv.connected: + # disconnected.add(rec_name) + #print(pv.connected) + #devs.add(m) - m=epics.Motor(rec_name) - pv.add(m) - #_log.debug(m.get_position()) # this is the VAL field - #_log.debug(m.PV('RBV').get()) # this is the RBV field - #_log.debug(m.get('RBV')) # this is the RBV field - #m.add_callback('RBV', self.update_label) - #m.set_callback('RBV', self.emit_signals, {'source_field': 'RBV'}) - #/home/zamofing_t/.local/lib/python3.8/site-packages/epics/motor.py - m.add_callback('RBV', self.OnChangedRBV) - #print(pv) -# def update_label(self, **kwargs): -# _log.info(kwargs) + def OnConnectionChange(self, pvname=None, conn=None, **kws): + pvc=self._pvConnected + if conn: + pvc+=1 + else: + if pvc>0: pvc-=1 + _log.info(f'PV connection {pvc}/{len(self._pvDict)}: {pvname} {conn}') + self._pvConnected=pvc -# def emit_signals(self, **kw): -# _log.info(kw) + app=QApplication.instance() + wGrp=app._wndVisualize._wdGrpDraw + key=self._pv2key[pvname] + wLb=wGrp.findChild(QLabel, key) + if not conn: + v=f"{key}" + wLb.setText(v) + else: + v=f"{key}" + wLb.setText(v) + def OnValueChange(self, pvname, value, **kw): #def OnChangedRBV(self, **kw): - def OnChangedRBV(self, pvname, value, **kw): #_log.info(kw) #{ # 'pvname': 'SATES30-ARES:MOT_2TRY.RBV', @@ -434,19 +515,43 @@ class WndVisualize(QWidget): # 'cb_info': (1, ) #} #_log.info(f"{kw['pvname']}:{kw['value']}") - _log.info(f"{pvname}:{value}") - if pvname=='SATES30-RIXS:MOT_RY.RBV': - self.sldChanged('aArm',value) - elif pvname=='SATES30-ARES:MOT_JFRY.RBV': - self.sldChanged('aJFr',value) - elif pvname=='SATES30-ARES:MOT_2TRY.RBV': - self.sldChanged('a2Th',value) - elif pvname=='SATES30-ARES:MOT_DRY.RBV': - self.sldChanged('aDet',value) - elif pvname=='SATES30-ARES:MOT_SRY.RBV': - self.sldChanged('aTrg',value) - else: + _log.info(f"PV val:{pvname}:{value}") + + try: + key=self._pv2key[pvname] + except KeyError as e: _log.warning(f"can't handle PV: {pvname}:{value}") + return + self.vis_update(key,value,'008000') + + def vis_update(self,key,value,col='000000'): + app=QApplication.instance() + wGrp=app._wndVisualize._wdGrpDraw + wSl=wGrp.findChild(QSlider, key) + wSl.blockSignals(True) + wSl.setValue(int(value)) # move the slider without emiting a signal + wSl.blockSignals(False) + self.sldChanged(key,value) # emit the signal + #self.event_update.emit(pvname=pvname, value=None) + wLb=wGrp.findChild(QLabel, key) + v=f"{key}" + wLb.setText(v) + + def liveView(self): + # try to live update all PVs + _log.info('') + p2k=self._pv2key + for pv in self._pvDict.values(): + pvn=pv.pvname + key=p2k[pvn] + if pv.connected: + value=pv.get() + self.vis_update(key,value,'008000') + print (pvn,key,value) + else: + print (pvn,key) + pass + def destroy(self, destroyWindow, destroySubWindows): #overloaded function _log.info('destroy') @@ -477,6 +582,11 @@ class WndVisualize(QWidget): g[key]=90-val/sclA else: g[key]=val + wGrp=app._wndVisualize._wdGrpDraw + wLb=wGrp.findChild(QLabel, key) + v=f"{key}" + wLb.setText(v)#print(wLb,v) + self.update() def mouseReleaseEvent(self, a0): @@ -494,6 +604,7 @@ class WndVisualize(QWidget): wGrp=self._wdGrpDraw #if wGrp.underMouse(): #draging sliders? if wGrp.geometry().contains(mousePos): + self.liveView() self._mouseDrag={'obj':wGrp, 'start':mousePos} return dev=app._dev @@ -513,7 +624,7 @@ class WndVisualize(QWidget): v=int(round(np.log2(v)*2)) else: v=devP[key] - wSl.setValue(v) + wSl.setValue(int(v)) self._mouseDrag={'obj':dev,'start':(mousePos,dev._paint['ofs'])} try: @@ -578,7 +689,7 @@ class WndVisualize(QWidget): if __name__ == '__main__': import argparse - logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') + logging.basicConfig(level=logging.DEBUG, handlers=[logHandler()], format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ') def main():