fs: improve and fix implementation
+ introduce WrapControlledBy and fix HasControlledBy this in a new module before mercury/triton have been fixed
This commit is contained in:
193
frappy/ctrlby.py
Normal file
193
frappy/ctrlby.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# 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:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
from frappy.datatypes import BoolType, EnumType, Enum
|
||||
from frappy.core import Parameter, Attached
|
||||
|
||||
|
||||
class WrapControlledBy:
|
||||
"""mixin for modules with controlled_by
|
||||
|
||||
Two use cases:
|
||||
|
||||
1) on the implementation of a hardware module it is already known that
|
||||
HasControlledBy is wanted. In this case, functionality to apply the
|
||||
target to the hardware has to be implemented in method 'set_target'
|
||||
|
||||
class MyWritable(HasControlledBy, ...):
|
||||
|
||||
def set_target(self, target):
|
||||
"apply target to HW"
|
||||
# no supercall needed!
|
||||
|
||||
# do not override write_target !
|
||||
|
||||
2) a hardware module is already available, and we extend it with the
|
||||
controlled_by stuff
|
||||
|
||||
class Enhanced(HasControlledBy, BaseWritable):
|
||||
set_target = BaseWritable.write_target
|
||||
|
||||
# nothing else is needed.
|
||||
"""
|
||||
|
||||
controlled_by = Parameter('source of target value', EnumType(members={'self': 0}), default=0)
|
||||
target = Parameter() # make sure target is a parameter
|
||||
inputCallbacks = ()
|
||||
|
||||
def register_input(self, name, deactivate_control):
|
||||
"""register input
|
||||
|
||||
:param name: the name of the module (for controlled_by enum)
|
||||
:param deactivate_control: a method on the input module to switch off control
|
||||
|
||||
called by <controller module>.initModule
|
||||
"""
|
||||
if not self.inputCallbacks:
|
||||
self.inputCallbacks = {}
|
||||
self.inputCallbacks[name] = deactivate_control
|
||||
prev_enum = self.parameters['controlled_by'].datatype.export_datatype()['members']
|
||||
# add enum member, using autoincrement feature of Enum
|
||||
self.parameters['controlled_by'].datatype = EnumType(Enum(prev_enum, **{name: None}))
|
||||
|
||||
def write_controlled_by(self, modulename):
|
||||
result = modulename
|
||||
if modulename in ('self', self.name):
|
||||
# inform the deactivate_control methods, that we have already switched
|
||||
self.controlled_by = result = 'self'
|
||||
for name, deactivate_control in self.inputCallbacks.items():
|
||||
if name != modulename:
|
||||
deactivate_control(modulename)
|
||||
return result
|
||||
|
||||
def self_controlled(self):
|
||||
"""method to change controlled_by to self
|
||||
|
||||
to be called from the write_target method
|
||||
"""
|
||||
if self.controlled_by != 0:
|
||||
self.write_controlled_by('self')
|
||||
|
||||
def set_off(self):
|
||||
"""to be overriden if the off state should be different than the default
|
||||
|
||||
on a FloatRange() the default value is 0
|
||||
"""
|
||||
self.self_controlled()
|
||||
self.set_target_cby(self.parameters['target'].datatype.default)
|
||||
|
||||
def update_target(self, module, value):
|
||||
"""update internal target value
|
||||
|
||||
as write_target would switch to manual mode, the controlling module
|
||||
has to use this method to update the value
|
||||
|
||||
override and super call, if other actions are needed
|
||||
"""
|
||||
if self.controlled_by != module:
|
||||
self.log.warning('UT %r %r', self.controlled_by, module)
|
||||
deactivate_control = self.inputCallbacks.get(self.controlled_by)
|
||||
if deactivate_control:
|
||||
deactivate_control(module)
|
||||
self.controlled_by = module
|
||||
target = self.set_target_cby(value)
|
||||
self.target = value if target is None else target
|
||||
|
||||
def write_target(self, target):
|
||||
self.self_controlled()
|
||||
return self.set_target_cby(target)
|
||||
|
||||
def set_target_cby(self, target):
|
||||
return super().write_target(target)
|
||||
|
||||
|
||||
class HasControlledBy(WrapControlledBy):
|
||||
def set_target(self, value):
|
||||
"""to be overridden for setting target of HW"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_target_cby(self, value):
|
||||
"""to be overridden in case this mixin is not added on top"""
|
||||
return self.set_target(value)
|
||||
|
||||
|
||||
class WrapOutputModule:
|
||||
"""mixin for modules having an output module
|
||||
|
||||
this module will call the update_target method of an output module
|
||||
"""
|
||||
# mandatory=False: it should be possible to configure a module with fixed control
|
||||
output_module = Attached(WrapControlledBy, mandatory=False)
|
||||
control_active = Parameter('control mode', BoolType(), default=False)
|
||||
target = Parameter() # make sure target is a parameter
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
if self.output_module:
|
||||
self.output_module.register_input(self.name, self.deactivate_control)
|
||||
|
||||
def write_control_active(self, value):
|
||||
"""override with supercall if needed"""
|
||||
out = self.output_module
|
||||
if out:
|
||||
if value:
|
||||
if out.controlled_by != self.name:
|
||||
# deactivate control an all modules controlling our output_module
|
||||
out.write_controlled_by(self.name)
|
||||
else:
|
||||
if out.controlled_by == self.name:
|
||||
out.set_off() # this sets out.controlled_by to 0 (=self)
|
||||
|
||||
def set_control_active(self, active):
|
||||
"""to be overridden for switching hw control"""
|
||||
self.control_active = active
|
||||
|
||||
def activate_control(self):
|
||||
"""method to switch control_active on
|
||||
|
||||
to be called from the write_target method, with the target as argument
|
||||
"""
|
||||
self.write_control_active(True)
|
||||
|
||||
def deactivate_control(self, source=None):
|
||||
"""called when another module takes over control
|
||||
|
||||
registered to be called from the controlled module(s)
|
||||
"""
|
||||
if self.control_active:
|
||||
self.write_control_active(False)
|
||||
self.log.warning(f'switched to manual mode by {source or self.name}')
|
||||
|
||||
def write_target(self, target):
|
||||
self.write_control_active(True)
|
||||
return self.set_target_out(target)
|
||||
|
||||
def set_target_out(self, target):
|
||||
return super().write_target(target)
|
||||
|
||||
|
||||
class HasOutputModule(WrapOutputModule):
|
||||
def set_target(self, target):
|
||||
"""to be overridden except for WrapOutputModule"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_target_out(self, target):
|
||||
return self.set_target(target)
|
||||
Reference in New Issue
Block a user