more merges from gerrit

Change-Id: I13441cd8889dd39f74a2dd1a85e75a1b76bb93c8
This commit is contained in:
2022-03-08 10:52:14 +01:00
parent 10018b8cad
commit 34b93adef0
20 changed files with 1423 additions and 340 deletions

View File

@ -39,7 +39,7 @@ from secop.datatypes import ArrayOf, EnumType, FloatRange, \
from secop.errors import CommunicationFailedError, \
ConfigError, HardwareError, ProgrammingError
from secop.lib import lazy_property
from secop.modules import BasicPoller, Command, \
from secop.modules import Command, \
Drivable, Module, Parameter, Readable
#####
@ -157,8 +157,6 @@ class PyTangoDevice(Module):
execution and attribute operations with logging and exception mapping.
"""
pollerClass = BasicPoller
# parameters
comtries = Parameter('Maximum retries for communication',
datatype=IntRange(1, 100), default=3, readonly=False,
@ -210,7 +208,7 @@ class PyTangoDevice(Module):
# exception mapping is enabled).
self._createPyTangoDevice = self._applyGuardToFunc(
self._createPyTangoDevice, 'constructor')
super(PyTangoDevice, self).earlyInit()
super().earlyInit()
@lazy_property
def _dev(self):
@ -249,10 +247,10 @@ class PyTangoDevice(Module):
# otherwise would lead to attribute errors later
try:
device.State
except AttributeError:
except AttributeError as e:
raise CommunicationFailedError(
self, 'connection to Tango server failed, '
'is the server running?')
'is the server running?') from e
return self._applyGuardsToPyTangoDevice(device)
def _applyGuardsToPyTangoDevice(self, dev):
@ -376,14 +374,17 @@ class AnalogInput(PyTangoDevice, Readable):
The AnalogInput handles all devices only delivering an analogue value.
"""
def startModule(self, started_callback):
super(AnalogInput, self).startModule(started_callback)
# query unit from tango and update value property
attrInfo = self._dev.attribute_query('value')
# prefer configured unit if nothing is set on the Tango device, else
# update
if attrInfo.unit != 'No unit':
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
def startModule(self, start_events):
super().startModule(start_events)
try:
# query unit from tango and update value property
attrInfo = self._dev.attribute_query('value')
# prefer configured unit if nothing is set on the Tango device, else
# update
if attrInfo.unit != 'No unit':
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
except Exception as e:
self.log.error(e)
def read_value(self):
return self._dev.value
@ -422,7 +423,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
userlimits = Parameter('User defined limits of device value',
datatype=LimitsType(FloatRange(unit='$')),
default=(float('-Inf'), float('+Inf')),
readonly=False, poll=10,
readonly=False,
)
abslimits = Parameter('Absolute limits of device value',
datatype=LimitsType(FloatRange(unit='$')),
@ -446,13 +447,13 @@ class AnalogOutput(PyTangoDevice, Drivable):
_moving = False
def initModule(self):
super(AnalogOutput, self).initModule()
super().initModule()
# init history
self._history = [] # will keep (timestamp, value) tuple
self._timeout = None # keeps the time at which we will timeout, or None
def startModule(self, started_callback):
super(AnalogOutput, self).startModule(started_callback)
def startModule(self, start_events):
super().startModule(start_events)
# query unit from tango and update value property
attrInfo = self._dev.attribute_query('value')
# prefer configured unit if nothing is set on the Tango device, else
@ -460,8 +461,8 @@ class AnalogOutput(PyTangoDevice, Drivable):
if attrInfo.unit != 'No unit':
self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit)
def pollParams(self, nr=0):
super(AnalogOutput, self).pollParams(nr)
def doPoll(self):
super().doPoll()
while len(self._history) > 2:
# if history would be too short, break
if self._history[-1][0] - self._history[1][0] <= self.window:
@ -489,8 +490,11 @@ class AnalogOutput(PyTangoDevice, Drivable):
hist = self._history[:]
window_start = currenttime() - self.window
hist_in_window = [v for (t, v) in hist if t >= window_start]
if len(hist) == len(hist_in_window):
return False # no data point before window
if not hist_in_window:
return False # no relevant history -> no knowledge
# window is too small -> use last point only
hist_in_window = [self.value]
max_in_hist = max(hist_in_window)
min_in_hist = min(hist_in_window)
@ -503,13 +507,14 @@ class AnalogOutput(PyTangoDevice, Drivable):
if self._isAtTarget():
self._timeout = None
self._moving = False
return super(AnalogOutput, self).read_status()
if self._timeout:
if self._timeout < currenttime():
return self.Status.UNSTABLE, 'timeout after waiting for stable value'
if self._moving:
return (self.Status.BUSY, 'moving')
return (self.Status.IDLE, 'stable')
status = super().read_status()
else:
if self._timeout and self._timeout < currenttime():
status = self.Status.UNSTABLE, 'timeout after waiting for stable value'
else:
status = (self.Status.BUSY, 'moving') if self._moving else (self.Status.IDLE, 'stable')
self.setFastPoll(self.isBusy(status))
return status
@property
def absmin(self):
@ -571,11 +576,14 @@ class AnalogOutput(PyTangoDevice, Drivable):
if not self.timeout:
self._timeout = None
self._moving = True
self._history = [] # clear history
self.read_status() # poll our status to keep it updated
# do not clear the history here:
# - if the target is not changed by more than precision, there is no need to wait
# self._history = []
self.read_status() # poll our status to keep it updated (this will also set fast poll)
return self.read_target()
def _hw_wait(self):
while super(AnalogOutput, self).read_status()[0] == self.Status.BUSY:
while super().read_status()[0] == self.Status.BUSY:
sleep(0.3)
def stop(self):
@ -597,8 +605,7 @@ class Actuator(AnalogOutput):
readonly=False, datatype=FloatRange(0, unit='$/s'),
)
ramp = Parameter('The speed of changing the value',
readonly=False, datatype=FloatRange(0, unit='$/s'),
poll=30,
readonly=False, datatype=FloatRange(0, unit='$/min'),
)
def read_speed(self):
@ -677,17 +684,22 @@ class TemperatureController(Actuator):
)
pid = Parameter('pid control Parameters',
datatype=TupleOf(FloatRange(), FloatRange(), FloatRange()),
readonly=False, group='pid', poll=30,
readonly=False, group='pid',
)
setpoint = Parameter('Current setpoint', datatype=FloatRange(unit='$'), poll=1,
setpoint = Parameter('Current setpoint', datatype=FloatRange(unit='$'),
)
heateroutput = Parameter('Heater output', datatype=FloatRange(), poll=1,
heateroutput = Parameter('Heater output', datatype=FloatRange(),
)
# overrides
precision = Parameter(default=0.1)
ramp = Parameter(description='Temperature ramp')
def doPoll(self):
super().doPoll()
self.read_setpoint()
self.read_heateroutput()
def read_ramp(self):
return self._dev.ramp
@ -730,6 +742,10 @@ class TemperatureController(Actuator):
def read_heateroutput(self):
return self._dev.heaterOutput
# remove UserCommand setposition from Actuator
# (makes no sense for a TemperatureController)
setposition = None
class PowerSupply(Actuator):
"""A power supply (voltage and current) device.
@ -737,13 +753,19 @@ class PowerSupply(Actuator):
# parameters
voltage = Parameter('Actual voltage',
datatype=FloatRange(unit='V'), poll=-5)
datatype=FloatRange(unit='V'))
current = Parameter('Actual current',
datatype=FloatRange(unit='A'), poll=-5)
datatype=FloatRange(unit='A'))
# overrides
ramp = Parameter(description='Current/voltage ramp')
def doPoll(self):
super().doPoll()
# TODO: poll voltage and current faster when busy
self.read_voltage()
self.read_current()
def read_ramp(self):
return self._dev.ramp
@ -777,16 +799,18 @@ class NamedDigitalInput(DigitalInput):
datatype=StringType(), export=False) # XXX:!!!
def initModule(self):
super(NamedDigitalInput, self).initModule()
super().initModule()
try:
# pylint: disable=eval-used
mapping = eval(self.mapping.replace('\n', ' '))
mapping = self.mapping
if isinstance(mapping, str):
# pylint: disable=eval-used
mapping = eval(self.mapping.replace('\n', ' '))
if isinstance(mapping, str):
# pylint: disable=eval-used
mapping = eval(mapping)
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
except Exception as e:
raise ValueError('Illegal Value for mapping: %r' % e)
raise ValueError('Illegal Value for mapping: %r' % self.mapping) from e
def read_value(self):
value = self._dev.value
@ -805,7 +829,7 @@ class PartialDigitalInput(NamedDigitalInput):
datatype=IntRange(0), default=1)
def initModule(self):
super(PartialDigitalInput, self).initModule()
super().initModule()
self._mask = (1 << self.bitwidth) - 1
# self.accessibles['value'].datatype = IntRange(0, self._mask)
@ -827,9 +851,16 @@ class DigitalOutput(PyTangoDevice, Drivable):
def read_value(self):
return self._dev.value # mapping is done by datatype upon export()
def read_status(self):
status = self.read_status()
self.setFastPoll(self.isBusy(status))
return status
def write_target(self, value):
self._dev.value = value
self.read_value()
self.read_status() # this will also set fast poll
return self.read_target()
def read_target(self):
attrObj = self._dev.read_attribute('value')
@ -845,22 +876,25 @@ class NamedDigitalOutput(DigitalOutput):
datatype=StringType(), export=False)
def initModule(self):
super(NamedDigitalOutput, self).initModule()
super().initModule()
try:
# pylint: disable=eval-used
mapping = eval(self.mapping.replace('\n', ' '))
mapping = self.mapping
if isinstance(mapping, str):
# pylint: disable=eval-used
mapping = eval(self.mapping.replace('\n', ' '))
if isinstance(mapping, str):
# pylint: disable=eval-used
mapping = eval(mapping)
self.accessibles['value'].setProperty('datatype', EnumType('value', **mapping))
self.accessibles['target'].setProperty('datatype', EnumType('target', **mapping))
except Exception as e:
raise ValueError('Illegal Value for mapping: %r' % e)
raise ValueError('Illegal Value for mapping: %r' % self.mapping) from e
def write_target(self, value):
# map from enum-str to integer value
self._dev.value = int(value)
self.read_value()
return self.read_target()
class PartialDigitalOutput(NamedDigitalOutput):
@ -875,7 +909,7 @@ class PartialDigitalOutput(NamedDigitalOutput):
datatype=IntRange(0), default=1)
def initModule(self):
super(PartialDigitalOutput, self).initModule()
super().initModule()
self._mask = (1 << self.bitwidth) - 1
# self.accessibles['value'].datatype = IntRange(0, self._mask)
# self.accessibles['target'].datatype = IntRange(0, self._mask)
@ -891,6 +925,7 @@ class PartialDigitalOutput(NamedDigitalOutput):
(value << self.startbit)
self._dev.value = newvalue
self.read_value()
return self.read_target()
class StringIO(PyTangoDevice, Module):