diff --git a/cfg/main/haakeuro_cfg.py b/cfg/main/haakeuro_cfg.py new file mode 100644 index 0000000..f3383d6 --- /dev/null +++ b/cfg/main/haakeuro_cfg.py @@ -0,0 +1,20 @@ +Node( + description = '''Haake thermostat + Eurotherm controller''', + id = haakeuro.config.sea.psi.ch, +) +Mod('sea_main', + 'frappy_psi.sea.SeaClient', + 'main sea connection for haakeuro.config', + config = 'haakeuro.config', + service = 'main', +) +Mod('th', + 'frappy_psi.sea.SeaDrivable', '', + io = 'sea_main', + sea_object = 'th', +) +Mod('te', + 'frappy_psi.sea.SeaDrivable', '', + io = 'sea_main', + sea_object = 'te', +) diff --git a/cfg/sea/haakeuro.config.json b/cfg/sea/haakeuro.config.json new file mode 100644 index 0000000..d1b9ad3 --- /dev/null +++ b/cfg/sea/haakeuro.config.json @@ -0,0 +1,160 @@ +{"th": {"base": "/th", "params": [ +{"path": "", "type": "float", "readonly": false, "cmd": "run th", "kids": 26}, +{"path": "unit", "type": "text", "readonly": false, "cmd": "th unit", "visibility": 3}, +{"path": "t2", "type": "float", "visibility": 3}, +{"path": "set", "type": "float"}, +{"path": "running", "type": "int", "readonly": false, "cmd": "th running", "visibility": 3}, +{"path": "extcontrol", "type": "int", "readonly": false, "cmd": "th extcontrol", "visibility": 3}, +{"path": "relais", "type": "int", "visibility": 3}, +{"path": "overtemp", "type": "int", "visibility": 3}, +{"path": "lowlevel", "type": "int", "visibility": 3}, +{"path": "pumpalarm", "type": "int", "visibility": 3}, +{"path": "externalarm", "type": "int", "visibility": 3}, +{"path": "coolalarm", "type": "int", "visibility": 3}, +{"path": "sensor1alarm", "type": "int", "visibility": 3}, +{"path": "sensor2alarm", "type": "int", "visibility": 3}, +{"path": "reset", "type": "int", "readonly": false, "cmd": "th reset", "visibility": 3}, +{"path": "with2sensors", "type": "int", "readonly": false, "cmd": "th with2sensors", "visibility": 3}, +{"path": "upperLimit", "type": "float", "readonly": false, "cmd": "th upperLimit"}, +{"path": "lowerLimit", "type": "float", "readonly": false, "cmd": "th lowerLimit"}, +{"path": "tolerance", "type": "float", "readonly": false, "cmd": "th tolerance"}, +{"path": "maxwait", "type": "int", "readonly": false, "cmd": "th maxwait"}, +{"path": "settle", "type": "int", "readonly": false, "cmd": "th settle"}, +{"path": "targetValue", "type": "float"}, +{"path": "is_running", "type": "int", "visibility": 3}, +{"path": "verbose", "type": "int", "readonly": false, "cmd": "th verbose", "visibility": 3}, +{"path": "driver", "type": "text", "visibility": 3}, +{"path": "creationCmd", "type": "text", "visibility": 3}, +{"path": "status", "type": "text", "readonly": false, "cmd": "th status"}]}, + +"te": {"base": "/te", "params": [ +{"path": "", "type": "float", "readonly": false, "cmd": "run te", "kids": 30}, +{"path": "unit", "type": "text", "readonly": false, "cmd": "te unit", "visibility": 3}, +{"path": "mode", "type": "int", "readonly": false, "cmd": "te mode"}, +{"path": "model", "type": "text", "visibility": 3}, +{"path": "pbPow", "type": "float", "visibility": 3}, +{"path": "pbMin", "type": "float", "visibility": 3}, +{"path": "pbScl", "type": "float", "visibility": 3}, +{"path": "output", "type": "float"}, +{"path": "position", "type": "float", "readonly": false, "cmd": "te position"}, +{"path": "asymmetry", "type": "float", "readonly": false, "cmd": "te asymmetry", "visibility": 3}, +{"path": "range", "type": "float", "readonly": false, "cmd": "te range", "visibility": 3}, +{"path": "set", "type": "float", "readonly": false, "cmd": "te set"}, +{"path": "rdonly", "type": "int", "readonly": false, "cmd": "te rdonly", "visibility": 3}, +{"path": "task", "type": "text", "readonly": false, "cmd": "te task"}, +{"path": "upperLimit", "type": "float", "readonly": false, "cmd": "te upperLimit"}, +{"path": "lowerLimit", "type": "float", "readonly": false, "cmd": "te lowerLimit", "visibility": 3}, +{"path": "tolerance", "type": "float", "readonly": false, "cmd": "te tolerance"}, +{"path": "maxwait", "type": "int", "readonly": false, "cmd": "te maxwait"}, +{"path": "settle", "type": "int", "readonly": false, "cmd": "te settle"}, +{"path": "targetValue", "type": "float"}, +{"path": "is_running", "type": "int", "visibility": 3}, +{"path": "verbose", "type": "int", "readonly": false, "cmd": "te verbose", "visibility": 3}, +{"path": "driver", "type": "text", "visibility": 3}, +{"path": "creationCmd", "type": "text", "visibility": 3}, +{"path": "status", "type": "text", "readonly": false, "cmd": "te status"}, +{"path": "pb", "type": "float", "readonly": false, "cmd": "te pb"}, +{"path": "ti", "type": "float", "readonly": false, "cmd": "te ti"}, +{"path": "td", "type": "float", "readonly": false, "cmd": "te td"}, +{"path": "manual", "type": "float", "readonly": false, "cmd": "te manual"}, +{"path": "rate", "type": "float", "readonly": false, "cmd": "te rate"}, +{"path": "workset", "type": "float", "readonly": false, "cmd": "te workset"}]}, + +"cc": {"base": "/cc", "params": [ +{"path": "", "type": "bool", "kids": 96}, +{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3}, +{"path": "status", "type": "text", "visibility": 3}, +{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"}, +{"path": "fav", "type": "bool", "readonly": false, "cmd": "cc fav"}, +{"path": "f", "type": "float", "visibility": 3}, +{"path": "fs", "type": "enum", "enum": {"ok": 0, "no_sens": 1}, "readonly": false, "cmd": "cc fs", "visibility": 3}, +{"path": "mav", "type": "bool", "readonly": false, "cmd": "cc mav"}, +{"path": "fm", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}, "visibility": 3}, +{"path": "fa", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "offline": 3}, "readonly": false, "cmd": "cc fa", "visibility": 3}, +{"path": "mp", "type": "float", "readonly": false, "cmd": "cc mp", "visibility": 3}, +{"path": "msp", "type": "float", "visibility": 3}, +{"path": "mmp", "type": "float", "visibility": 3}, +{"path": "mc", "type": "float", "readonly": false, "cmd": "cc mc", "visibility": 3}, +{"path": "mfc", "type": "float", "readonly": false, "cmd": "cc mfc", "visibility": 3}, +{"path": "moc", "type": "float", "readonly": false, "cmd": "cc moc", "visibility": 3}, +{"path": "mtc", "type": "float", "readonly": false, "cmd": "cc mtc", "visibility": 3}, +{"path": "mtl", "type": "float", "visibility": 3}, +{"path": "mft", "type": "float", "readonly": false, "cmd": "cc mft", "visibility": 3}, +{"path": "mt", "type": "float", "visibility": 3}, +{"path": "mo", "type": "float", "visibility": 3}, +{"path": "mcr", "type": "float", "visibility": 3}, +{"path": "mot", "type": "float", "visibility": 3}, +{"path": "mw", "type": "float", "readonly": false, "cmd": "cc mw", "description": "correction pulse after automatic open", "visibility": 3}, +{"path": "hav", "type": "bool", "readonly": false, "cmd": "cc hav"}, +{"path": "h", "type": "float", "visibility": 3}, +{"path": "hr", "type": "float", "visibility": 3}, +{"path": "hc", "type": "float", "visibility": 3}, +{"path": "hu", "type": "float", "visibility": 3}, +{"path": "hh", "type": "float", "readonly": false, "cmd": "cc hh", "visibility": 3}, +{"path": "hl", "type": "float", "readonly": false, "cmd": "cc hl", "visibility": 3}, +{"path": "htf", "type": "float", "readonly": false, "cmd": "cc htf", "description": "meas. period in fast mode", "visibility": 3}, +{"path": "hts", "type": "float", "readonly": false, "cmd": "cc hts", "description": "meas. period in slow mode", "visibility": 3}, +{"path": "hd", "type": "float", "readonly": false, "cmd": "cc hd", "visibility": 3}, +{"path": "hwr", "type": "float", "readonly": false, "cmd": "cc hwr", "visibility": 3}, +{"path": "hem", "type": "float", "readonly": false, "cmd": "cc hem", "description": "sensor length in mm from top to empty pos.", "visibility": 3}, +{"path": "hfu", "type": "float", "readonly": false, "cmd": "cc hfu", "description": "sensor length in mm from top to full pos.", "visibility": 3}, +{"path": "hcd", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3, "manual": 7}, "readonly": false, "cmd": "cc hcd", "visibility": 3}, +{"path": "hv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4}, "visibility": 3}, +{"path": "hsf", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "ha", "type": "bool", "readonly": false, "cmd": "cc ha", "visibility": 3}, +{"path": "hm", "type": "bool", "visibility": 3}, +{"path": "hf", "type": "enum", "enum": {"slow": 0, "fast": 1}, "readonly": false, "cmd": "cc hf", "visibility": 3}, +{"path": "hbe", "type": "bool", "readonly": false, "cmd": "cc hbe", "visibility": 3}, +{"path": "hmf", "type": "float", "visibility": 3}, +{"path": "hms", "type": "float", "visibility": 3}, +{"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit", "visibility": 3}, +{"path": "hft", "type": "int", "readonly": false, "cmd": "cc hft", "visibility": 3}, +{"path": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 6}, "readonly": false, "cmd": "cc hea"}, +{"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch", "visibility": 3}, +{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0", "visibility": 3}, +{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos.", "visibility": 3}, +{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos.", "visibility": 3}, +{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)", "visibility": 3}, +{"path": "h0", "type": "float", "visibility": 3}, +{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "h1", "type": "float", "visibility": 3}, +{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "h2", "type": "float", "visibility": 3}, +{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "h3", "type": "float", "visibility": 3}, +{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "h4", "type": "float", "visibility": 3}, +{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "h5", "type": "float", "visibility": 3}, +{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3}, +{"path": "hfb", "type": "float", "visibility": 3}, +{"path": "nav", "type": "bool", "readonly": false, "cmd": "cc nav"}, +{"path": "nu", "type": "float", "visibility": 3}, +{"path": "nl", "type": "float", "visibility": 3}, +{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth", "visibility": 3}, +{"path": "ntc", "type": "float", "readonly": false, "cmd": "cc ntc", "visibility": 3}, +{"path": "ntm", "type": "float", "readonly": false, "cmd": "cc ntm", "visibility": 3}, +{"path": "ns", "type": "enum", "enum": {"sens_ok": 0, "no_sens": 1, "short_circuit": 2, "upside_down": 3, "sens_warm": 4, "empty": 5}, "visibility": 3}, +{"path": "na", "type": "bool", "readonly": false, "cmd": "cc na", "visibility": 3}, +{"path": "nv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4, "boost": 5}, "visibility": 3}, +{"path": "nc", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3}, "readonly": false, "cmd": "cc nc", "visibility": 3}, +{"path": "nfb", "type": "float", "visibility": 3}, +{"path": "cda", "type": "float"}, +{"path": "cdb", "type": "float"}, +{"path": "cba", "type": "float"}, +{"path": "cbb", "type": "float"}, +{"path": "cvs", "type": "int"}, +{"path": "csp", "type": "int"}, +{"path": "cdv", "type": "text", "readonly": false, "cmd": "cc cdv"}, +{"path": "cic", "type": "text", "readonly": false, "cmd": "cc cic"}, +{"path": "cin", "type": "text"}, +{"path": "cds", "type": "enum", "enum": {"local": 0, "remote": 1, "loading": 2, "by_code": 3, "by_touch": 4}, "readonly": false, "cmd": "cc cds"}, +{"path": "timing", "type": "bool", "readonly": false, "cmd": "cc timing"}, +{"path": "tc", "type": "float", "visibility": 3}, +{"path": "tn", "type": "float", "visibility": 3}, +{"path": "th", "type": "float", "visibility": 3}, +{"path": "tf", "type": "float", "visibility": 3}, +{"path": "tm", "type": "float", "visibility": 3}, +{"path": "tv", "type": "float", "visibility": 3}, +{"path": "tq", "type": "float", "visibility": 3}, +{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}} diff --git a/frappy/client/__init__.py b/frappy/client/__init__.py index 59fb20f..3f491ef 100644 --- a/frappy/client/__init__.py +++ b/frappy/client/__init__.py @@ -245,8 +245,10 @@ class ProxyClient: except UnregisterCallback: cblist.remove(cbfunc) except Exception as e: + # the programmer should catch all errors in callbacks + # if not, the log will be flooded with errors if self.log: - self.log.error('error %r calling %s%r', e, cbfunc.__name__, args) + self.log.exception('error %r calling %s%r', e, cbfunc.__name__, args) return bool(cblist) def updateValue(self, module, param, value, timestamp, readerror): @@ -398,6 +400,7 @@ class SecopClient(ProxyClient): value = data[0] readerror = None module, param = module_param + timestamp = min(time.time(), timestamp) # no timestamps in the future! try: self.updateValue(module, param, value, timestamp, readerror) except KeyError: diff --git a/frappy/lib/__init__.py b/frappy/lib/__init__.py index 827ac03..cf40de1 100644 --- a/frappy/lib/__init__.py +++ b/frappy/lib/__init__.py @@ -405,4 +405,8 @@ def merge_status(*args): texts matching maximal code are joined with ', ' """ maxcode = max(a[0] for a in args) - return maxcode, ', '.join([a[1] for a in args if a[0] == maxcode and a[1]]) + # take status value matching highest status code + merged = [a[1] for a in args if a[0] == maxcode and a[1]] + # merge the split texts. use dict instead of set for keeping order + merged = {m: 0 for mm in merged for m in mm.split(', ')} + return maxcode, ', '.join(merged) diff --git a/frappy_psi/lakeshore.py b/frappy_psi/lakeshore.py index 53f16d7..57787f8 100644 --- a/frappy_psi/lakeshore.py +++ b/frappy_psi/lakeshore.py @@ -25,7 +25,7 @@ from frappy.core import Readable, Parameter, IntRange, EnumType, FloatRange, \ Attached, StructOf, WARN, Done, BoolType, Enum from frappy_psi.convergence import HasConvergence -from frappy_psi.mixins import HasOutputModule, HasControlledBy +from frappy.mixins import HasOutputModule, HasControlledBy class Ls340IO(StringIO): diff --git a/frappy_psi/mixins.py b/frappy_psi/mixins.py index 5e82eac..b44df3e 100644 --- a/frappy_psi/mixins.py +++ b/frappy_psi/mixins.py @@ -20,78 +20,120 @@ # # ***************************************************************************** -from frappy.datatypes import BoolType, EnumType, Enum -from frappy.core import Parameter, Writable, Attached +import time +from math import copysign +from frappy.datatypes import BoolType, FloatRange +from frappy.core import Parameter, BUSY +from frappy.lib import merge_status, clamp +from frappy.errors import RangeError -class HasControlledBy(Writable): - """mixin for modules with controlled_by +class HasRamp: + """software ramp""" + # make sure it is a drivable + status = Parameter() + target = Parameter() + ramp = Parameter('ramp rate', FloatRange(0, unit='$/min'), default=0, readonly=False) + ramp_used = Parameter('False: infinite ramp', BoolType(), default=False, readonly=False) + setpoint = Parameter('ramping setpoint', FloatRange(unit='$'), readonly=False) + maxdif = Parameter('''max. difference between setpoint and value + + stop ramp then value lags behind setpoint by more than maxdif + maxdif=0: use 'ramp' value (value lags 1 minute behind setpoint) + ''', + FloatRange(0, unit='$'), default=0, readonly=False) + rampinterval = Parameter('interval for changing the setpoint', FloatRange(0, unit='s'), + default=1, readonly=False) + workingramp = Parameter('effective ramp', FloatRange(unit='$/min')) - in the :meth:`write_target` the hardware action to switch to own control should be done - and in addition self.self_controlled() should be called - """ - controlled_by = Parameter('source of target value', EnumType(members={'self': 0}), default=0) - inputCallbacks = () + _ramp_status = None + _last_time = None + _buffer = 0 - def register_input(self, name, control_off): - """register input + def doPoll(self): + super().doPoll() # suppose that this is reading value and status + self.ramp_step(self.target) - :param name: the name of the module (for controlled_by enum) - :param control_off: a method on the input module to switch off control - """ - if not self.inputCallbacks: - self.inputCallbacks = {} - self.inputCallbacks[name] = control_off - 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 ramp_step(self, target): + now = time.time() + setpoint = self.setpoint + if self._ramp_status is not None: + if setpoint == target: + self._ramp_status = None # at target + self.workingramp = 0 + else: + sign = copysign(1, target - setpoint) + prev_t, prev_v = self._last_point + if self.value == prev_v: + return # no reads happened + delay = (now - prev_t) / 60.0 # minutes ! + slope = (self.value - prev_v) / max(1e-5, delay) + dif = (setpoint - self.value) * sign + maxdif = self.maxdif or self.ramp + if dif < maxdif: + # reduce ramp when slope is bigger than ramp + ramp = max(2 * self.ramp - sign * slope, 0) + self._buffer + if ramp > self.ramp: + self._buffer = min(ramp - self.ramp, self.ramp) + ramp = self.ramp + else: + self._buffer = 0 + setpoint += sign * delay * ramp + if sign * (setpoint - target) >= 0: + self.write_setpoint(setpoint) + self.workingramp = 0 + self._ramp_status = None # at target + else: + if ramp != self.workingramp: + self.workingramp = sign * ramp + self.write_setpoint(setpoint) + self._ramp_status = 'ramping' + else: + self._ramp_status = 'holding' + self._last_point = now, self.value + self.read_status() - def self_controlled(self): - """method to change controlled_by to self + def read_status(self): + status = super().read_status() + if self._ramp_status is None: + if self.pollInfo.fast_flag: + self.setFastPoll(False) + return status + if self.pollInfo.interval != self.rampinterval: + self.setFastPoll(True, self.rampinterval) + return merge_status((BUSY, self._ramp_status), status) - must be called from the write_target method - """ - if self.controlled_by: - self.controlled_by = 0 - for name, control_off in self.inputCallbacks.items(): - control_off(self.name) + def write_ramp(self, ramp): + if ramp: + self.write_ramp_used(True) + else: + raise RangeError('ramp must not 0, use ramp_used = False to disable ramping') + return ramp + def write_ramp_used(self, used): + if used != self.ramp_used: + self.ramp_used = used + if self._ramp_status: + self.write_target(self.target) -class HasOutputModule(Writable): - """mixin for modules having an output module + def write_setpoint(self, setpoint): + super().write_target(setpoint) + return setpoint - in the :meth:`write_target` the hardware action to switch to own control should be done - and in addition self.activate_output() should be called - """ - # allow unassigned output module, it should be possible to configure a - # module with fixed control - output_module = Attached(HasControlledBy, mandatory=False) - control_active = Parameter('control mode', BoolType()) + def read_target(self): + if not self._ramp_status: + return super().read_target() + return self.target - def initModule(self): - super().initModule() - if self.output_module: - self.output_module.register_input(self.name, self.control_off) - - def activate_output(self): - """method to switch control_active on - - self.activate_output() must be called from the write_target method - """ - out = self.output_module - if out: - for name, control_off in out.inputCallbacks.items(): - if name != self.name: - control_off(self.name) - out.controlled_by = self.name - self.control_active = True - - def control_off(self, switched_by): - """control_off is called, when an other module takes over control - - if possible avoid hardware access in an overriding method in an overriding method - as this might lead to a deadlock with the modules accessLock - """ - if self.control_active: - self.control_active = False - self.log.warning(f'switched to manual mode by {switched_by}') + def write_target(self, target): + if self.ramp_used: + if self.parameters['setpoint'].readerror: + self.write_setpoint(self.read_value()) + self._ramp_status = 'changed target' + self._last_time = time.time() + self.setFastPoll(True, self.rampinterval) + self.ramp_step(target) + return target + self._ramp_status = None + self.write_setpoint(target) + return target diff --git a/frappy_psi/parmod.py b/frappy_psi/parmod.py new file mode 100644 index 0000000..58c3b3f --- /dev/null +++ b/frappy_psi/parmod.py @@ -0,0 +1,99 @@ +# -*- 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: +# Markus Zolliker +# +# ***************************************************************************** + +"""modules to access parameters""" + +from frappy.core import Drivable, IDLE, Attached, StringType, Property, \ + Parameter, FloatRange +from frappy.errors import ConfigError +from frappy_psi.convergence import HasConvergence +from frappy_psi.mixins import HasRamp + + +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 diff --git a/frappy_psi/sea.py b/frappy_psi/sea.py index 8bb60f4..2d98f92 100644 --- a/frappy_psi/sea.py +++ b/frappy_psi/sea.py @@ -48,9 +48,8 @@ from frappy.modules import Attached, Command, Done, Drivable, \ from frappy.protocol.dispatcher import make_update -CFG_HEADER = """Node( - description = '''%(nodedescr)s''', - id = %(config)s.sea.psi.ch, +CFG_HEADER = """Node('%(config)s.sea.psi.ch', + '''%(nodedescr)s''', ) Mod(%(seaconn)r, 'frappy_psi.sea.SeaClient',