# -*- coding: utf-8 -*- # ***************************************************************************** # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 2 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: # Enrico Faulhaber # Markus Zolliker # # ***************************************************************************** """Define Mixin Features for real Modules implemented in the server""" from secop.datatypes import ArrayOf, BoolType, EnumType, \ FloatRange, StringType, StructOf, TupleOf from secop.core import Command, Done, Drivable, Feature, \ Parameter, Property, PersistentParam, Readable from secop.errors import BadValueError, ConfigError from secop.lib import clamp class HasSimpleOffset(Feature): """has a client side offset parameter this is just a storage! """ offset = PersistentParam('offset (physical value + offset = HW value)', FloatRange(unit='deg'), readonly=False, default=0) class HasTargetLimits(Feature): """user limits implementation to be done in the subclass according to standard """ target_limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99), datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg'))) def check_limits(self, value): """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_)) # --- legacy mixins, not agreed as standard --- class HasOffset(Feature): """has an offset parameter implementation to be done in the subclass """ offset = PersistentParam('offset (physical value + offset = HW value)', FloatRange(unit='$'), 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='$'), FloatRange(unit='$'))) limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99), datatype=TupleOf(FloatRange(unit='$'), FloatRange(unit='$'))) _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) self.parameters['limits'].default = self.abslimits 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) print(self.parameters['limits'], 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_))