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():