Files
EsfRixsApps/scratch/epics_widgets/SimMotorTweak.py
2024-08-28 08:45:18 +02:00

326 lines
11 KiB
Python

#!/usr/bin/env python
# *-----------------------------------------------------------------------*
# | |
# | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) |
# | Based on Zac great first implementation |
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
# *-----------------------------------------------------------------------*
# simulated motor tweaks ui:
# same interface as MotorTweak and SmaractMotorTweaks, but mimes just a simulated motor
import logging
from time import sleep
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPainter, QBrush, QColor, QPainterPath, QPen, QDoubleValidator
from PyQt5.QtWidgets import QMenu, QInputDialog, QAction
from PyQt5.uic import loadUiType
from epics.ca import pend_event
Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui')
_log = logging.getLogger(__name__)
#logger.setLevel(logging.INFO)
class SimMotor:
def __init__(self,rec_name, short_name):
self._llm = -10
self._hlm = 10
self._prec = 5
self._twv = 0.1
self._units = 'mm'
self._pos = 3.1415
self._short_name=short_name
self._rec_name=rec_name
class SimMotorTweak(QWidget, Ui_MotorTweak):
event_val = pyqtSignal(str, dict)
event_rbv = pyqtSignal(str, dict)
event_soft_limit = pyqtSignal(str, dict)
event_high_hard_limit = pyqtSignal(str, dict)
event_low_hard_limit = pyqtSignal(str, dict)
event_axis_fault = pyqtSignal(str, dict)
def __init__(self, parent=None):
super(SimMotorTweak, self).__init__(parent=parent)
self.setupUi(self)
self._wheel_tweaks = True
self._auto_labels = True
self._locked = False
self._label_style = 'basic'
self._templates_source = {
'basic': '<b>{short_name}</b> <font color="#080">{{rbv:.{precision}f}} {units}</font>',
'small': '<small>{short_name} <font size="small" color="#080">{{rbv:.{precision}f}} {units}</font><small>',
'2 lines': '<b>{short_name}</b><br><font size="small" color="#080">{{rbv:.{precision}f}} {units}</font>',
'busy': '<b>{short_name}</b> <font color="#080">{{rbv:.{precision}f}} {units}</font>'
}
self._templates = {}
def connect_motor(self, rec_name, short_name=None, *args, **kwargs):
self.label.setToolTip('{} => {}'.format(rec_name, short_name))
self._motor=m=SimMotor(rec_name, short_name)
self.set_motor_validator()
self._drive_val.setText(str(m._pos))
self._drive_val.returnPressed.connect(self.move_abs)
self.jog_forward.hide()
self.jog_reverse.hide()
tweak_min = kwargs.get("tweak_min", 0.0001)
tweak_max = kwargs.get("tweak_max", 20.0)
self._tweak_val.setText(str(m._twv))
self._tweak_val.setValidator(QDoubleValidator(tweak_min, tweak_max, 4, self._tweak_val))
#self._tweak_val.editingFinished.connect(lambda m=self._pv_tweak_val: m.put(self._tweak_val.text()))
self.tweak_forward.clicked.connect(lambda m: self.move_rel(float(self._tweak_val.text())))
self.tweak_reverse.clicked.connect(lambda m: self.move_rel(-float(self._tweak_val.text())))
#self.tweak_forward.clicked.connect(lambda m: self._pv_tweak_f.put(1))
#self.tweak_reverse.clicked.connect(lambda m: self._pv_tweak_r.put(1))
self.bind_wheel()
self.update_label_template()
#self._pv_readback.add_callback(self.update_label)
#self._pv_status.add_callback(self.set_val_on_status_change)
# self._pv_drive.add_callback(self.set_val)
#self._pv_drive.add_callback(self.emit_signals, source_field='VAL')
#self._pv_readback.add_callback(self.emit_signals, source_field='RBV')
#self._pv_hlm.add_callback(self.emit_signals, source_field='HLS')
#self._pv_llm.add_callback(self.emit_signals, source_field='LLS')
# m.set_callback('HLM', self.set_motor_validator)
def set_motor_validator(self, **kwargs):
m=self._motor
lineedit = self._drive_val
min, max = m._llm, m._hlm
if min == max:
min = -1e6
max = 1e6
lineedit.setValidator(QDoubleValidator(min, max, 5, lineedit))
def move_rel(self, dist, delay=None):
m=self._motor
m._pos += dist
if delay:
sleep(delay)
_log.debug('{} rel move => {}'.format(m._short_name, dist))
self.update_label()
self.emit_signals(source_field='VAL')
def get_rbv(self):
return self._motor._pos
def get_val(self):
return self._motor._pos
def is_homed(self):
return True
def get_status(self, as_string=False):
"""
States:
0 Stopped
1 Stepping
2 Scanning
3 Holding
4 Targeting
5 Move Delay
6 Calibrating
7 Finding Ref
8 Locked
:return:
"""
return 0
def wait(self):
pend_event(.01)
while not self.is_done():
pend_event(0.2)
def is_done(self):
return True # in (0, 3)
def move_abs(self, drive=None, wait=False, assert_position=False):
m=self._motor
if assert_position:
wait=True
if drive is None:
_log.debug('{} abs target from widget'.format(m._short_name))
drive = float(self._drive_val.text())
_log.debug('{} abs move => {}'.format(m._short_name, drive))
m._pos=drive
self.update_label()
self.emit_signals(source_field='VAL')
def emit_signals(self, **kw):
m=self._motor
field = kw['source_field']
if field == 'VAL':
self.event_val.emit(m._rec_name, kw)
elif field == 'RBV':
self.event_rbv.emit(m._rec_name, kw)
elif field == 'LVIO':
self.event_soft_limit.emit(m._rec_name, kw)
elif field == 'HLS':
self.event_high_hard_limit.emit(m._rec_name, kw)
self.event_axis_fault.emit(m._rec_name, kw)
elif field == 'LVIO':
self.event_low_hard_limit.emit(m._rec_name, kw)
self.event_axis_fault.emit(m._rec_name, kw)
elif field == 'STAT':
self.event_axis_fault.emit(m._rec_name, kw)
def set_val_on_status_change(self, **kw):
'''
0 Stopped
1 Stepping
2 Scanning
3 Holding
4 Targeting
5 Move Delay
6 Calibrating
7 Finding Ref
8 Locked
:param kw:
:return:
'''
status = kw['char_value']
if status in ('Holding', 'Stopped'):
v = self._pv_readback.get(as_string=True)
_log.debug('updating VAL on status change to holding/stopped = {}'.format(v))
self._drive_val.setText(v)
def set_val(self, **kw):
v = kw['char_value']
_log.debug('updating VAL = {}'.format(v))
self._drive_val.setText(v)
def update_label(self, **kwargs):
m=self._motor
self.label.setText(self._templates[self._label_style].format(rbv=m._pos))
self._drive_val.setText(f'{m._pos:.3f}')
twv=float(self._tweak_val.text())
self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(twv, m._units))
self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(twv, m._units))
def tweak_event(self, event):
sign = event.angleDelta().y()
if sign < 0:
self._pv_tweak_r.put(1)
else:
self._pv_tweak_f.put(1)
def bind_wheel(self):
self.tweak_forward.wheelEvent = self.tweak_event
self.tweak_reverse.wheelEvent = self.tweak_event
def contextMenuEvent(self, event):
name = self._pv_name.get()
unit = self._units
prec = self._prec
menu = QMenu(self)
menu.setTitle(self.short_name)
lockmotor = QAction('lock motor', menu, checkable=True)
lockmotor.setChecked(self._locked)
menu.addAction(lockmotor)
autolabelsAction = QAction('auto', menu, checkable=True)
autolabelsAction.setChecked(self._auto_labels)
menu.addAction(autolabelsAction)
wheel_tweaks = QAction('Mouse wheel tweaks motor', menu, checkable=True)
wheel_tweaks.setChecked(self._wheel_tweaks)
menu.addAction(wheel_tweaks)
# stopmotorAction = QAction('Stopped', menu, checkable=True)
# stopmotorAction.setChecked(SPMG_STOP == m.stop_go)
# menu.addAction(stopmotorAction)
changeprecAction = menu.addAction("Change Precision")
changetweakstepAction = menu.addAction("Tweak step {:.3f} {}".format(self._pv_tweak_val.get(), unit))
tozeroAction = menu.addAction("move to Zero")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == lockmotor:
self._locked = not self._locked
if self._locked:
self._controlbox.setDisabled(True)
else:
self._controlbox.setDisabled(False)
elif action == changeprecAction:
msg = 'Precision for motor {}'.format(name)
_log.debug('prec before %d', prec)
prec, ok = QInputDialog.getInt(self, msg, msg, prec, 0, 10)
_log.debug('prec after (%d) %d', ok, prec)
if ok:
self._prec = prec
elif action == changetweakstepAction:
tv = self._pv_tweak_val.get()
msg = 'Tweak step for motor {} at {} {}'.format(name, tv, unit)
tv, ok = QInputDialog.getDouble(self, msg, msg, tv, pow(10, -prec), 10.0, prec)
if ok:
self._pv_tweak_val.put(tv)
elif action == tozeroAction:
self._pv_drive.put(0.0)
elif action == autolabelsAction:
self._auto_labels = not self._auto_labels
elif action == wheel_tweaks:
self._wheel_tweaks = not self._wheel_tweaks
self.bind_wheel()
self.update_label_template()
def resizeEvent(self, event):
pass
def update_label_template(self):
m=self._motor
source = self._templates_source
target = self._templates
for k in source:
target[k] = source[k].format(
short_name=m._short_name,
precision=m._prec,
units=m._units)
self.label.setText(target[self._label_style].format(rbv=m._pos))
def paintEvent(self, e):
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing)
self._draw_limits(qp)
qp.end()
def _draw_limits(self, qp):
pass
# width, height = self.size().width(), self.size().height()
# pad = 5
# rounding = 2
# size = 10
# if m.HLS:
# x, y, w, h = width - size, pad, size, height - 2 * pad
# elif m.LLS:
# x, y, w, h = 0, pad, size, height - 2 * pad
# else:
# return
# color = QColor('indianred')
# qp.setBrush(QBrush(color, Qt.SolidPattern))
# qp.setPen(QPen(color))
# path = QPainterPath()
# path.setFillRule(Qt.WindingFill)
# path.addRoundedRect(x, y, w, h, rounding, rounding)
# qp.drawPath(path)