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

@ -31,7 +31,7 @@ import math
from secop.datatypes import ArrayOf, FloatRange, StringType, StructOf, TupleOf
from secop.errors import ConfigError, DisabledError
from secop.lib.sequence import SequencerMixin, Step
from secop.modules import BasicPoller, Drivable, Parameter
from secop.modules import Drivable, Parameter
class GarfieldMagnet(SequencerMixin, Drivable):
@ -47,9 +47,6 @@ class GarfieldMagnet(SequencerMixin, Drivable):
the symmetry setting selects which.
"""
pollerClass = BasicPoller
# parameters
subdev_currentsource = Parameter('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False)
subdev_enable = Parameter('Switch to set for on/off', datatype=StringType(), readonly=True, export=False)
@ -57,10 +54,10 @@ class GarfieldMagnet(SequencerMixin, Drivable):
subdev_symmetry = Parameter('Switch to read for symmetry', datatype=StringType(), readonly=True, export=False)
userlimits = Parameter('User defined limits of device value',
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
default=(float('-Inf'), float('+Inf')), readonly=False, poll=10)
default=(float('-Inf'), float('+Inf')), readonly=False)
abslimits = Parameter('Absolute limits of device value',
datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$')),
default=(-0.5, 0.5), poll=True,
default=(-0.5, 0.5),
)
precision = Parameter('Precision of the device value (allowed deviation '
'of stable values from target)',
@ -71,7 +68,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
calibration = Parameter('Coefficients for calibration '
'function: [c0, c1, c2, c3, c4] calculates '
'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)'
' in T', poll=1,
' in T',
datatype=ArrayOf(FloatRange(), 5, 5),
default=(1.0, 0.0, 0.0, 0.0, 0.0))
calibrationtable = Parameter('Map of Coefficients for calibration per symmetry setting',
@ -137,7 +134,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
'_current2field polynome not monotonic!')
def initModule(self):
super(GarfieldMagnet, self).initModule()
super().initModule()
self._enable = self.DISPATCHER.get_module(self.subdev_enable)
self._symmetry = self.DISPATCHER.get_module(self.subdev_symmetry)
self._polswitch = self.DISPATCHER.get_module(self.subdev_polswitch)
@ -220,7 +217,7 @@ class GarfieldMagnet(SequencerMixin, Drivable):
self._currentsource.read_value() *
self._get_field_polarity())
def read_hw_status(self):
def readHwStatus(self):
# called from SequencerMixin.read_status if no sequence is running
if self._enable.value == 'Off':
return self.Status.WARN, 'Disabled'

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