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:
zolliker 2023-03-13 17:16:07 +01:00
parent 9cab6670b9
commit 0d265b9752
22 changed files with 188 additions and 119 deletions

View File

@ -31,9 +31,6 @@ def read\_\ *<parameter>*\ (self):
Called on a ``read`` SECoP message and whenever the internal poll mechanism 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 of Frappy tries to get a new value. The return value should be the
retrieved value. 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 This method might also be called internally, in case a fresh value of
the parameter is needed. 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>`` 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 method. Often the easiest implementation is just returning the result of
a call to the ``read_<parameter>`` method. 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 .. admonition:: behind the scenes

View File

@ -11,10 +11,6 @@ imported from the frappy.core module.
Module Base Classes Module Base Classes
................... ...................
.. _done unique:
.. autodata:: frappy.modules.Done
.. autoclass:: frappy.modules.Module .. autoclass:: frappy.modules.Module
:members: earlyInit, initModule, startModule :members: earlyInit, initModule, startModule

View File

@ -36,7 +36,6 @@ class Node(dict):
description, description,
interface=None, interface=None,
cls='frappy.protocol.dispatcher.Dispatcher', cls='frappy.protocol.dispatcher.Dispatcher',
omit_unchanged_within=1.1,
**kwds **kwds
): ):
super().__init__( super().__init__(
@ -44,7 +43,6 @@ class Node(dict):
description=description, description=description,
interface=interface, interface=interface,
cls=cls, cls=cls,
omit_unchanged_within=omit_unchanged_within,
**kwds **kwds
) )

View File

@ -1212,7 +1212,7 @@ class OrType(DataType):
"""accepts any of the given types, takes the first valid""" """accepts any of the given types, takes the first valid"""
for t in self.types: for t in self.types:
try: try:
return t(value) return t.validate(value) # use always strict validation
except Exception: except Exception:
pass pass
raise WrongTypeError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types)))) raise WrongTypeError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types))))

View File

@ -24,7 +24,7 @@
from frappy.datatypes import ArrayOf, BoolType, EnumType, \ from frappy.datatypes import ArrayOf, BoolType, EnumType, \
FloatRange, StringType, StructOf, TupleOf FloatRange, StringType, StructOf, TupleOf
from frappy.core import Command, Done, Drivable, Feature, \ from frappy.core import Command, Drivable, Feature, \
Parameter, Property, PersistentParam, Readable Parameter, Property, PersistentParam, Readable
from frappy.errors import RangeError, ConfigError from frappy.errors import RangeError, ConfigError
from frappy.lib import clamp from frappy.lib import clamp
@ -39,6 +39,17 @@ class HasSimpleOffset(Feature):
offset = PersistentParam('offset (physical value + offset = HW value)', offset = PersistentParam('offset (physical value + offset = HW value)',
FloatRange(unit='deg'), readonly=False, default=0) 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): class HasTargetLimits(Feature):
"""user limits """user limits
@ -83,7 +94,7 @@ class HasTargetLimits(Feature):
raise RangeError('limits not within abs limits [%g, %g]' % (min_, max_)) raise RangeError('limits not within abs limits [%g, %g]' % (min_, max_))
self.limits = value self.limits = value
self.saveParameters() self.saveParameters()
return Done return self.limits
def check_limits(self, value): def check_limits(self, value):
"""check if value is valid""" """check if value is valid"""
@ -123,4 +134,4 @@ class HasLimits(Feature):
"""check if value is valid""" """check if value is valid"""
min_, max_ = self.target_limits min_, max_ = self.target_limits
if not min_ <= value <= max_: 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_))

View File

@ -33,7 +33,7 @@ from frappy.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange,
StringType, TupleOf, ValueType StringType, TupleOf, ValueType
from frappy.errors import CommunicationFailedError, ConfigError, ProgrammingError from frappy.errors import CommunicationFailedError, ConfigError, ProgrammingError
from frappy.modules import Attached, Command, \ from frappy.modules import Attached, Command, \
Communicator, Done, Module, Parameter, Property Communicator, Module, Parameter, Property
from frappy.lib import generalConfig from frappy.lib import generalConfig
generalConfig.set_default('legacy_hasiodev', False) generalConfig.set_default('legacy_hasiodev', False)
@ -110,7 +110,8 @@ class IOBase(Communicator):
""", datatype=StringType()) """, datatype=StringType())
timeout = Parameter('timeout', datatype=FloatRange(0), default=2) timeout = Parameter('timeout', datatype=FloatRange(0), default=2)
wait_before = Parameter('wait time before sending', datatype=FloatRange(), default=0) 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) 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: #: 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 self.is_connected is changed only by self.connectStart or self.closeConnection
""" """
if self.is_connected: if self.is_connected:
return Done # no need for intermediate updates return True
try: try:
self.connectStart() self.connectStart()
if self._last_error: if self._last_error:
self.log.info('connected') self.log.info('connected')
self._last_error = 'connected' self._last_error = 'connected'
self.callCallbacks() self.callCallbacks()
return Done return self.is_connected
except Exception as e: except Exception as e:
if str(e) != self._last_error: if str(e) != self._last_error:
self._last_error = str(e) self._last_error = str(e)
self.log.error(self._last_error) self.log.error(self._last_error)
raise SilentError(repr(e)) from e raise SilentError(repr(e)) from e
return Done return self.is_connected
def write_is_connected(self, value): def write_is_connected(self, value):
"""value = True: connect if not yet done """value = True: connect if not yet done

View File

@ -28,7 +28,8 @@ import threading
from collections import OrderedDict from collections import OrderedDict
from frappy.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \ 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, \ from frappy.errors import BadValueError, CommunicationFailedError, ConfigError, \
ProgrammingError, SECoPError, secop_error ProgrammingError, SECoPError, secop_error
from frappy.lib import formatException, mkthread, UniqueObject 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.log.debug("read_%s failed with %r", pname, e)
self.announceUpdate(pname, None, e) self.announceUpdate(pname, None, e)
raise raise
if value is Done: if value is Done: # TODO: to be removed when all code using Done is updated
return getattr(self, pname) return getattr(self, pname)
setattr(self, pname, value) # important! trigger the setter setattr(self, pname, value) # important! trigger the setter
return value return value
@ -163,7 +164,7 @@ class HasAccessibles(HasProperties):
new_value = pobj.datatype(value) new_value = pobj.datatype(value)
new_value = wfunc(self, new_value) new_value = wfunc(self, new_value)
self.log.debug('write_%s(%r) returned %r', pname, value, 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) return getattr(self, pname)
if new_value is None: if new_value is None:
new_value = value new_value = value
@ -300,6 +301,8 @@ class Module(HasAccessibles):
features = Property('list of features', ArrayOf(StringType()), extname='features') features = Property('list of features', ArrayOf(StringType()), extname='features')
pollinterval = Property('poll interval for parameters handled by doPoll', FloatRange(0.1, 120), default=5) 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) 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 enablePoll = True
# properties, parameters and commands are auto-merged upon subclassing # properties, parameters and commands are auto-merged upon subclassing
@ -315,7 +318,6 @@ class Module(HasAccessibles):
def __init__(self, name, logger, cfgdict, srv): def __init__(self, name, logger, cfgdict, srv):
# remember the dispatcher object (for the async callbacks) # remember the dispatcher object (for the async callbacks)
self.DISPATCHER = srv.dispatcher self.DISPATCHER = srv.dispatcher
self.omit_unchanged_within = getattr(self.DISPATCHER, 'omit_unchanged_within', 0.1)
self.log = logger self.log = logger
self.name = name self.name = name
self.valueCallbacks = {} self.valueCallbacks = {}
@ -443,7 +445,7 @@ class Module(HasAccessibles):
# 5) ensure consistency # 5) ensure consistency
for aobj in self.accessibles.values(): for aobj in self.accessibles.values():
aobj.finish() aobj.finish(self)
# Modify units AFTER applying the cfgdict # Modify units AFTER applying the cfgdict
mainvalue = self.parameters.get('value') mainvalue = self.parameters.get('value')
@ -502,7 +504,7 @@ class Module(HasAccessibles):
err = secop_error(err) err = secop_error(err)
if str(err) == str(pobj.readerror): if str(err) == str(pobj.readerror):
return # no updates for repeated errors 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 # no change within short time -> omit
return return
pobj.timestamp = timestamp or time.time() pobj.timestamp = timestamp or time.time()

View File

@ -26,13 +26,14 @@
import inspect import inspect
from frappy.datatypes import BoolType, CommandType, DataType, \ from frappy.datatypes import BoolType, CommandType, DataType, \
DataTypeType, EnumType, NoneOr, OrType, \ DataTypeType, EnumType, NoneOr, OrType, FloatRange, \
StringType, StructOf, TextType, TupleOf, ValueType StringType, StructOf, TextType, TupleOf, ValueType
from frappy.errors import BadValueError, WrongTypeError, ProgrammingError from frappy.errors import BadValueError, WrongTypeError, ProgrammingError
from frappy.properties import HasProperties, Property from frappy.properties import HasProperties, Property
from frappy.lib import generalConfig from frappy.lib import generalConfig
generalConfig.set_default('tolerate_poll_property', False) generalConfig.set_default('tolerate_poll_property', False)
generalConfig.set_default('omit_unchanged_within', 0.1)
class Accessible(HasProperties): class Accessible(HasProperties):
@ -77,7 +78,7 @@ class Accessible(HasProperties):
""" """
raise NotImplementedError raise NotImplementedError
def finish(self): def finish(self, modobj=None):
"""ensure consistency""" """ensure consistency"""
raise NotImplementedError raise NotImplementedError
@ -160,14 +161,19 @@ class Parameter(Accessible):
needscfg = Property( needscfg = Property(
'[internal] needs value in config', NoneOr(BoolType()), '[internal] needs value in config', NoneOr(BoolType()),
export=False, default=False) export=False, default=False)
# optional = Property( update_unchanged = Property(
# '[internal] is this parameter optional?', BoolType(), '''[internal] updates of unchanged values
# export=False, settable=False, default=False)
- 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 # used on the instance copy only
# value = None # value = None
timestamp = 0 timestamp = 0
readerror = None readerror = None
omit_unchanged_within = 0
def __init__(self, description=None, datatype=None, inherit=True, **kwds): def __init__(self, description=None, datatype=None, inherit=True, **kwds):
super().__init__() super().__init__()
@ -261,8 +267,11 @@ class Parameter(Accessible):
self.init(merged_properties) self.init(merged_properties)
self.finish() self.finish()
def finish(self): def finish(self, modobj=None):
"""ensure consistency""" """ensure consistency
:param modobj: final call, called from Module.__init__
"""
if self.constant is not None: if self.constant is not None:
constant = self.datatype(self.constant) constant = self.datatype(self.constant)
@ -279,6 +288,14 @@ class Parameter(Accessible):
except BadValueError: except BadValueError:
# clear, if it does not match datatype # clear, if it does not match datatype
pass 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): def export_value(self):
return self.datatype.export_value(self.value) return self.datatype.export_value(self.value)
@ -450,7 +467,7 @@ class Command(Accessible):
self.init(merged_properties) self.init(merged_properties)
self.finish() self.finish()
def finish(self): def finish(self, modobj=None):
"""ensure consistency""" """ensure consistency"""
self.datatype = CommandType(self.argument, self.result) self.datatype = CommandType(self.argument, self.result)

View File

@ -55,17 +55,15 @@ def make_update(modulename, pobj):
if pobj.readerror: if pobj.readerror:
return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export), return (ERRORPREFIX + EVENTREPLY, '%s:%s' % (modulename, pobj.export),
# error-report ! # 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), return (EVENTREPLY, '%s:%s' % (modulename, pobj.export),
[pobj.export_value(), dict(t=pobj.timestamp)]) [pobj.export_value(), {'t': pobj.timestamp}])
class Dispatcher: class Dispatcher:
def __init__(self, name, logger, options, srv): def __init__(self, name, logger, options, srv):
# to avoid errors, we want to eat all options here # to avoid errors, we want to eat all options here
self.equipment_id = options.pop('equipment_id', name) 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 = {} self.nodeprops = {}
for k in list(options): for k in list(options):
self.nodeprops[k] = options.pop(k) self.nodeprops[k] = options.pop(k)
@ -230,7 +228,7 @@ class Dispatcher:
result = cobj.do(moduleobj, argument) result = cobj.do(moduleobj, argument)
if cobj.result: if cobj.result:
result = cobj.result.export_value(result) result = cobj.result.export_value(result)
return result, dict(t=currenttime()) return result, {'t': currenttime()}
def _setParameterValue(self, modulename, exportedname, value): def _setParameterValue(self, modulename, exportedname, value):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
@ -253,7 +251,7 @@ class Dispatcher:
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
getattr(moduleobj, 'write_' + pname)(value) getattr(moduleobj, 'write_' + pname)(value)
# return value is ignored here, as already handled # 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): def _getParameterValue(self, modulename, exportedname):
moduleobj = self.get_module(modulename) moduleobj = self.get_module(modulename)
@ -272,7 +270,7 @@ class Dispatcher:
# note: exceptions are handled in handle_request, not here! # note: exceptions are handled in handle_request, not here!
getattr(moduleobj, 'read_' + pname)() getattr(moduleobj, 'read_' + pname)()
# return value is ignored here, as already handled # 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' # api to be called from the 'interface'

View File

@ -58,7 +58,6 @@ Example 2: addressable HW parameters
""" """
import functools import functools
from frappy.modules import Done
from frappy.errors import ProgrammingError from frappy.errors import ProgrammingError
@ -134,8 +133,6 @@ class ReadHandler(Handler):
def method(module, pname=key, func=self.func): def method(module, pname=key, func=self.func):
with module.accessLock: with module.accessLock:
value = func(module, pname) value = func(module, pname)
if value is Done:
return getattr(module, pname)
setattr(module, pname, value) setattr(module, pname, value)
return value return value
@ -156,7 +153,7 @@ class CommonReadHandler(ReadHandler):
def method(module, pname=key, func=self.func): def method(module, pname=key, func=self.func):
with module.accessLock: with module.accessLock:
ret = func(module) 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') raise ProgrammingError('a method wrapped with CommonReadHandler must not return any value')
return getattr(module, pname) return getattr(module, pname)
@ -174,8 +171,7 @@ class WriteHandler(Handler):
def method(module, value, pname=key, func=self.func): def method(module, value, pname=key, func=self.func):
with module.accessLock: with module.accessLock:
value = func(module, pname, value) value = func(module, pname, value)
if value is not Done: setattr(module, pname, value)
setattr(module, pname, value)
return value return value
return method return method
@ -217,7 +213,7 @@ class CommonWriteHandler(WriteHandler):
values = WriteParameters(module) values = WriteParameters(module)
values[pname] = value values[pname] = value
ret = func(module, values) 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') 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 # remove pname from writeDict. this was not removed in WriteParameters, as it was not missing
module.writeDict.pop(pname, None) module.writeDict.pop(pname, None)

View File

@ -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, \ from frappy.lib.statemachine import StateMachine, Finish, Start, Stop, \
Retry # pylint: disable=unused-import Retry # pylint: disable=unused-import
@ -53,7 +53,7 @@ def status_code(code, text=None):
class HasStates: class HasStates:
"""mixin for modules needing a statemachine""" """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 all_status_changes = True # when True, send also updates for status changes within a cycle
_state_machine = None _state_machine = None
_status = IDLE, '' _status = IDLE, ''
@ -132,10 +132,7 @@ class HasStates:
return status return status
def read_status(self): def read_status(self):
sm = self._state_machine return self._state_machine.status
if sm.status == self.status:
return Done
return sm.status
def cycle_machine(self): def cycle_machine(self):
sm = self._state_machine sm = self._state_machine

View File

@ -20,7 +20,7 @@
# ***************************************************************************** # *****************************************************************************
"""Andeen Hagerling capacitance bridge""" """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): class Ah2700IO(StringIO):
@ -43,7 +43,7 @@ class Capacitance(HasIO, Readable):
reply = self.communicate('SI') reply = self.communicate('SI')
if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH" if not reply.startswith('F='): # this is probably an error message like "LOSS TOO HIGH"
self.status = [self.Status.ERROR, reply] self.status = [self.Status.ERROR, reply]
return return self.value
self.status = [self.Status.IDLE, ''] self.status = [self.Status.IDLE, '']
# examples of replies: # examples of replies:
# 'F= 1000.0 HZ C= 0.000001 PF L> 0.0 DS V= 15.0 V' # '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] _, freq, _, _, cap, _, _, loss, lossunit, _, volt = reply[:11]
self.freq = freq self.freq = freq
self.voltage = volt self.voltage = volt
self.value = cap
if lossunit == 'DS': if lossunit == 'DS':
self.loss = loss self.loss = loss
else: # the unit was wrong, we want DS = tan(delta), not NS = nanoSiemens 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] self.loss = reply[7]
except IndexError: except IndexError:
pass # don't worry, loss will be updated next time pass # don't worry, loss will be updated next time
return cap
def read_value(self): def read_value(self):
self.parse_reply(self.communicate('SI')) # SI = single trigger return self.parse_reply(self.communicate('SI')) # SI = single trigger
return Done
@nopoll @nopoll
def read_freq(self): def read_freq(self):
self.read_value() self.read_value()
return Done return self.freq
@nopoll @nopoll
def read_loss(self): def read_loss(self):
self.read_value() self.read_value()
return Done return self.loss
@nopoll @nopoll
def read_voltage(self): def read_voltage(self):
self.read_value() self.read_value()
return Done return self.voltage
def write_freq(self, value): def write_freq(self, value):
self.parse_reply(self.communicate('FR %g;SI' % value)) self.value = self.parse_reply(self.communicate('FR %g;SI' % value))
return Done return self.freq
def write_voltage(self, value): def write_voltage(self, value):
self.parse_reply(self.communicate('V %g;SI' % value)) self.value = self.parse_reply(self.communicate('V %g;SI' % value))
return Done return self.voltage

View File

@ -39,7 +39,7 @@ switcher=sw
import time import time
from frappy.datatypes import IntRange, BoolType, FloatRange 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): class ChannelSwitcher(Drivable):
@ -55,6 +55,7 @@ class ChannelSwitcher(Drivable):
""" """
value = Parameter('the current channel number', IntRange(), needscfg=False) value = Parameter('the current channel number', IntRange(), needscfg=False)
target = Parameter('channel to select', IntRange(), needscfg=False) target = Parameter('channel to select', IntRange(), needscfg=False)
status = Parameter(update_unchanged='never')
autoscan = Parameter('whether to scan automatically', autoscan = Parameter('whether to scan automatically',
BoolType(), readonly=False, default=True) BoolType(), readonly=False, default=True)
pollinterval = Parameter(default=1, export=False) pollinterval = Parameter(default=1, export=False)
@ -108,7 +109,7 @@ class ChannelSwitcher(Drivable):
if self.status[0] == 'BUSY': if self.status[0] == 'BUSY':
chan = self._channels[self.target] chan = self._channels[self.target]
if chan.is_switching(now, self._start_switch, self.switch_delay): if chan.is_switching(now, self._start_switch, self.switch_delay):
return Done return self.status
self.setFastPoll(False) self.setFastPoll(False)
self.status = 'IDLE', 'measure' self.status = 'IDLE', 'measure'
self.value = self.target self.value = self.target
@ -116,7 +117,7 @@ class ChannelSwitcher(Drivable):
chan.read_value() chan.read_value()
chan.read_status() chan.read_status()
if self.measure_delay > self._time_tol: if self.measure_delay > self._time_tol:
return Done return self.status
else: else:
chan = self._channels[self.value] chan = self._channels[self.value]
self.read_value() # this might modify autoscan or deadline! self.read_value() # this might modify autoscan or deadline!
@ -129,7 +130,7 @@ class ChannelSwitcher(Drivable):
chan.read_status() chan.read_status()
self._last_measure = next_measure self._last_measure = next_measure
if not self.autoscan or now + self._time_tol < self._start_measure + self.measure_delay: 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) next_channel = self.next_channel(self.value)
if next_channel == self.value: if next_channel == self.value:
return 'IDLE', 'single channel' return 'IDLE', 'single channel'

View File

@ -26,7 +26,7 @@ import re
import time import time
from frappy.core import Drivable, HasIO, Writable, \ 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.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType
from frappy.errors import HardwareError from frappy.errors import HardwareError
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
@ -70,7 +70,7 @@ class MercuryChannel(HasIO):
example: DB6.T1,DB1.H1 example: DB6.T1,DB1.H1
slot ids for sensor (and control output)''', slot ids for sensor (and control output)''',
StringType()) 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 channel_type = '' #: channel type(s) for sensor (and control) e.g. TEMP,HTR or PRES,AUX
def _complete_adr(self, adr): def _complete_adr(self, adr):
@ -158,7 +158,7 @@ class MercuryChannel(HasIO):
def read_channel_name(self): def read_channel_name(self):
if self.channel_name: 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) return self.query('0:NICK', as_string)
@ -189,14 +189,14 @@ class HasInput(MercuryChannel):
def write_controlled_by(self, value): def write_controlled_by(self, value):
if self.controlled_by == value: if self.controlled_by == value:
return Done return value
self.controlled_by = value self.controlled_by = value
if value == SELF: if value == SELF:
self.log.warning('switch to manual mode') self.log.warning('switch to manual mode')
for input_module in self.input_modules: for input_module in self.input_modules:
if input_module.control_active: if input_module.control_active:
input_module.write_control_active(False) input_module.write_control_active(False)
return Done return value
class Loop(HasConvergence, MercuryChannel, Drivable): class Loop(HasConvergence, MercuryChannel, Drivable):
@ -261,7 +261,8 @@ class HeaterOutput(HasInput, MercuryChannel, Writable):
""" """
channel_type = 'HTR' channel_type = 'HTR'
value = Parameter('heater output', FloatRange(unit='W'), readonly=False) 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'), resistivity = Parameter('heater resistivity', FloatRange(10, 1000, unit='Ohm'),
readonly=False) readonly=False)
true_power = Parameter('calculate power from measured current', BoolType(), readonly=False, default=True) 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: if not self.true_power:
self._volt_target = math.sqrt(self._last_target * self.resistivity) self._volt_target = math.sqrt(self._last_target * self.resistivity)
self.change('HTR:SIG:VOLT', self._volt_target) self.change('HTR:SIG:VOLT', self._volt_target)
return Done return self.resistivity
def read_status(self): def read_status(self):
status = IDLE, ('true power' if self.true_power else 'fixed resistivity') return IDLE, ('true power' if self.true_power else 'fixed resistivity')
if self.status != status:
return status
return Done
def read_value(self): def read_value(self):
if self._last_target is None: # on init 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: if self.controlled_by != 0 and self.target:
return 0 return 0
if self._last_target is not None: if self._last_target is not None:
return Done return self.target
self._volt_target = self.query('HTR:SIG:VOLT') self._volt_target = self.query('HTR:SIG:VOLT')
self.resistivity = max(10, self.query('HTR:RES')) self.resistivity = max(10, self.query('HTR:RES'))
self._last_target = self._volt_target ** 2 / max(10, self.resistivity) self._last_target = self._volt_target ** 2 / max(10, self.resistivity)
@ -389,7 +387,7 @@ class TemperatureLoop(TemperatureSensor, Loop, Drivable):
self.set_target(value) self.set_target(value)
else: else:
self.set_target(target) self.set_target(target)
return Done return self.target
def read_enable_ramp(self): def read_enable_ramp(self):
return self.query('TEMP:LOOP:RENA', off_on) return self.query('TEMP:LOOP:RENA', off_on)
@ -472,7 +470,7 @@ class PressureLoop(PressureSensor, Loop, Drivable):
def write_target(self, value): def write_target(self, value):
target = self.change('PRES:LOOP:PRST', value) target = self.change('PRES:LOOP:PRST', value)
self.set_target(target) self.set_target(target)
return Done return self.target
class HeLevel(MercuryChannel, Readable): class HeLevel(MercuryChannel, Readable):

View File

@ -44,7 +44,7 @@ lowspeed = 50 # speed for final closing / reference run
""" """
from frappy.core import Drivable, Parameter, EnumType, Attached, FloatRange, \ 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.errors import HardwareError
from frappy_psi.trinamic import Motor from frappy_psi.trinamic import Motor
from frappy.lib.statemachine import StateMachine, Retry, Stop from frappy.lib.statemachine import StateMachine, Retry, Stop
@ -53,7 +53,7 @@ from frappy.lib.statemachine import StateMachine, Retry, Stop
class MotorValve(PersistentMixin, Drivable): class MotorValve(PersistentMixin, Drivable):
motor = Attached(Motor) motor = Attached(Motor)
value = Parameter('current state', EnumType( 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)) target = Parameter('target state', EnumType(close=0, open=1))
turns = Parameter('number of turns to open', FloatRange(), readonly=False, group='settings') turns = Parameter('number of turns to open', FloatRange(), readonly=False, group='settings')
speed = Parameter('speed for far moves', 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]) raise HardwareError('%s: need refrun' % self.status[1])
self.target = target self.target = target
self._state.start(self.goto_target, count=3) self._state.start(self.goto_target, count=3)
return Done return self.target
def goto_target(self, state): def goto_target(self, state):
self.value = 'undefined' self.value = 'undefined'
@ -90,7 +90,7 @@ class MotorValve(PersistentMixin, Drivable):
if self.status[0] == ERROR: if self.status[0] == ERROR:
return 'undefined' return 'undefined'
if self.motor.isBusy(): if self.motor.isBusy():
return Done return self.value
motpos = self.motor.read_value() motpos = self.motor.read_value()
if self.motor.read_home(): if self.motor.read_home():
if motpos > 360: if motpos > 360:

View File

@ -40,7 +40,7 @@ from frappy.datatypes import BoolType, EnumType, \
from frappy.errors import HardwareError from frappy.errors import HardwareError
from frappy.lib import clamp from frappy.lib import clamp
from frappy.lib.enum import Enum from frappy.lib.enum import Enum
from frappy.modules import Communicator, Done, \ from frappy.modules import Communicator, \
Drivable, Parameter, Property, Readable Drivable, Parameter, Property, Readable
from frappy.io import HasIO from frappy.io import HasIO
from frappy.rwhandler import CommonReadHandler, CommonWriteHandler from frappy.rwhandler import CommonReadHandler, CommonWriteHandler
@ -478,16 +478,14 @@ class Temp(PpmsDrivable):
def write_approachmode(self, value): def write_approachmode(self, value):
if self.isDriving(): if self.isDriving():
self._write_params(self.setpoint, self.ramp, value) self._write_params(self.setpoint, self.ramp, value)
return Done return self.approachmode
self.approachmode = value return value # do not execute TEMP command, as this would trigger an unnecessary T change
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
def write_ramp(self, value): def write_ramp(self, value):
if self.isDriving(): if self.isDriving():
self._write_params(self.setpoint, value, self.approachmode) self._write_params(self.setpoint, value, self.approachmode)
return Done return self.ramp
self.ramp = value return value # do not execute TEMP command, as this would trigger an unnecessary T change
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
def calc_expected(self, target, ramp): def calc_expected(self, target, ramp):
self._expected_target_time = time.time() + abs(target - self.value) * 60.0 / max(0.1, 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._last_change = time.time()
self.status = (self.Status.BUSY, 'changed target') self.status = (self.Status.BUSY, 'changed target')
self._write_params(target, self.ramp, self.approachmode, self.persistentmode) self._write_params(target, self.ramp, self.approachmode, self.persistentmode)
return Done return self.target
def write_persistentmode(self, mode): def write_persistentmode(self, mode):
if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode: if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode:
@ -617,20 +615,18 @@ class Field(PpmsDrivable):
self._stopped = False self._stopped = False
self.status = (self.Status.BUSY, 'changed persistent mode') self.status = (self.Status.BUSY, 'changed persistent mode')
self._write_params(self.target, self.ramp, self.approachmode, mode) self._write_params(self.target, self.ramp, self.approachmode, mode)
return Done return self.persistentmode
def write_ramp(self, value): def write_ramp(self, value):
self.ramp = value
if self.isDriving(): if self.isDriving():
self._write_params(self.target, value, self.approachmode, self.persistentmode) self._write_params(self.target, value, self.approachmode, self.persistentmode)
return Done return self.ramp
return None # do not execute FIELD command, as this would trigger a ramp up of leads current return value # do not execute FIELD command, as this would trigger a ramp up of leads current
def write_approachmode(self, value): def write_approachmode(self, value):
if self.isDriving(): if self.isDriving():
self._write_params(self.target, self.ramp, value, self.persistentmode) self._write_params(self.target, self.ramp, value, self.persistentmode)
return Done # do not execute FIELD command, as this would trigger a ramp up of leads current
return None # do not execute FIELD command, as this would trigger a ramp up of leads current
def stop(self): def stop(self):
if not self.isDriving(): if not self.isDriving():
@ -728,14 +724,13 @@ class Position(PpmsDrivable):
self._status_before_change = self.status self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target') self.status = (self.Status.BUSY, 'changed target')
self._write_params(target, self.speed) self._write_params(target, self.speed)
return Done return self.target
def write_speed(self, value): def write_speed(self, value):
if self.isDriving(): if self.isDriving():
self._write_params(self.target, value) self._write_params(self.target, value)
return Done return self.speed
self.speed = value return value # do not execute MOVE command, as this would trigger an unnecessary move
return None # do not execute MOVE command, as this would trigger an unnecessary move
def stop(self): def stop(self):
if not self.isDriving(): if not self.isDriving():

View File

@ -28,7 +28,7 @@ import numpy as np
from scipy.interpolate import splev, splrep # pylint: disable=import-error from scipy.interpolate import splev, splrep # pylint: disable=import-error
from frappy.core import Attached, BoolType, Parameter, Readable, StringType, \ from frappy.core import Attached, BoolType, Parameter, Readable, StringType, \
FloatRange, Done FloatRange
def linear(x): def linear(x):
@ -95,7 +95,7 @@ class Parser340(StdParser):
KINDS = { KINDS = {
"340": (Parser340, {}), # lakeshore 340 format "340": (Parser340, {}), # lakeshore 340 format
"inp": (StdParser, {}), # M. Zollikers *.inp calcurve 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 "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) abs = Parameter('True: take abs(raw) before calib', datatype=BoolType(), readonly=False, default=True)
value = Parameter(datatype=FloatRange(unit='K')) value = Parameter(datatype=FloatRange(unit='K'))
pollinterval = Parameter(export=False) 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' description = 'a calibrated sensor value'
_value_error = None _value_error = None
@ -227,4 +227,4 @@ class Sensor(Readable):
def read_status(self): def read_status(self):
self.update_status(self.rawsensor.status) self.update_status(self.rawsensor.status)
return Done return self.status

View File

@ -23,7 +23,8 @@
from frappy.rwhandler import ReadHandler, WriteHandler, \ from frappy.rwhandler import ReadHandler, WriteHandler, \
CommonReadHandler, CommonWriteHandler, nopoll 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: class DispatcherStub:
@ -31,9 +32,9 @@ class DispatcherStub:
# initial value from the timestamp. However, in the test below # initial value from the timestamp. However, in the test below
# the second update happens after the updates dict is cleared # the second update happens after the updates dict is cleared
# -> we have to inhibit the 'omit unchanged update' feature # -> we have to inhibit the 'omit unchanged update' feature
omit_unchanged_within = 0
def __init__(self, updates): def __init__(self, updates):
generalConfig.testinit(omit_unchanged_within=0)
self.updates = updates self.updates = updates
def announce_update(self, modulename, pname, pobj): def announce_update(self, modulename, pname, pobj):
@ -104,7 +105,7 @@ def test_handler():
assert m.b == 7 assert m.b == 7
assert data.pop() == 'b' assert data.pop() == 'b'
data.append(Done) data.append(m.b)
assert m.read_b() == 7 assert m.read_b() == 7
assert data.pop() == 'b' assert data.pop() == 'b'

View File

@ -31,6 +31,7 @@ from frappy.errors import ProgrammingError, ConfigError
from frappy.modules import Communicator, Drivable, Readable, Module from frappy.modules import Communicator, Drivable, Readable, Module
from frappy.params import Command, Parameter from frappy.params import Command, Parameter
from frappy.rwhandler import ReadHandler, WriteHandler, nopoll from frappy.rwhandler import ReadHandler, WriteHandler, nopoll
from frappy.lib import generalConfig
class DispatcherStub: class DispatcherStub:
@ -38,9 +39,9 @@ class DispatcherStub:
# initial value from the timestamp. However, in the test below # initial value from the timestamp. However, in the test below
# the second update happens after the updates dict is cleared # the second update happens after the updates dict is cleared
# -> we have to inhibit the 'omit unchanged update' feature # -> we have to inhibit the 'omit unchanged update' feature
omit_unchanged_within = 0
def __init__(self, updates): def __init__(self, updates):
generalConfig.testinit(omit_unchanged_within=0)
self.updates = updates self.updates = updates
def announce_update(self, modulename, pname, pobj): def announce_update(self, modulename, pname, pobj):
@ -239,12 +240,12 @@ def test_ModuleMagic():
'export', 'group', 'description', 'features', 'export', 'group', 'description', 'features',
'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop', 'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop',
'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2', 'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2',
'cmd2', 'value', 'a1'} 'cmd2', 'value', 'a1', 'omit_unchanged_within'}
assert set(cfg['value'].keys()) == { assert set(cfg['value'].keys()) == {
'group', 'export', 'relative_resolution', 'group', 'export', 'relative_resolution',
'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr', 'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr',
'absolute_resolution', 'max', 'min', 'readonly', 'constant', 'absolute_resolution', 'max', 'min', 'readonly', 'constant',
'description', 'needscfg'} 'description', 'needscfg', 'update_unchanged'}
# check on the level of classes # check on the level of classes
# this checks Newclass1 too, as it is inherited by Newclass2 # 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 = Mod('mod', LoggerStub(), {'description': ''}, ServerStub({}))
mod.write_a(1.5) mod.write_a(1.5)
assert mod.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

View File

@ -109,3 +109,21 @@ def test_Export():
class Mod(HasAccessibles): class Mod(HasAccessibles):
param = Parameter('description1', datatype=BoolType, default=False) param = Parameter('description1', datatype=BoolType, default=False)
assert Mod.param.export == '_param' 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)

View File

@ -31,6 +31,7 @@ import pytest
from frappy.core import Module, Parameter, FloatRange, Readable, ReadHandler, nopoll from frappy.core import Module, Parameter, FloatRange, Readable, ReadHandler, nopoll
from frappy.lib.multievent import MultiEvent from frappy.lib.multievent import MultiEvent
from frappy.lib import generalConfig
class Time: class Time:
@ -66,13 +67,14 @@ class DispatcherStub:
class ServerStub: class ServerStub:
def __init__(self): def __init__(self):
generalConfig.testinit()
self.dispatcher = DispatcherStub() self.dispatcher = DispatcherStub()
class Base(Module): class Base(Module):
def __init__(self): def __init__(self):
srv = ServerStub() srv = ServerStub()
super().__init__('mod', logging.getLogger('dummy'), dict(description=''), srv) super().__init__('mod', logging.getLogger('dummy'), {'description': ''}, srv)
self.dispatcher = srv.dispatcher self.dispatcher = srv.dispatcher
def run(self, maxcycles): def run(self, maxcycles):

View File

@ -24,6 +24,7 @@
from frappy.core import Drivable, Parameter from frappy.core import Drivable, Parameter
from frappy.datatypes import StatusType, Enum from frappy.datatypes import StatusType, Enum
from frappy.states import StateMachine, Stop, Retry, Finish, Start, HasStates, status_code from frappy.states import StateMachine, Stop, Retry, Finish, Start, HasStates, status_code
from frappy.lib import generalConfig
class LoggerStub: class LoggerStub:
@ -189,9 +190,9 @@ class DispatcherStub:
# initial value from the timestamp. However, in the test below # initial value from the timestamp. However, in the test below
# the second update happens after the updates dict is cleared # the second update happens after the updates dict is cleared
# -> we have to inhibit the 'omit unchanged update' feature # -> we have to inhibit the 'omit unchanged update' feature
omit_unchanged_within = 0
def __init__(self, updates): def __init__(self, updates):
generalConfig.testinit(omit_unchanged_within=0)
self.updates = updates self.updates = updates
def announce_update(self, modulename, pname, pobj): def announce_update(self, modulename, pname, pobj):