[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 ccc66468d4
commit dad9536eb5
4 changed files with 134 additions and 70 deletions

View File

@ -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,
)

View File

@ -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')

View File

@ -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)

View File

@ -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: