[WIP] fi furnace improvements

- still under development

Change-Id: I5fc22f041fb136b549016f510f06ea703122bee5
This commit is contained in:
zolliker 2025-05-08 08:29:45 +02:00
parent e46291eef6
commit 3bae6f8d7f
4 changed files with 134 additions and 70 deletions

View File

@ -6,7 +6,7 @@ Node('fi.psi.ch',
Mod('T_main', Mod('T_main',
'frappy_psi.furnace.PRtransmitter', 'frappy_psi.furnace.PRtransmitter',
'sample temperature', 'sample temperature',
addr='ai1', addr='ai2',
valuerange=(0, 2300), valuerange=(0, 2300),
value=Param(unit='degC'), value=Param(unit='degC'),
) )
@ -14,7 +14,7 @@ Mod('T_main',
Mod('T_extra', Mod('T_extra',
'frappy_psi.furnace.PRtransmitter', 'frappy_psi.furnace.PRtransmitter',
'extra temperature', 'extra temperature',
addr='ai2', addr='ai1',
valuerange=(0, 2300), valuerange=(0, 2300),
value=Param(unit='degC'), value=Param(unit='degC'),
) )
@ -49,8 +49,11 @@ Mod('T',
'controlled Temperature', 'controlled Temperature',
input_module='T_main', input_module='T_main',
output_module='htr', output_module='htr',
value = Param(unit='degC'),
output_min = 0,
output_max = 100,
# relais='relais', # relais='relais',
p=2, p=0.1,
i=0.01, i=0.01,
) )
@ -81,16 +84,21 @@ Mod('flowswitch',
true_level='low', true_level='low',
) )
# Mod('interlocks', Mod('interlocks',
# 'frappy_psi.furnace.Interlocks', 'frappy_psi.furnace.Interlocks',
# 'interlock parameters', 'interlock parameters',
# input='T_htr', main_T='T_main',
# wall_T='T_wall', extra_T='T_extra',
# vacuum='p', wall_T='T_wall',
# control='T', vacuum='p',
# wall_limit=50, control='T',
# vacuum_limit=0.1, htr='htr',
# ) flowswitch='flowswitch',
wall_limit=50,
main_T_limit = 1400,
extra_T_limit = 1400,
vacuum_limit=0.01,
)
Mod('p', Mod('p',
'frappy_psi.furnace.PKRgauge', 'frappy_psi.furnace.PKRgauge',
@ -105,5 +113,5 @@ Mod('vso',
'frappy_psi.ionopimax.VoltagePower', 'frappy_psi.ionopimax.VoltagePower',
'voltage power output', 'voltage power output',
target = 24, target = 24,
# export = False, export = False,
) )

View File

@ -19,9 +19,9 @@
"""interlocks for furnace""" """interlocks for furnace"""
import time
from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\ from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\
BoolType, ERROR, IDLE BoolType, ERROR, IDLE
from frappy.errors import ImpossibleError
from frappy.mixins import HasControlledBy from frappy.mixins import HasControlledBy
from frappy_psi.picontrol import PImixin from frappy_psi.picontrol import PImixin
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
@ -30,15 +30,19 @@ import frappy_psi.tdkpower as tdkpower
import frappy_psi.bkpower as bkpower import frappy_psi.bkpower as bkpower
class Interlocks(Module): class Interlocks(Writable):
input = Attached(Readable, 'the input module') value = Parameter('interlock o.k.', BoolType(), default=True)
vacuum = Attached(Readable, 'the vacuum pressure') target = Parameter('set to true to confirm', BoolType(), readonly=False)
wall_T = Attached(Readable, 'the wall temperature') input = Attached(Readable, 'the input module', mandatory=False) # TODO: remove
htr_T = Attached(Readable, 'the heater temperature') 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') main_T = Attached(Readable, 'the main temperature')
extra_T = Attached(Readable, 'the extra temperature') extra_T = Attached(Readable, 'the extra temperature')
control = Attached(Module, 'the control module') 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'), wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'),
default = 50, readonly = False) default = 50, readonly = False)
vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'), 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'), htr_T_limit = Parameter('maximum htr temperature', FloatRange(0, unit='degC'),
default = 530, readonly = False) default = 530, readonly = False)
main_T_limit = Parameter('maximum main temperature', FloatRange(0, unit='degC'), 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'), 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): def initModule(self):
super().initModule() super().initModule()
@ -60,25 +67,50 @@ class Interlocks(Module):
(self.vacuum, 'vacuum_limit'), (self.vacuum, 'vacuum_limit'),
] ]
def doPoll(self): def write_target(self, value):
# TODO: check channels are valid if value:
super().doPoll() self.read_status()
newstatus = None if self._conditions:
if self.input.status[0] >= ERROR: raise ImpossibleError('not ready to start')
newstatus = self.input.status self._off_reason = None
else: self.value = True
for sensor, limitname in self._sensor_checks: elif self.value:
if sensor.value > getattr(self, limitname): self.switch_off()
newstatus = f'above {sensor.name} limit' self._off_reason = 'switched off'
break self.value = False
if sensor.status[0] >= ERROR: self.read_status()
newstatus = f'error at {sensor.name}: {sensor.status[1]}'
return def switch_off(self):
self.control.status = newstatus if self.value:
self._off_reason = self._conditions
self.value = False
if self.control.control_active: if self.control.control_active:
self.log.error('switch control off %r', self.control.status) self.log.error('switch control off %r', self.control.status)
self.control.write_control_active(False) self.control.write_control_active(False)
self.relais.write_target(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): class PI(HasConvergence, PImixin):
@ -111,3 +143,4 @@ class PKRgauge(LogVoltageInput):
rawrange = (1.82, 8.6) rawrange = (1.82, 8.6)
valuerange = (5e-9, 1000) valuerange = (5e-9, 1000)
extendedrange = (0.5, 9.5) extendedrange = (0.5, 9.5)
value = Parameter(unit='mbar')

View File

@ -63,7 +63,7 @@ import math
from frappy.core import Readable, Writable, Parameter, Attached, IDLE, Property from frappy.core import Readable, Writable, Parameter, Attached, IDLE, Property
from frappy.lib import clamp from frappy.lib import clamp
from frappy.datatypes import LimitsType, EnumType, BoolType, FloatRange from frappy.datatypes import LimitsType, EnumType, BoolType, FloatRange
from frappy.mixins import HasOutputModule from frappy.newmixins import HasOutputModule
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
@ -79,7 +79,8 @@ class PImixin(HasOutputModule, Writable):
value = Parameter(unit='K') value = Parameter(unit='K')
_lastdiff = None _lastdiff = None
_lasttime = 0 _lasttime = 0
_clamp_limits = None _get_range = None # a function get output range from output_module
_overflow = 0
def initModule(self): def initModule(self):
super().initModule() super().initModule()
@ -88,21 +89,9 @@ class PImixin(HasOutputModule, Writable):
def doPoll(self): def doPoll(self):
super().doPoll() 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: if not self.control_active:
return return
out = self.output_module
self.status = IDLE, 'controlling' self.status = IDLE, 'controlling'
now = time.time() now = time.time()
deltat = clamp(0, now-self._lasttime, 10) deltat = clamp(0, now-self._lasttime, 10)
@ -112,17 +101,51 @@ class PImixin(HasOutputModule, Writable):
self._lastdiff = diff self._lastdiff = diff
deltadiff = diff - self._lastdiff deltadiff = diff - self._lastdiff
self._lastdiff = diff 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 out = self.output_module
output = out.target if hasattr(out, 'max_target'):
if self.output_func == 'square': if hasattr(self, 'min_target'):
output = math.sqrt(max(0, output)) self._get_range = lambda o=out: (o.read_min_target(), o.read_max_target())
output += self.p * deltadiff + self.i * deltat * diff else:
if self.output_func == 'square': self._get_range = lambda o=out: (0, o.read_max_target())
output = output ** 2 elif hasattr(out, 'limit'): # mercury.HeaterOutput
output = self._clamp_limits(output) self._get_range = lambda o=out: (0, o.read_limit())
out.update_target(self.name, clamp(output, self.output_min, self.output_max)) 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): def write_control_active(self, value):
super().write_control_active(value)
if not value: if not value:
self.output_module.write_target(0) self.output_module.write_target(0)

View File

@ -31,13 +31,13 @@ class IO(StringIO):
class Power(HasIO, Readable): class Power(HasIO, Readable):
value = Parameter(datatype=FloatRange(0,3300,unit='W')) 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): def read_value(self):
reply_volt = self.communicate('MV?') self.voltage = float(self.communicate('MV?'))
reply_current = self.communicate('MC?') self.current = float(self.communicate('MC?'))
volt = float(reply_volt) return self.voltage * self.current
current = float(reply_current)
return volt*current
class Output(HasIO, Writable): class Output(HasIO, Writable):
@ -59,7 +59,7 @@ class Output(HasIO, Writable):
# take care of proper order # take care of proper order
if target == 0: if target == 0:
self.write_output_enable(False) 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 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 curr = self.maxcurrent if self.mode == 'voltage' else self.maxcurrent * 0.01 * target
if curr < prev_curr: if curr < prev_curr: