diff --git a/cfg/fi_cfg.py b/cfg/fi_cfg.py index 2dd1b6a..51b858f 100644 --- a/cfg/fi_cfg.py +++ b/cfg/fi_cfg.py @@ -6,7 +6,7 @@ Node('fi.psi.ch', Mod('T_main', 'frappy_psi.furnace.PRtransmitter', 'sample temperature', - addr='ai1', + addr='ai2', valuerange=(0, 2300), value=Param(unit='degC'), ) @@ -14,7 +14,7 @@ Mod('T_main', Mod('T_extra', 'frappy_psi.furnace.PRtransmitter', 'extra temperature', - addr='ai2', + addr='ai1', valuerange=(0, 2300), value=Param(unit='degC'), ) @@ -49,8 +49,11 @@ Mod('T', 'controlled Temperature', input_module='T_main', output_module='htr', + value = Param(unit='degC'), + output_min = 0, + output_max = 100, # relais='relais', - p=2, + p=0.1, i=0.01, ) @@ -81,16 +84,21 @@ Mod('flowswitch', true_level='low', ) -# Mod('interlocks', -# 'frappy_psi.furnace.Interlocks', -# 'interlock parameters', -# input='T_htr', -# wall_T='T_wall', -# vacuum='p', -# control='T', -# wall_limit=50, -# vacuum_limit=0.1, -# ) +Mod('interlocks', + 'frappy_psi.furnace.Interlocks', + 'interlock parameters', + main_T='T_main', + extra_T='T_extra', + wall_T='T_wall', + vacuum='p', + control='T', + htr='htr', + flowswitch='flowswitch', + wall_limit=50, + main_T_limit = 1400, + extra_T_limit = 1400, + vacuum_limit=0.01, + ) Mod('p', 'frappy_psi.furnace.PKRgauge', @@ -105,5 +113,5 @@ Mod('vso', 'frappy_psi.ionopimax.VoltagePower', 'voltage power output', target = 24, - # export = False, + export = False, ) diff --git a/frappy_psi/furnace.py b/frappy_psi/furnace.py index 8f245eb..4047855 100644 --- a/frappy_psi/furnace.py +++ b/frappy_psi/furnace.py @@ -19,9 +19,9 @@ """interlocks for furnace""" -import time from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\ BoolType, ERROR, IDLE +from frappy.errors import ImpossibleError from frappy.mixins import HasControlledBy from frappy_psi.picontrol import PImixin from frappy_psi.convergence import HasConvergence @@ -30,15 +30,19 @@ import frappy_psi.tdkpower as tdkpower import frappy_psi.bkpower as bkpower -class Interlocks(Module): - input = Attached(Readable, 'the input module') - vacuum = Attached(Readable, 'the vacuum pressure') - wall_T = Attached(Readable, 'the wall temperature') - htr_T = Attached(Readable, 'the heater temperature') +class Interlocks(Writable): + value = Parameter('interlock o.k.', BoolType(), default=True) + target = Parameter('set to true to confirm', BoolType(), readonly=False) + input = Attached(Readable, 'the input module', mandatory=False) # TODO: remove + vacuum = Attached(Readable, 'the vacuum pressure', mandatory=False) + wall_T = Attached(Readable, 'the wall temperature', mandatory=False) + htr_T = Attached(Readable, 'the heater temperature', mandatory=False) main_T = Attached(Readable, 'the main temperature') extra_T = Attached(Readable, 'the extra temperature') control = Attached(Module, 'the control module') - relais = Attached(Writable, 'the interlock relais') + htr = Attached(Module, 'the heater module', mandatory=False) + relais = Attached(Writable, 'the interlock relais', mandatory=False) + flowswitch = Attached(Readable, 'the flow switch', mandatory=False) wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'), default = 50, readonly = False) vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'), @@ -46,9 +50,12 @@ class Interlocks(Module): htr_T_limit = Parameter('maximum htr temperature', FloatRange(0, unit='degC'), default = 530, readonly = False) main_T_limit = Parameter('maximum main temperature', FloatRange(0, unit='degC'), - default = 530, readonly = False) + default = 530, readonly = False) extra_T_limit = Parameter('maximum extra temperature', FloatRange(0, unit='degC'), - default = 530, readonly = False) + default = 530, readonly = False) + + _off_reason = None # reason triggering interlock + _conditions = '' # summary of reasons why locked now def initModule(self): super().initModule() @@ -60,25 +67,50 @@ class Interlocks(Module): (self.vacuum, 'vacuum_limit'), ] - def doPoll(self): - # TODO: check channels are valid - super().doPoll() - newstatus = None - if self.input.status[0] >= ERROR: - newstatus = self.input.status - else: - for sensor, limitname in self._sensor_checks: - if sensor.value > getattr(self, limitname): - newstatus = f'above {sensor.name} limit' - break - if sensor.status[0] >= ERROR: - newstatus = f'error at {sensor.name}: {sensor.status[1]}' - return - self.control.status = newstatus + def write_target(self, value): + if value: + self.read_status() + if self._conditions: + raise ImpossibleError('not ready to start') + self._off_reason = None + self.value = True + elif self.value: + self.switch_off() + self._off_reason = 'switched off' + self.value = False + self.read_status() + + def switch_off(self): + if self.value: + self._off_reason = self._conditions + self.value = False if self.control.control_active: self.log.error('switch control off %r', self.control.status) - self.control.write_control_active(False) - self.relais.write_target(False) + self.control.write_control_active(False) + self.control.status = ERROR, self._conditions + if self.htr and self.htr.target: + self.htr.write_target(0) + if self.relais and (self.relais.value or self.relais.target): + self.relais.write_target(False) + + def read_status(self): + conditions = [] + if self.flowswitch and self.flowswitch.value == 0: + conditions.append('no cooling water') + for sensor, limitname in self._sensor_checks: + if sensor is None: + continue + if sensor.value > getattr(self, limitname): + conditions.append(f'above {sensor.name} limit') + if sensor.status[0] >= ERROR: + conditions.append(f'error at {sensor.name}: {sensor.status[1]}') + break + self._conditions = ', '.join(conditions) + if conditions and (self.control.control_active or self.htr.target): + self.switch_off() + if self.value: + return IDLE, '; '.join(conditions) + return ERROR, self._off_reason class PI(HasConvergence, PImixin): @@ -111,3 +143,4 @@ class PKRgauge(LogVoltageInput): rawrange = (1.82, 8.6) valuerange = (5e-9, 1000) extendedrange = (0.5, 9.5) + value = Parameter(unit='mbar') diff --git a/frappy_psi/picontrol.py b/frappy_psi/picontrol.py index a550a52..2728891 100644 --- a/frappy_psi/picontrol.py +++ b/frappy_psi/picontrol.py @@ -63,7 +63,7 @@ import math from frappy.core import Readable, Writable, Parameter, Attached, IDLE, Property from frappy.lib import clamp from frappy.datatypes import LimitsType, EnumType, BoolType, FloatRange -from frappy.mixins import HasOutputModule +from frappy.newmixins import HasOutputModule from frappy_psi.convergence import HasConvergence @@ -79,7 +79,8 @@ class PImixin(HasOutputModule, Writable): value = Parameter(unit='K') _lastdiff = None _lasttime = 0 - _clamp_limits = None + _get_range = None # a function get output range from output_module + _overflow = 0 def initModule(self): super().initModule() @@ -88,21 +89,9 @@ class PImixin(HasOutputModule, Writable): def doPoll(self): super().doPoll() - if self._clamp_limits is None: - out = self.output_module - if hasattr(out, 'max_target'): - if hasattr(self, 'min_target'): - self._clamp_limits = lambda v, o=out: clamp(v, o.read_min_target(), o.read_max_target()) - else: - self._clamp_limits = lambda v, o=out: clamp(v, 0, o.read_max_target()) - elif hasattr(out, 'limit'): # mercury.HeaterOutput - self._clamp_limits = lambda v, o=out: clamp(v, 0, o.read_limit()) - else: - self._clamp_limits = lambda v: v - if self.output_min == 0 and self.output_max == 0: - self.output_max = self._clamp_limits(float('inf')) if not self.control_active: return + out = self.output_module self.status = IDLE, 'controlling' now = time.time() deltat = clamp(0, now-self._lasttime, 10) @@ -112,17 +101,51 @@ class PImixin(HasOutputModule, Writable): self._lastdiff = diff deltadiff = diff - self._lastdiff self._lastdiff = diff + output, omin, omax = self._cvt2int(out.target) + output += self._overflow + self.p * deltadiff + self.i * deltat * diff + if output < omin: + self._overflow = max(omin - omax, output - omin) + output = omin + elif output > omax: + self._overflow = min(omax - omin, output - omax) + output = omax + else: + self._overflow = 0 + out.update_target(self.name, self._cvt2ext(output)) + + def cvt2int_square(self, output): + return (math.sqrt(max(0, clamp(x, *self._get_range()))) for x in (output, self.output_min, self.output_max)) + + def cvt2ext_square(self, output): + return output ** 2 + + def cvt2int_lin(self, output): + return (clamp(x, *self._get_range()) for x in (output, self.output_min, self.output_max)) + + def cvt2ext_lin(self, output): + return output + + def write_output_func(self, value): out = self.output_module - output = out.target - if self.output_func == 'square': - output = math.sqrt(max(0, output)) - output += self.p * deltadiff + self.i * deltat * diff - if self.output_func == 'square': - output = output ** 2 - output = self._clamp_limits(output) - out.update_target(self.name, clamp(output, self.output_min, self.output_max)) + if hasattr(out, 'max_target'): + if hasattr(self, 'min_target'): + self._get_range = lambda o=out: (o.read_min_target(), o.read_max_target()) + else: + self._get_range = lambda o=out: (0, o.read_max_target()) + elif hasattr(out, 'limit'): # mercury.HeaterOutput + self._get_range = lambda o=out: (0, o.read_limit()) + else: + if self.output_min == self.output_max == 0: + self.output_max = 1 + self._get_range = lambda o=self: (o.output_min, o.output_max) + if self.output_min == self.output_max == 0: + self.output_min, self.output_max = self._get_range() + self.output_func = value + self._cvt2int = getattr(self, f'cvt2int_{self.output_func.name}') + self._cvt2ext = getattr(self, f'cvt2ext_{self.output_func.name}') def write_control_active(self, value): + super().write_control_active(value) if not value: self.output_module.write_target(0) diff --git a/frappy_psi/tdkpower.py b/frappy_psi/tdkpower.py index f57a2cf..71c09fe 100644 --- a/frappy_psi/tdkpower.py +++ b/frappy_psi/tdkpower.py @@ -31,13 +31,13 @@ class IO(StringIO): class Power(HasIO, Readable): value = Parameter(datatype=FloatRange(0,3300,unit='W')) - + voltage = Parameter('voltage', FloatRange(0,8, unit='V')) + current = Parameter('current', FloatRange(0,400, unit='A')) + def read_value(self): - reply_volt = self.communicate('MV?') - reply_current = self.communicate('MC?') - volt = float(reply_volt) - current = float(reply_current) - return volt*current + self.voltage = float(self.communicate('MV?')) + self.current = float(self.communicate('MC?')) + return self.voltage * self.current class Output(HasIO, Writable): @@ -59,7 +59,7 @@ class Output(HasIO, Writable): # take care of proper order if target == 0: self.write_output_enable(False) - prev_curr = self.communicate(f'PC?') + prev_curr = float(self.communicate(f'PC?')) volt = self.maxvolt if self.mode == 'current' else self.maxvolt * 0.01 * target curr = self.maxcurrent if self.mode == 'voltage' else self.maxcurrent * 0.01 * target if curr < prev_curr: