# ***************************************************************************** # # 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 # # ***************************************************************************** """modules to access parameters""" from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \ Parameter, FloatRange, BoolType, Readable, ERROR from frappy.errors import ConfigError from frappy_psi.convergence import HasConvergence from frappy_psi.mixins import HasRamp from frappy.lib import merge_status class Par(Readable): value = Parameter(datatype=FloatRange(unit='$')) read = Attached(description='. for read') unit = Property('main unit', StringType()) def setProperty(self, key, value): if key == 'read': value, param = value.split('.') setattr(self, f'{key}_param', param) super().setProperty(key, value) def checkProperties(self): self.applyMainUnit(self.unit) if self.read == self.name : raise ConfigError('illegal recursive read/write module') super().checkProperties() def read_value(self): return getattr(self.read, f'{self.read_param}') def read_status(self): return IDLE, '' class Driv(Drivable): value = Parameter(datatype=FloatRange(unit='$')) target = Parameter(datatype=FloatRange(unit='$')) read = Attached(description='. for read') write = Attached(description='. for read') unit = Property('main unit', StringType()) def setProperty(self, key, value): if key in ('read', 'write'): value, param = value.split('.') setattr(self, f'{key}_param', param) super().setProperty(key, value) def checkProperties(self): self.applyMainUnit(self.unit) if self.read == self.name or self.write == self.name: raise ConfigError('illegal recursive read/write module') super().checkProperties() #def registerUpdates(self): # self.read.valueCallbacks[self.read_param].append(self.update_value) # self.write.valueCallbacks[self.write_param].append(self.update_target) # #def startModule(self, start_events): # start_events.queue(self.registerUpdates) # super().startModule(start_events) def read_value(self): return getattr(self.read, f'{self.read_param}') def read_target(self): return getattr(self.write, f'{self.write_param}') def read_status(self): return IDLE, '' def write_target(self, target): return getattr(self.write, f'write_{self.write_param}')(target) class Converging(HasConvergence, Driv): """drivable with convergence""" pollinterval = 1 def checkProperties(self): self.parameters['tolerance'].setProperty('unit', self.unit) super().checkProperties() #def update_value(self, value): # print('UV', value) # self.value = value #def error_update_value(self, err): # raise err #def update_target(self, value): # self.target = value #def error_update_target(self, err): # raise err def write_target(self, target): self.convergence_start() return super().write_target(target) class RampDriv(HasRamp, Driv): pass def set_enabled(modobj, value): """set enabled on module if available""" if hasattr(modobj, 'enabled') and modobj.enabled != value: modobj.write_enabled(value) def get_value(obj, default): """get the value of given module. if not valid, return the limit (min_high or max_low)""" if not getattr(obj, 'enabled', True): return default # consider also that a value 0 is invalid return (obj.value if IDLE <= obj.status[0] < ERROR else 0) or default LOW = 0 HIGH = 1 class SwitchDriv(HasConvergence, Drivable): low = Attached(description='low range module') high = Attached(description='high range module') min_high = Parameter('minimum high target', FloatRange(unit='$'), readonly=False) max_low = Parameter('maximum low target', FloatRange(unit='$'), readonly=False) # disable_other = Parameter('whether to disable unused channel', BoolType(), readonly=False) selected = Parameter('selected module', EnumType(low=LOW, high=HIGH), readonly=False, default=0) autoswitch = Parameter('switch sensor automatically', BoolType(), readonly=False, default=True) _switch_target = None # if not None, switch to selection mhen mid range is reached # TODO: copy units from attached module # TODO: callbacks for updates def doPoll(self): super().doPoll() if self._switch_target is not None: mid = (self.min_high + self.max_low) * 0.5 if self._switch_target == HIGH: low = get_value(self.low, mid) # returns mid when low is invalid if low > mid: self.value = self.low.value self._switch_target = None self.write_target(self.target) return else: high = get_value(self.high, mid) # return mid when high is invalid if high < mid: self.value = self.high.value self._switch_target = None self.write_target(self.target) return if not self.isBusy() and self.autoswitch: low = get_value(self.low, self.max_low) high = get_value(self.high, self.min_high) low_valid = low < self.max_low high_valid = high > self.min_high if high_valid and high > self.max_low: if not low_valid and not self.low.control_active: set_enabled(self.low, False) return if low_valid and low < self.min_high: if not high_valid and not self.high.control_active: set_enabled(self.high, False) return # keep only one channel on #set_enabled(self.low, True) #set_enabled(self.high, True) def get_selected(self): low = get_value(self.low, self.max_low) high = get_value(self.high, self.min_high) if low < self.min_high: return 0 if high > self.max_low: return 1 return self.selected def read_value(self): return self.low.value if self.get_selected() == LOW else self.high.value def read_status(self): status = self.low.status if self.get_selected() == LOW else self.high.status if status[0] >= ERROR: return status return super().read_status() # convergence status def write_target(self, target): this, other = self.low, self.high selected = self.selected target1 = target self._switch_target = None if target > self.max_low * 0.75 + self.min_high * 0.25: if self.value < self.min_high: target1 = min(target, self.max_low) self._switch_target = HIGH selected = LOW else: this, other = other, this selected = HIGH elif target < self.min_high * 0.75 + self.max_low * 0.25: #if self.value > self.max_low: # target1 = max(self.min_high, target) # self._switch_target = LOW # this, other = other, this # selected = HIGH #else: selected = LOW elif self.selected == HIGH: this, other = other, this if hasattr(other, 'control_off'): other.control_off() set_enabled(this, True) set_enabled(other, False) self.write_selected(selected) self.convergence_start() self.log.info('target=%g (%s)', target, this.name) this.write_target(target1) return target