feature implementation

implement features including two proposed features
HasOffset and HasLimits

Change-Id: I7949f12dc8abe28fb2ee040e64e7db19d1b23b9a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/28485
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2022-05-20 15:22:17 +02:00
parent 4c94580cb9
commit d717a481d7
4 changed files with 101 additions and 22 deletions

View File

@ -31,7 +31,7 @@ from secop.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
from secop.iohandler import IOHandler, IOHandlerBase from secop.iohandler import IOHandler, IOHandlerBase
from secop.lib.enum import Enum from secop.lib.enum import Enum
from secop.modules import Attached, Communicator, \ from secop.modules import Attached, Communicator, \
Done, Drivable, Module, Readable, Writable, HasAccessibles Done, Drivable, Feature, Module, Readable, Writable, HasAccessibles
from secop.params import Command, Parameter from secop.params import Command, Parameter
from secop.properties import Property from secop.properties import Property
from secop.proxy import Proxy, SecNode, proxy_class from secop.proxy import Proxy, SecNode, proxy_class

View File

@ -17,6 +17,7 @@
# #
# Module authors: # Module authors:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> # Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
"""Define Mixin Features for real Modules implemented in the server""" """Define Mixin Features for real Modules implemented in the server"""
@ -24,12 +25,91 @@
from secop.datatypes import ArrayOf, BoolType, EnumType, \ from secop.datatypes import ArrayOf, BoolType, EnumType, \
FloatRange, StringType, StructOf, TupleOf FloatRange, StringType, StructOf, TupleOf
from secop.modules import Command, HasAccessibles, Parameter from secop.core import Command, Done, Drivable, Feature, \
Parameter, Property, PersistentParam, Readable
from secop.errors import BadValueError, ConfigError
from secop.lib import clamp
class Feature(HasAccessibles): # --- proposals, to be used at SINQ (not agreed as standard yet) ---
"""all things belonging to a small, predefined functionality influencing the working of a module"""
class HasOffset(Feature):
"""has an offset parameter
implementation to be done in the subclass
"""
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 Done
class HasLimits(Feature):
"""user limits
implementation to be done in the subclass
for a drivable, abslimits is roughly the same as the target datatype limits,
except for the offset
"""
abslimits = Property('abs limits (raw values)', default=(-9e99, 9e99), extname='abslimits', export=True,
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99), initwrite=True,
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
_limits = None
def apply_offset(self, sign, *values):
if isinstance(self, HasOffset):
return tuple(v + sign * self.offset for v in values)
return values
def earlyInit(self):
super().earlyInit()
# make limits valid
_limits = self.apply_offset(1, *self.limits)
self._limits = tuple(clamp(self.abslimits[0], v, self.abslimits[1]) for v in _limits)
self.read_limits()
def checkProperties(self):
pname = 'target' if isinstance(self, Drivable) else 'value'
dt = self.parameters[pname].datatype
min_, max_ = self.abslimits
t_min, t_max = self.apply_offset(1, dt.min, dt.max)
if t_min > max_ or t_max < min_:
raise ConfigError('abslimits not within %s range' % pname)
self.abslimits = clamp(t_min, min_, t_max), clamp(t_min, max_, t_max)
super().checkProperties()
def read_limits(self):
return self.apply_offset(-1, *self._limits)
def write_limits(self, value):
min_, max_ = self.apply_offset(-1, *self.abslimits)
if not min_ <= value[0] <= value[1] <= max_:
if value[0] > value[1]:
raise BadValueError('invalid interval: %r' % value)
raise BadValueError('limits not within abs limits [%g, %g]' % (min_, max_))
self.limits = value
self.saveParameters()
return Done
def check_limits(self, value):
"""check if value is valid"""
min_, max_ = self.limits
if not min_ <= value <= max_:
raise BadValueError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
# --- not used, not tested yet ---
class HAS_PID(Feature): class HAS_PID(Feature):
# note: implementors should either use p,i,d or pid, but ECS must be handle both cases # note: implementors should either use p,i,d or pid, but ECS must be handle both cases
@ -56,7 +136,6 @@ class HAS_PID(Feature):
output = Parameter('(optional) output of pid-control', datatype=FloatRange(0), optional=True, readonly=False) output = Parameter('(optional) output of pid-control', datatype=FloatRange(0), optional=True, readonly=False)
class Has_PIDTable(HAS_PID): class Has_PIDTable(HAS_PID):
# parameters # parameters
@ -69,8 +148,6 @@ class Has_PIDTable(HAS_PID):
_base_output=FloatRange(0),),),), optional=True) # struct may include 'heaterrange' _base_output=FloatRange(0),),),), optional=True) # struct may include 'heaterrange'
class HAS_Persistent(Feature): class HAS_Persistent(Feature):
#extra_Status { #extra_Status {
# 'decoupled' : Status.IDLE+1, # to be discussed. # 'decoupled' : Status.IDLE+1, # to be discussed.
@ -85,11 +162,10 @@ class HAS_Persistent(Feature):
default=0, readonly=False) default=0, readonly=False)
is_persistent = Parameter('current state of persistence', is_persistent = Parameter('current state of persistence',
datatype=BoolType(), optional=True) datatype=BoolType(), optional=True)
stored_value = Parameter('current persistence value, often used as the modules value', # stored_value = Parameter('current persistence value, often used as the modules value',
datatype='main', unit='$', optional=True) # datatype='main', unit='$', optional=True)
driven_value = Parameter('driven value (outside value, syncs with stored_value if non-persistent)', # driven_value = Parameter('driven value (outside value, syncs with stored_value if non-persistent)',
datatype='main', unit='$' ) # datatype='main', unit='$' )
class HAS_Tolerance(Feature): class HAS_Tolerance(Feature):
@ -105,7 +181,6 @@ class HAS_Tolerance(Feature):
optional=True) optional=True)
class HAS_Timeout(Feature): class HAS_Timeout(Feature):
# parameters # parameters
@ -113,7 +188,6 @@ class HAS_Timeout(Feature):
datatype=FloatRange(0), default=0, unit='s') datatype=FloatRange(0), default=0, unit='s')
class HAS_Pause(Feature): class HAS_Pause(Feature):
# just a proposal, can't agree on it.... # just a proposal, can't agree on it....
@ -139,7 +213,6 @@ class HAS_Ramp(Feature):
readonly=True, ) readonly=True, )
class HAS_Speed(Feature): class HAS_Speed(Feature):
# parameters # parameters
@ -147,7 +220,6 @@ class HAS_Speed(Feature):
unit='$/s', datatype=FloatRange(0)) unit='$/s', datatype=FloatRange(0))
class HAS_Accel(HAS_Speed): class HAS_Accel(HAS_Speed):
# parameters # parameters
@ -157,7 +229,6 @@ class HAS_Accel(HAS_Speed):
datatype=FloatRange(0), optional=True) datatype=FloatRange(0), optional=True)
class HAS_MotorCurrents(Feature): class HAS_MotorCurrents(Feature):
# parameters # parameters
@ -167,9 +238,8 @@ class HAS_MotorCurrents(Feature):
datatype=FloatRange(0), optional=True) datatype=FloatRange(0), optional=True)
class HAS_Curve(Feature): class HAS_Curve(Feature):
# proposed, not yet agreed upon! # proposed, not yet agreed upon!
# parameters # parameters
curve = Parameter('Calibration curve', datatype=StringType(80), default='<unset>') curve = Parameter('Calibration curve', datatype=StringType(), default='<unset>')

View File

@ -228,6 +228,14 @@ class HasAccessibles(HasProperties):
cls.configurables = res cls.configurables = res
class Feature(HasAccessibles):
"""all things belonging to a small, predefined functionality influencing the working of a module
a mixin with Feature as a direct base class is recognized as a SECoP feature
and reported in the module property 'features'
"""
class PollInfo: class PollInfo:
def __init__(self, pollinterval, trigger_event): def __init__(self, pollinterval, trigger_event):
self.interval = pollinterval self.interval = pollinterval
@ -292,6 +300,7 @@ class Module(HasAccessibles):
extname='implementation') extname='implementation')
interface_classes = Property('offical highest interface-class of the module', ArrayOf(StringType()), interface_classes = Property('offical highest interface-class of the module', ArrayOf(StringType()),
extname='interface_classes') extname='interface_classes')
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)
enablePoll = True enablePoll = True
@ -350,10 +359,10 @@ class Module(HasAccessibles):
# b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')] # b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')]
# list of only the 'highest' secop module class # list of only the 'highest' secop module class
self.interface_classes = [ self.interface_classes = [
b.__name__ for b in mycls.__mro__ if b.__module__.startswith('secop.modules')][0:1] b.__name__ for b in mycls.__mro__ if issubclass(Drivable, b)][0:1]
# handle Features # handle Features
# XXX: todo self.features = [b.__name__ for b in mycls.__mro__ if Feature in b.__bases__]
# handle accessibles # handle accessibles
# 1) make local copies of parameter objects # 1) make local copies of parameter objects

View File

@ -235,7 +235,7 @@ def test_ModuleMagic():
assert o2.parameters['a1'].datatype.unit == 'mm/s' assert o2.parameters['a1'].datatype.unit == 'mm/s'
cfg = Newclass2.configurables cfg = Newclass2.configurables
assert set(cfg.keys()) == { assert set(cfg.keys()) == {
'export', 'group', 'description', 'disable_value_range_check', 'export', 'group', 'description', 'disable_value_range_check', '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'}