make return value 'Done' unneccessary
'Done' was introduced in order to suppress unneccessary duplicate updates. However, since super calls on access methods are allowed, it is not nice when such a method returns Done, as this is not automagically replaced by the current parameter value. As a consequence: - using Done is discouraged, but not (yet) removed in all code - the 'omit_unchanged_within' property is moved from Module to an internal Parameter property 'update_unchanged' - its default is moved from a SEC node property to generalConfig - the 'update_unchanged' parameter property may be set to 'never' for parameters where duplicate updates make no sense - this property might be set to 'always', for measurements, where even unchanged values taken from HW should be transmitted Change-Id: I2847c983ca09c2c4098e402edd08d0c96c3913f4 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30672 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
9cab6670b9
commit
0d265b9752
@ -31,9 +31,6 @@ def read\_\ *<parameter>*\ (self):
|
||||
Called on a ``read`` SECoP message and whenever the internal poll mechanism
|
||||
of Frappy tries to get a new value. The return value should be the
|
||||
retrieved value.
|
||||
In special cases :data:`Done <frappy.modules.Done>` might be returned instead,
|
||||
when the internal code has already updated the parameter, or
|
||||
when the value has not changed and no updates should be emitted.
|
||||
This method might also be called internally, in case a fresh value of
|
||||
the parameter is needed.
|
||||
|
||||
@ -55,9 +52,6 @@ def write\_\ *<parameter>*\ (self, value):
|
||||
value would be the same, as if it would be done by the ``read_<parameter>``
|
||||
method. Often the easiest implementation is just returning the result of
|
||||
a call to the ``read_<parameter>`` method.
|
||||
Also, :ref:`Done <done unique>` might be returned in special
|
||||
cases, e.g. when the code was written in a way, when self.<parameter> is
|
||||
assigned already before returning from the method.
|
||||
|
||||
.. admonition:: behind the scenes
|
||||
|
||||
|
@ -11,10 +11,6 @@ imported from the frappy.core module.
|
||||
Module Base Classes
|
||||
...................
|
||||
|
||||
.. _done unique:
|
||||
|
||||
.. autodata:: frappy.modules.Done
|
||||
|
||||
.. autoclass:: frappy.modules.Module
|
||||
:members: earlyInit, initModule, startModule
|
||||
|
||||
|
@ -36,7 +36,6 @@ class Node(dict):
|
||||
description,
|
||||
interface=None,
|
||||
cls='frappy.protocol.dispatcher.Dispatcher',
|
||||
omit_unchanged_within=1.1,
|
||||
**kwds
|
||||
):
|
||||
super().__init__(
|
||||
@ -44,7 +43,6 @@ class Node(dict):
|
||||
description=description,
|
||||
interface=interface,
|
||||
cls=cls,
|
||||
omit_unchanged_within=omit_unchanged_within,
|
||||
**kwds
|
||||
)
|
||||
|
||||
|
@ -1212,7 +1212,7 @@ class OrType(DataType):
|
||||
"""accepts any of the given types, takes the first valid"""
|
||||
for t in self.types:
|
||||
try:
|
||||
return t(value)
|
||||
return t.validate(value) # use always strict validation
|
||||
except Exception:
|
||||
pass
|
||||
raise WrongTypeError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types))))
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
from frappy.datatypes import ArrayOf, BoolType, EnumType, \
|
||||
FloatRange, StringType, StructOf, TupleOf
|
||||
from frappy.core import Command, Done, Drivable, Feature, \
|
||||
from frappy.core import Command, Drivable, Feature, \
|
||||
Parameter, Property, PersistentParam, Readable
|
||||
from frappy.errors import RangeError, ConfigError
|
||||
from frappy.lib import clamp
|
||||
@ -39,6 +39,17 @@ class HasSimpleOffset(Feature):
|
||||
offset = PersistentParam('offset (physical value + offset = HW value)',
|
||||
FloatRange(unit='deg'), readonly=False, default=0)
|
||||
|
||||
def write_offset(self, value):
|
||||
self.offset = value
|
||||
if isinstance(self, HasLimits):
|
||||
self.read_limits()
|
||||
if isinstance(self, Readable):
|
||||
self.read_value()
|
||||
if isinstance(self, Drivable):
|
||||
self.read_target()
|
||||
self.saveParameters()
|
||||
return value
|
||||
|
||||
|
||||
class HasTargetLimits(Feature):
|
||||
"""user limits
|
||||
@ -83,7 +94,7 @@ class HasTargetLimits(Feature):
|
||||
raise RangeError('limits not within abs limits [%g, %g]' % (min_, max_))
|
||||
self.limits = value
|
||||
self.saveParameters()
|
||||
return Done
|
||||
return self.limits
|
||||
|
||||
def check_limits(self, value):
|
||||
"""check if value is valid"""
|
||||
@ -123,4 +134,4 @@ class HasLimits(Feature):
|
||||
"""check if value is valid"""
|
||||
min_, max_ = self.target_limits
|
||||
if not min_ <= value <= max_:
|
||||
raise BadValueError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
|
||||
raise RangeError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
|
||||
|
11
frappy/io.py
11
frappy/io.py
@ -33,7 +33,7 @@ from frappy.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange,
|
||||
StringType, TupleOf, ValueType
|
||||
from frappy.errors import CommunicationFailedError, ConfigError, ProgrammingError
|
||||
from frappy.modules import Attached, Command, \
|
||||
Communicator, Done, Module, Parameter, Property
|
||||
Communicator, Module, Parameter, Property
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
generalConfig.set_default('legacy_hasiodev', False)
|
||||
@ -110,7 +110,8 @@ class IOBase(Communicator):
|
||||
""", datatype=StringType())
|
||||
timeout = Parameter('timeout', datatype=FloatRange(0), default=2)
|
||||
wait_before = Parameter('wait time before sending', datatype=FloatRange(), default=0)
|
||||
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, default=False)
|
||||
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, default=False,
|
||||
update_unchanged='never')
|
||||
pollinterval = Parameter('reconnect interval', datatype=FloatRange(0), readonly=False, default=10)
|
||||
#: a dict of default settings for a device, e.g. for a LakeShore 336:
|
||||
#:
|
||||
@ -151,20 +152,20 @@ class IOBase(Communicator):
|
||||
self.is_connected is changed only by self.connectStart or self.closeConnection
|
||||
"""
|
||||
if self.is_connected:
|
||||
return Done # no need for intermediate updates
|
||||
return True
|
||||
try:
|
||||
self.connectStart()
|
||||
if self._last_error:
|
||||
self.log.info('connected')
|
||||
self._last_error = 'connected'
|
||||
self.callCallbacks()
|
||||
return Done
|
||||
return self.is_connected
|
||||
except Exception as e:
|
||||
if str(e) != self._last_error:
|
||||
self._last_error = str(e)
|
||||
self.log.error(self._last_error)
|
||||
raise SilentError(repr(e)) from e
|
||||
return Done
|
||||
return self.is_connected
|
||||
|
||||
def write_is_connected(self, value):
|
||||
"""value = True: connect if not yet done
|
||||
|
@ -28,7 +28,8 @@ import threading
|
||||
from collections import OrderedDict
|
||||
|
||||
from frappy.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
|
||||
IntRange, StatusType, StringType, TextType, TupleOf, DiscouragedConversion
|
||||
IntRange, StatusType, StringType, TextType, TupleOf, DiscouragedConversion, \
|
||||
NoneOr
|
||||
from frappy.errors import BadValueError, CommunicationFailedError, ConfigError, \
|
||||
ProgrammingError, SECoPError, secop_error
|
||||
from frappy.lib import formatException, mkthread, UniqueObject
|
||||
@ -131,7 +132,7 @@ class HasAccessibles(HasProperties):
|
||||
self.log.debug("read_%s failed with %r", pname, e)
|
||||
self.announceUpdate(pname, None, e)
|
||||
raise
|
||||
if value is Done:
|
||||
if value is Done: # TODO: to be removed when all code using Done is updated
|
||||
return getattr(self, pname)
|
||||
setattr(self, pname, value) # important! trigger the setter
|
||||
return value
|
||||
@ -163,7 +164,7 @@ class HasAccessibles(HasProperties):
|
||||
new_value = pobj.datatype(value)
|
||||
new_value = wfunc(self, new_value)
|
||||
self.log.debug('write_%s(%r) returned %r', pname, value, new_value)
|
||||
if new_value is Done:
|
||||
if new_value is Done: # TODO: to be removed when all code using Done is updated
|
||||
return getattr(self, pname)
|
||||
if new_value is None:
|
||||
new_value = value
|
||||
@ -300,6 +301,8 @@ class Module(HasAccessibles):
|
||||
features = Property('list of features', ArrayOf(StringType()), extname='features')
|
||||
pollinterval = Property('poll interval for parameters handled by doPoll', FloatRange(0.1, 120), default=5)
|
||||
slowinterval = Property('poll interval for other parameters', FloatRange(0.1, 120), default=15)
|
||||
omit_unchanged_within = Property('default for minimum time between updates of unchanged values',
|
||||
NoneOr(FloatRange(0)), export=False, default=None)
|
||||
enablePoll = True
|
||||
|
||||
# properties, parameters and commands are auto-merged upon subclassing
|
||||
@ -315,7 +318,6 @@ class Module(HasAccessibles):
|
||||
def __init__(self, name, logger, cfgdict, srv):
|
||||
# remember the dispatcher object (for the async callbacks)
|
||||
self.DISPATCHER = srv.dispatcher
|
||||
self.omit_unchanged_within = getattr(self.DISPATCHER, 'omit_unchanged_within', 0.1)
|
||||
self.log = logger
|
||||
self.name = name
|
||||
self.valueCallbacks = {}
|
||||
@ -443,7 +445,7 @@ class Module(HasAccessibles):
|
||||
|
||||
# 5) ensure consistency
|
||||
for aobj in self.accessibles.values():
|
||||
aobj.finish()
|
||||
aobj.finish(self)
|
||||
|
||||
# Modify units AFTER applying the cfgdict
|
||||
mainvalue = self.parameters.get('value')
|
||||
@ -502,7 +504,7 @@ class Module(HasAccessibles):
|
||||
err = secop_error(err)
|
||||
if str(err) == str(pobj.readerror):
|
||||
return # no updates for repeated errors
|
||||
elif not changed and timestamp < (pobj.timestamp or 0) + self.omit_unchanged_within:
|
||||
elif not changed and timestamp < (pobj.timestamp or 0) + pobj.omit_unchanged_within:
|
||||
# no change within short time -> omit
|
||||
return
|
||||
pobj.timestamp = timestamp or time.time()
|
||||
|
@ -26,13 +26,14 @@
|
||||
import inspect
|
||||
|
||||
from frappy.datatypes import BoolType, CommandType, DataType, \
|
||||
DataTypeType, EnumType, NoneOr, OrType, \
|
||||
DataTypeType, EnumType, NoneOr, OrType, FloatRange, \
|
||||
StringType, StructOf, TextType, TupleOf, ValueType
|
||||
from frappy.errors import BadValueError, WrongTypeError, ProgrammingError
|
||||
from frappy.properties import HasProperties, Property
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
generalConfig.set_default('tolerate_poll_property', False)
|
||||
generalConfig.set_default('omit_unchanged_within', 0.1)
|
||||
|
||||
|
||||
class Accessible(HasProperties):
|
||||
@ -77,7 +78,7 @@ class Accessible(HasProperties):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def finish(self):
|
||||
def finish(self, modobj=None):
|
||||
"""ensure consistency"""
|
||||
raise NotImplementedError
|
||||
|
||||
@ -160,14 +161,19 @@ class Parameter(Accessible):
|
||||
needscfg = Property(
|
||||
'[internal] needs value in config', NoneOr(BoolType()),
|
||||
export=False, default=False)
|
||||
# optional = Property(
|
||||
# '[internal] is this parameter optional?', BoolType(),
|
||||
# export=False, settable=False, default=False)
|
||||
update_unchanged = Property(
|
||||
'''[internal] updates of unchanged values
|
||||
|
||||
- one of the values 'always', 'never', 'default'
|
||||
or the minimum time between updates of equal values [sec]''',
|
||||
OrType(FloatRange(0), EnumType(always=0, never=999999999, default=-1)),
|
||||
export=False, default=-1)
|
||||
|
||||
# used on the instance copy only
|
||||
# value = None
|
||||
timestamp = 0
|
||||
readerror = None
|
||||
omit_unchanged_within = 0
|
||||
|
||||
def __init__(self, description=None, datatype=None, inherit=True, **kwds):
|
||||
super().__init__()
|
||||
@ -261,8 +267,11 @@ class Parameter(Accessible):
|
||||
self.init(merged_properties)
|
||||
self.finish()
|
||||
|
||||
def finish(self):
|
||||
"""ensure consistency"""
|
||||
def finish(self, modobj=None):
|
||||
"""ensure consistency
|
||||
|
||||
:param modobj: final call, called from Module.__init__
|
||||
"""
|
||||
|
||||
if self.constant is not None:
|
||||
constant = self.datatype(self.constant)
|
||||
@ -279,6 +288,14 @@ class Parameter(Accessible):
|
||||
except BadValueError:
|
||||
# clear, if it does not match datatype
|
||||
pass
|
||||
if modobj:
|
||||
if self.update_unchanged == -1:
|
||||
t = modobj.omit_unchanged_within
|
||||
print(self, t)
|
||||
self.omit_unchanged_within = generalConfig.omit_unchanged_within if t is None else t
|
||||
else:
|
||||
self.omit_unchanged_within = float(self.update_unchanged)
|
||||
print(self, self.omit_unchanged_within, generalConfig.defaults, generalConfig._config)
|
||||
|
||||
def export_value(self):
|
||||
return self.datatype.export_value(self.value)
|
||||
@ -450,7 +467,7 @@ class Command(Accessible):
|
||||
self.init(merged_properties)
|
||||
self.finish()
|
||||
|
||||
def finish(self):
|
||||
def finish(self, modobj=None):
|
||||
"""ensure consistency"""
|
||||
self.datatype = CommandType(self.argument, self.result)
|
||||
|
||||
|
@ -55,17 +55,15 @@ def make_update(modulename, pobj):
|
||||
if pobj.readerror:
|
||||
return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export),
|
||||
# error-report !
|
||||
[pobj.readerror.name, repr(pobj.readerror), dict(t=pobj.timestamp)])
|
||||
[pobj.readerror.name, repr(pobj.readerror), {'t': pobj.timestamp}])
|
||||
return (EVENTREPLY, '%s:%s' % (modulename, pobj.export),
|
||||
[pobj.export_value(), dict(t=pobj.timestamp)])
|
||||
[pobj.export_value(), {'t': pobj.timestamp}])
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, name, logger, options, srv):
|
||||
# to avoid errors, we want to eat all options here
|
||||
self.equipment_id = options.pop('equipment_id', name)
|
||||
# time interval for omitting updates of unchanged values
|
||||
self.omit_unchanged_within = options.pop('omit_unchanged_within', 0.1)
|
||||
self.nodeprops = {}
|
||||
for k in list(options):
|
||||
self.nodeprops[k] = options.pop(k)
|
||||
@ -230,7 +228,7 @@ class Dispatcher:
|
||||
result = cobj.do(moduleobj, argument)
|
||||
if cobj.result:
|
||||
result = cobj.result.export_value(result)
|
||||
return result, dict(t=currenttime())
|
||||
return result, {'t': currenttime()}
|
||||
|
||||
def _setParameterValue(self, modulename, exportedname, value):
|
||||
moduleobj = self.get_module(modulename)
|
||||
@ -253,7 +251,7 @@ class Dispatcher:
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
getattr(moduleobj, 'write_' + pname)(value)
|
||||
# return value is ignored here, as already handled
|
||||
return pobj.export_value(), dict(t=pobj.timestamp) if pobj.timestamp else {}
|
||||
return pobj.export_value(), {'t': pobj.timestamp} if pobj.timestamp else {}
|
||||
|
||||
def _getParameterValue(self, modulename, exportedname):
|
||||
moduleobj = self.get_module(modulename)
|
||||
@ -272,7 +270,7 @@ class Dispatcher:
|
||||
# note: exceptions are handled in handle_request, not here!
|
||||
getattr(moduleobj, 'read_' + pname)()
|
||||
# return value is ignored here, as already handled
|
||||
return pobj.export_value(), dict(t=pobj.timestamp) if pobj.timestamp else {}
|
||||
return pobj.export_value(), {'t': pobj.timestamp} if pobj.timestamp else {}
|
||||
|
||||
#
|
||||
# api to be called from the 'interface'
|
||||
|
@ -58,7 +58,6 @@ Example 2: addressable HW parameters
|
||||
"""
|
||||
|
||||
import functools
|
||||
from frappy.modules import Done
|
||||
from frappy.errors import ProgrammingError
|
||||
|
||||
|
||||
@ -134,8 +133,6 @@ class ReadHandler(Handler):
|
||||
def method(module, pname=key, func=self.func):
|
||||
with module.accessLock:
|
||||
value = func(module, pname)
|
||||
if value is Done:
|
||||
return getattr(module, pname)
|
||||
setattr(module, pname, value)
|
||||
return value
|
||||
|
||||
@ -156,7 +153,7 @@ class CommonReadHandler(ReadHandler):
|
||||
def method(module, pname=key, func=self.func):
|
||||
with module.accessLock:
|
||||
ret = func(module)
|
||||
if ret not in (None, Done):
|
||||
if ret is not None:
|
||||
raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value')
|
||||
return getattr(module, pname)
|
||||
|
||||
@ -174,8 +171,7 @@ class WriteHandler(Handler):
|
||||
def method(module, value, pname=key, func=self.func):
|
||||
with module.accessLock:
|
||||
value = func(module, pname, value)
|
||||
if value is not Done:
|
||||
setattr(module, pname, value)
|
||||
setattr(module, pname, value)
|
||||
return value
|
||||
return method
|
||||
|
||||
@ -217,7 +213,7 @@ class CommonWriteHandler(WriteHandler):
|
||||
values = WriteParameters(module)
|
||||
values[pname] = value
|
||||
ret = func(module, values)
|
||||
if ret not in (None, Done):
|
||||
if ret is not None:
|
||||
raise ProgrammingError('a method wrapped with CommonWriteHandler must not return any value')
|
||||
# remove pname from writeDict. this was not removed in WriteParameters, as it was not missing
|
||||
module.writeDict.pop(pname, None)
|
||||
|
@ -26,7 +26,7 @@ handles status depending on statemachine state
|
||||
"""
|
||||
|
||||
|
||||
from frappy.core import BUSY, IDLE, ERROR, Parameter, Command, Done
|
||||
from frappy.core import BUSY, IDLE, ERROR, Parameter, Command
|
||||
from frappy.lib.statemachine import StateMachine, Finish, Start, Stop, \
|
||||
Retry # pylint: disable=unused-import
|
||||
|
||||
@ -53,7 +53,7 @@ def status_code(code, text=None):
|
||||
|
||||
class HasStates:
|
||||
"""mixin for modules needing a statemachine"""
|
||||
status = Parameter() # make sure this is a parameter
|
||||
status = Parameter(update_unchanged='never')
|
||||
all_status_changes = True # when True, send also updates for status changes within a cycle
|
||||
_state_machine = None
|
||||
_status = IDLE, ''
|
||||
@ -132,10 +132,7 @@ class HasStates:
|
||||
return status
|
||||
|
||||
def read_status(self):
|
||||
sm = self._state_machine
|
||||
if sm.status == self.status:
|
||||
return Done
|
||||
return sm.status
|
||||
return self._state_machine.status
|
||||
|
||||
def cycle_machine(self):
|
||||
sm = self._state_machine
|
||||
|
@ -20,7 +20,7 @@
|
||||
# *****************************************************************************
|
||||
"""Andeen Hagerling capacitance bridge"""
|
||||
|
||||
from frappy.core import Done, FloatRange, HasIO, Parameter, Readable, StringIO, nopoll
|
||||
from frappy.core import FloatRange, HasIO, Parameter, Readable, StringIO, nopoll
|
||||
|
||||
|
||||
class Ah2700IO(StringIO):
|
||||
@ -43,7 +43,7 @@ class Capacitance(HasIO, Readable):
|
||||
reply = self.communicate('SI')
|
||||
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
|
||||
self.status = [self.Status.ERROR, reply]
|
||||
return
|
||||
return self.value
|
||||
self.status = [self.Status.IDLE, '']
|
||||
# examples of replies:
|
||||
# 'F= 1000.0 HZ C= 0.000001 PF L> 0.0 DS V= 15.0 V'
|
||||
@ -55,7 +55,6 @@ class Capacitance(HasIO, Readable):
|
||||
_, freq, _, _, cap, _, _, loss, lossunit, _, volt = reply[:11]
|
||||
self.freq = freq
|
||||
self.voltage = volt
|
||||
self.value = cap
|
||||
if lossunit == 'DS':
|
||||
self.loss = loss
|
||||
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens
|
||||
@ -64,30 +63,30 @@ class Capacitance(HasIO, Readable):
|
||||
self.loss = reply[7]
|
||||
except IndexError:
|
||||
pass # don't worry, loss will be updated next time
|
||||
return cap
|
||||
|
||||
def read_value(self):
|
||||
self.parse_reply(self.communicate('SI')) # SI = single trigger
|
||||
return Done
|
||||
return self.parse_reply(self.communicate('SI')) # SI = single trigger
|
||||
|
||||
@nopoll
|
||||
def read_freq(self):
|
||||
self.read_value()
|
||||
return Done
|
||||
return self.freq
|
||||
|
||||
@nopoll
|
||||
def read_loss(self):
|
||||
self.read_value()
|
||||
return Done
|
||||
return self.loss
|
||||
|
||||
@nopoll
|
||||
def read_voltage(self):
|
||||
self.read_value()
|
||||
return Done
|
||||
return self.voltage
|
||||
|
||||
def write_freq(self, value):
|
||||
self.parse_reply(self.communicate('FR %g;SI' % value))
|
||||
return Done
|
||||
self.value = self.parse_reply(self.communicate('FR %g;SI' % value))
|
||||
return self.freq
|
||||
|
||||
def write_voltage(self, value):
|
||||
self.parse_reply(self.communicate('V %g;SI' % value))
|
||||
return Done
|
||||
self.value = self.parse_reply(self.communicate('V %g;SI' % value))
|
||||
return self.voltage
|
||||
|
@ -39,7 +39,7 @@ switcher=sw
|
||||
import time
|
||||
|
||||
from frappy.datatypes import IntRange, BoolType, FloatRange
|
||||
from frappy.core import Attached, Property, Drivable, Parameter, Readable, Done
|
||||
from frappy.core import Attached, Property, Drivable, Parameter, Readable
|
||||
|
||||
|
||||
class ChannelSwitcher(Drivable):
|
||||
@ -55,6 +55,7 @@ class ChannelSwitcher(Drivable):
|
||||
"""
|
||||
value = Parameter('the current channel number', IntRange(), needscfg=False)
|
||||
target = Parameter('channel to select', IntRange(), needscfg=False)
|
||||
status = Parameter(update_unchanged='never')
|
||||
autoscan = Parameter('whether to scan automatically',
|
||||
BoolType(), readonly=False, default=True)
|
||||
pollinterval = Parameter(default=1, export=False)
|
||||
@ -108,7 +109,7 @@ class ChannelSwitcher(Drivable):
|
||||
if self.status[0] == 'BUSY':
|
||||
chan = self._channels[self.target]
|
||||
if chan.is_switching(now, self._start_switch, self.switch_delay):
|
||||
return Done
|
||||
return self.status
|
||||
self.setFastPoll(False)
|
||||
self.status = 'IDLE', 'measure'
|
||||
self.value = self.target
|
||||
@ -116,7 +117,7 @@ class ChannelSwitcher(Drivable):
|
||||
chan.read_value()
|
||||
chan.read_status()
|
||||
if self.measure_delay > self._time_tol:
|
||||
return Done
|
||||
return self.status
|
||||
else:
|
||||
chan = self._channels[self.value]
|
||||
self.read_value() # this might modify autoscan or deadline!
|
||||
@ -129,7 +130,7 @@ class ChannelSwitcher(Drivable):
|
||||
chan.read_status()
|
||||
self._last_measure = next_measure
|
||||
if not self.autoscan or now + self._time_tol < self._start_measure + self.measure_delay:
|
||||
return Done
|
||||
return self.status
|
||||
next_channel = self.next_channel(self.value)
|
||||
if next_channel == self.value:
|
||||
return 'IDLE', 'single channel'
|
||||
|
@ -26,7 +26,7 @@ import re
|
||||
import time
|
||||
|
||||
from frappy.core import Drivable, HasIO, Writable, \
|
||||
Parameter, Property, Readable, StringIO, Attached, Done, IDLE, nopoll
|
||||
Parameter, Property, Readable, StringIO, Attached, IDLE, nopoll
|
||||
from frappy.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType
|
||||
from frappy.errors import HardwareError
|
||||
from frappy_psi.convergence import HasConvergence
|
||||
@ -70,7 +70,7 @@ class MercuryChannel(HasIO):
|
||||
example: DB6.T1,DB1.H1
|
||||
slot ids for sensor (and control output)''',
|
||||
StringType())
|
||||
channel_name = Parameter('mercury nick name', StringType(), default='')
|
||||
channel_name = Parameter('mercury nick name', StringType(), default='', update_unchanged='never')
|
||||
channel_type = '' #: channel type(s) for sensor (and control) e.g. TEMP,HTR or PRES,AUX
|
||||
|
||||
def _complete_adr(self, adr):
|
||||
@ -158,7 +158,7 @@ class MercuryChannel(HasIO):
|
||||
|
||||
def read_channel_name(self):
|
||||
if self.channel_name:
|
||||
return Done # channel name will not change
|
||||
return self.channel_name # channel name will not change
|
||||
return self.query('0:NICK', as_string)
|
||||
|
||||
|
||||
@ -189,14 +189,14 @@ class HasInput(MercuryChannel):
|
||||
|
||||
def write_controlled_by(self, value):
|
||||
if self.controlled_by == value:
|
||||
return Done
|
||||
return value
|
||||
self.controlled_by = value
|
||||
if value == SELF:
|
||||
self.log.warning('switch to manual mode')
|
||||
for input_module in self.input_modules:
|
||||
if input_module.control_active:
|
||||
input_module.write_control_active(False)
|
||||
return Done
|
||||
return value
|
||||
|
||||
|
||||
class Loop(HasConvergence, MercuryChannel, Drivable):
|
||||
@ -261,7 +261,8 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
|
||||
"""
|
||||
channel_type = 'HTR'
|
||||
value = Parameter('heater output', FloatRange(unit='W'), readonly=False)
|
||||
target = Parameter('heater output', FloatRange(0, 100, unit='$'), readonly=False)
|
||||
status = Parameter(update_unchanged='never')
|
||||
target = Parameter('heater output', FloatRange(0, 100, unit='$'), readonly=False, update_unchanged='never')
|
||||
resistivity = Parameter('heater resistivity', FloatRange(10, 1000, unit='Ohm'),
|
||||
readonly=False)
|
||||
true_power = Parameter('calculate power from measured current', BoolType(), readonly=False, default=True)
|
||||
@ -288,13 +289,10 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
|
||||
if not self.true_power:
|
||||
self._volt_target = math.sqrt(self._last_target * self.resistivity)
|
||||
self.change('HTR:SIG:VOLT', self._volt_target)
|
||||
return Done
|
||||
return self.resistivity
|
||||
|
||||
def read_status(self):
|
||||
status = IDLE, ('true power' if self.true_power else 'fixed resistivity')
|
||||
if self.status != status:
|
||||
return status
|
||||
return Done
|
||||
return IDLE, ('true power' if self.true_power else 'fixed resistivity')
|
||||
|
||||
def read_value(self):
|
||||
if self._last_target is None: # on init
|
||||
@ -317,7 +315,7 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
|
||||
if self.controlled_by != 0 and self.target:
|
||||
return 0
|
||||
if self._last_target is not None:
|
||||
return Done
|
||||
return self.target
|
||||
self._volt_target = self.query('HTR:SIG:VOLT')
|
||||
self.resistivity = max(10, self.query('HTR:RES'))
|
||||
self._last_target = self._volt_target ** 2 / max(10, self.resistivity)
|
||||
@ -389,7 +387,7 @@ class TemperatureLoop(TemperatureSensor, Loop, Drivable):
|
||||
self.set_target(value)
|
||||
else:
|
||||
self.set_target(target)
|
||||
return Done
|
||||
return self.target
|
||||
|
||||
def read_enable_ramp(self):
|
||||
return self.query('TEMP:LOOP:RENA', off_on)
|
||||
@ -472,7 +470,7 @@ class PressureLoop(PressureSensor, Loop, Drivable):
|
||||
def write_target(self, value):
|
||||
target = self.change('PRES:LOOP:PRST', value)
|
||||
self.set_target(target)
|
||||
return Done
|
||||
return self.target
|
||||
|
||||
|
||||
class HeLevel(MercuryChannel, Readable):
|
||||
|
@ -44,7 +44,7 @@ lowspeed = 50 # speed for final closing / reference run
|
||||
"""
|
||||
|
||||
from frappy.core import Drivable, Parameter, EnumType, Attached, FloatRange, \
|
||||
Command, IDLE, BUSY, WARN, ERROR, Done, PersistentParam, PersistentMixin
|
||||
Command, IDLE, BUSY, WARN, ERROR, PersistentParam, PersistentMixin
|
||||
from frappy.errors import HardwareError
|
||||
from frappy_psi.trinamic import Motor
|
||||
from frappy.lib.statemachine import StateMachine, Retry, Stop
|
||||
@ -53,7 +53,7 @@ from frappy.lib.statemachine import StateMachine, Retry, Stop
|
||||
class MotorValve(PersistentMixin, Drivable):
|
||||
motor = Attached(Motor)
|
||||
value = Parameter('current state', EnumType(
|
||||
closed=0, opened=1, undefined=-1), default=-1)
|
||||
closed=0, opened=1, undefined=-1), default=-1, update_unchanged='never')
|
||||
target = Parameter('target state', EnumType(close=0, open=1))
|
||||
turns = Parameter('number of turns to open', FloatRange(), readonly=False, group='settings')
|
||||
speed = Parameter('speed for far moves', FloatRange(), readonly=False, group='settings')
|
||||
@ -75,7 +75,7 @@ class MotorValve(PersistentMixin, Drivable):
|
||||
raise HardwareError('%s: need refrun' % self.status[1])
|
||||
self.target = target
|
||||
self._state.start(self.goto_target, count=3)
|
||||
return Done
|
||||
return self.target
|
||||
|
||||
def goto_target(self, state):
|
||||
self.value = 'undefined'
|
||||
@ -90,7 +90,7 @@ class MotorValve(PersistentMixin, Drivable):
|
||||
if self.status[0] == ERROR:
|
||||
return 'undefined'
|
||||
if self.motor.isBusy():
|
||||
return Done
|
||||
return self.value
|
||||
motpos = self.motor.read_value()
|
||||
if self.motor.read_home():
|
||||
if motpos > 360:
|
||||
|
@ -40,7 +40,7 @@ from frappy.datatypes import BoolType, EnumType, \
|
||||
from frappy.errors import HardwareError
|
||||
from frappy.lib import clamp
|
||||
from frappy.lib.enum import Enum
|
||||
from frappy.modules import Communicator, Done, \
|
||||
from frappy.modules import Communicator, \
|
||||
Drivable, Parameter, Property, Readable
|
||||
from frappy.io import HasIO
|
||||
from frappy.rwhandler import CommonReadHandler, CommonWriteHandler
|
||||
@ -478,16 +478,14 @@ class Temp(PpmsDrivable):
|
||||
def write_approachmode(self, value):
|
||||
if self.isDriving():
|
||||
self._write_params(self.setpoint, self.ramp, value)
|
||||
return Done
|
||||
self.approachmode = value
|
||||
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||
return self.approachmode
|
||||
return value # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||
|
||||
def write_ramp(self, value):
|
||||
if self.isDriving():
|
||||
self._write_params(self.setpoint, value, self.approachmode)
|
||||
return Done
|
||||
self.ramp = value
|
||||
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||
return self.ramp
|
||||
return value # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||
|
||||
def calc_expected(self, target, ramp):
|
||||
self._expected_target_time = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
|
||||
@ -606,7 +604,7 @@ class Field(PpmsDrivable):
|
||||
self._last_change = time.time()
|
||||
self.status = (self.Status.BUSY, 'changed target')
|
||||
self._write_params(target, self.ramp, self.approachmode, self.persistentmode)
|
||||
return Done
|
||||
return self.target
|
||||
|
||||
def write_persistentmode(self, mode):
|
||||
if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode:
|
||||
@ -617,20 +615,18 @@ class Field(PpmsDrivable):
|
||||
self._stopped = False
|
||||
self.status = (self.Status.BUSY, 'changed persistent mode')
|
||||
self._write_params(self.target, self.ramp, self.approachmode, mode)
|
||||
return Done
|
||||
return self.persistentmode
|
||||
|
||||
def write_ramp(self, value):
|
||||
self.ramp = value
|
||||
if self.isDriving():
|
||||
self._write_params(self.target, value, self.approachmode, self.persistentmode)
|
||||
return Done
|
||||
return None # do not execute FIELD command, as this would trigger a ramp up of leads current
|
||||
return self.ramp
|
||||
return value # do not execute FIELD command, as this would trigger a ramp up of leads current
|
||||
|
||||
def write_approachmode(self, value):
|
||||
if self.isDriving():
|
||||
self._write_params(self.target, self.ramp, value, self.persistentmode)
|
||||
return Done
|
||||
return None # do not execute FIELD command, as this would trigger a ramp up of leads current
|
||||
# do not execute FIELD command, as this would trigger a ramp up of leads current
|
||||
|
||||
def stop(self):
|
||||
if not self.isDriving():
|
||||
@ -728,14 +724,13 @@ class Position(PpmsDrivable):
|
||||
self._status_before_change = self.status
|
||||
self.status = (self.Status.BUSY, 'changed target')
|
||||
self._write_params(target, self.speed)
|
||||
return Done
|
||||
return self.target
|
||||
|
||||
def write_speed(self, value):
|
||||
if self.isDriving():
|
||||
self._write_params(self.target, value)
|
||||
return Done
|
||||
self.speed = value
|
||||
return None # do not execute MOVE command, as this would trigger an unnecessary move
|
||||
return self.speed
|
||||
return value # do not execute MOVE command, as this would trigger an unnecessary move
|
||||
|
||||
def stop(self):
|
||||
if not self.isDriving():
|
||||
|
@ -28,7 +28,7 @@ import numpy as np
|
||||
from scipy.interpolate import splev, splrep # pylint: disable=import-error
|
||||
|
||||
from frappy.core import Attached, BoolType, Parameter, Readable, StringType, \
|
||||
FloatRange, Done
|
||||
FloatRange
|
||||
|
||||
|
||||
def linear(x):
|
||||
@ -95,7 +95,7 @@ class Parser340(StdParser):
|
||||
KINDS = {
|
||||
"340": (Parser340, {}), # lakeshore 340 format
|
||||
"inp": (StdParser, {}), # M. Zollikers *.inp calcurve format
|
||||
"caldat": (StdParser, dict(x=1, y=2)), # format from sea/tcl/startup/calib_ext.tcl
|
||||
"caldat": (StdParser, {'x': 1, 'y': 2}), # format from sea/tcl/startup/calib_ext.tcl
|
||||
"dat": (StdParser, {}), # lakeshore raw data *.dat format
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ class Sensor(Readable):
|
||||
abs = Parameter('True: take abs(raw) before calib', datatype=BoolType(), readonly=False, default=True)
|
||||
value = Parameter(datatype=FloatRange(unit='K'))
|
||||
pollinterval = Parameter(export=False)
|
||||
status = Parameter(default=(Readable.Status.ERROR, 'unintialized'))
|
||||
status = Parameter(default=(Readable.Status.ERROR, 'unintialized'), update_unchanged='never')
|
||||
|
||||
description = 'a calibrated sensor value'
|
||||
_value_error = None
|
||||
@ -227,4 +227,4 @@ class Sensor(Readable):
|
||||
|
||||
def read_status(self):
|
||||
self.update_status(self.rawsensor.status)
|
||||
return Done
|
||||
return self.status
|
||||
|
@ -23,7 +23,8 @@
|
||||
|
||||
from frappy.rwhandler import ReadHandler, WriteHandler, \
|
||||
CommonReadHandler, CommonWriteHandler, nopoll
|
||||
from frappy.core import Module, Parameter, FloatRange, Done
|
||||
from frappy.core import Module, Parameter, FloatRange
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
class DispatcherStub:
|
||||
@ -31,9 +32,9 @@ class DispatcherStub:
|
||||
# initial value from the timestamp. However, in the test below
|
||||
# the second update happens after the updates dict is cleared
|
||||
# -> we have to inhibit the 'omit unchanged update' feature
|
||||
omit_unchanged_within = 0
|
||||
|
||||
def __init__(self, updates):
|
||||
generalConfig.testinit(omit_unchanged_within=0)
|
||||
self.updates = updates
|
||||
|
||||
def announce_update(self, modulename, pname, pobj):
|
||||
@ -104,7 +105,7 @@ def test_handler():
|
||||
assert m.b == 7
|
||||
assert data.pop() == 'b'
|
||||
|
||||
data.append(Done)
|
||||
data.append(m.b)
|
||||
assert m.read_b() == 7
|
||||
assert data.pop() == 'b'
|
||||
|
||||
|
@ -31,6 +31,7 @@ from frappy.errors import ProgrammingError, ConfigError
|
||||
from frappy.modules import Communicator, Drivable, Readable, Module
|
||||
from frappy.params import Command, Parameter
|
||||
from frappy.rwhandler import ReadHandler, WriteHandler, nopoll
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
class DispatcherStub:
|
||||
@ -38,9 +39,9 @@ class DispatcherStub:
|
||||
# initial value from the timestamp. However, in the test below
|
||||
# the second update happens after the updates dict is cleared
|
||||
# -> we have to inhibit the 'omit unchanged update' feature
|
||||
omit_unchanged_within = 0
|
||||
|
||||
def __init__(self, updates):
|
||||
generalConfig.testinit(omit_unchanged_within=0)
|
||||
self.updates = updates
|
||||
|
||||
def announce_update(self, modulename, pname, pobj):
|
||||
@ -239,12 +240,12 @@ def test_ModuleMagic():
|
||||
'export', 'group', 'description', 'features',
|
||||
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
|
||||
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2',
|
||||
'cmd2', 'value', 'a1'}
|
||||
'cmd2', 'value', 'a1', 'omit_unchanged_within'}
|
||||
assert set(cfg['value'].keys()) == {
|
||||
'group', 'export', 'relative_resolution',
|
||||
'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr',
|
||||
'absolute_resolution', 'max', 'min', 'readonly', 'constant',
|
||||
'description', 'needscfg'}
|
||||
'description', 'needscfg', 'update_unchanged'}
|
||||
|
||||
# check on the level of classes
|
||||
# this checks Newclass1 too, as it is inherited by Newclass2
|
||||
@ -739,3 +740,46 @@ def test_write_method_returns_none():
|
||||
mod = Mod('mod', LoggerStub(), {'description': ''}, ServerStub({}))
|
||||
mod.write_a(1.5)
|
||||
assert mod.a == 1.5
|
||||
|
||||
|
||||
@pytest.mark.parametrize('arg, value', [
|
||||
('always', 0),
|
||||
(0, 0),
|
||||
('never', 999999999),
|
||||
(999999999, 999999999),
|
||||
('default', 0.25),
|
||||
(1, 1),
|
||||
])
|
||||
def test_update_unchanged_ok(arg, value):
|
||||
srv = ServerStub({})
|
||||
generalConfig.testinit(omit_unchanged_within=0.25) # override value from DispatcherStub
|
||||
|
||||
class Mod1(Module):
|
||||
a = Parameter('', FloatRange(), default=0, update_unchanged=arg)
|
||||
|
||||
mod1 = Mod1('mod1', LoggerStub(), {'description': ''}, srv)
|
||||
par = mod1.parameters['a']
|
||||
assert par.omit_unchanged_within == value
|
||||
assert Mod1.a.omit_unchanged_within == 0
|
||||
|
||||
class Mod2(Module):
|
||||
a = Parameter('', FloatRange(), default=0)
|
||||
|
||||
mod2 = Mod2('mod2', LoggerStub(), {'description': '', 'a': {'update_unchanged': arg}}, srv)
|
||||
par = mod2.parameters['a']
|
||||
assert par.omit_unchanged_within == value
|
||||
assert Mod2.a.omit_unchanged_within == 0
|
||||
|
||||
|
||||
def test_omit_unchanged_within():
|
||||
srv = ServerStub({})
|
||||
generalConfig.testinit(omit_unchanged_within=0.25) # override call from DispatcherStub
|
||||
|
||||
class Mod(Module):
|
||||
a = Parameter('', FloatRange())
|
||||
|
||||
mod1 = Mod('mod1', LoggerStub(), {'description': ''}, srv)
|
||||
assert mod1.parameters['a'].omit_unchanged_within == 0.25
|
||||
|
||||
mod2 = Mod('mod2', LoggerStub(), {'description': '', 'omit_unchanged_within': 0.125}, srv)
|
||||
assert mod2.parameters['a'].omit_unchanged_within == 0.125
|
||||
|
@ -109,3 +109,21 @@ def test_Export():
|
||||
class Mod(HasAccessibles):
|
||||
param = Parameter('description1', datatype=BoolType, default=False)
|
||||
assert Mod.param.export == '_param'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('arg, value', [
|
||||
('always', 0),
|
||||
(0, 0),
|
||||
('never', 999999999),
|
||||
(999999999, 999999999),
|
||||
(1, 1),
|
||||
])
|
||||
def test_update_unchanged_ok(arg, value):
|
||||
par = Parameter('', datatype=FloatRange(), default=0, update_unchanged=arg)
|
||||
assert par.update_unchanged == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize('arg', ['alws', '', -2, -0.1, None])
|
||||
def test_update_unchanged_fail(arg):
|
||||
with pytest.raises(ProgrammingError):
|
||||
Parameter('', datatype=FloatRange(), default=0, update_unchanged=arg)
|
||||
|
@ -31,6 +31,7 @@ import pytest
|
||||
|
||||
from frappy.core import Module, Parameter, FloatRange, Readable, ReadHandler, nopoll
|
||||
from frappy.lib.multievent import MultiEvent
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
class Time:
|
||||
@ -66,13 +67,14 @@ class DispatcherStub:
|
||||
|
||||
class ServerStub:
|
||||
def __init__(self):
|
||||
generalConfig.testinit()
|
||||
self.dispatcher = DispatcherStub()
|
||||
|
||||
|
||||
class Base(Module):
|
||||
def __init__(self):
|
||||
srv = ServerStub()
|
||||
super().__init__('mod', logging.getLogger('dummy'), dict(description=''), srv)
|
||||
super().__init__('mod', logging.getLogger('dummy'), {'description': ''}, srv)
|
||||
self.dispatcher = srv.dispatcher
|
||||
|
||||
def run(self, maxcycles):
|
||||
|
@ -24,6 +24,7 @@
|
||||
from frappy.core import Drivable, Parameter
|
||||
from frappy.datatypes import StatusType, Enum
|
||||
from frappy.states import StateMachine, Stop, Retry, Finish, Start, HasStates, status_code
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
class LoggerStub:
|
||||
@ -189,9 +190,9 @@ class DispatcherStub:
|
||||
# initial value from the timestamp. However, in the test below
|
||||
# the second update happens after the updates dict is cleared
|
||||
# -> we have to inhibit the 'omit unchanged update' feature
|
||||
omit_unchanged_within = 0
|
||||
|
||||
def __init__(self, updates):
|
||||
generalConfig.testinit(omit_unchanged_within=0)
|
||||
self.updates = updates
|
||||
|
||||
def announce_update(self, modulename, pname, pobj):
|
||||
|
Loading…
x
Reference in New Issue
Block a user