From 0ca913bf1900af2fbfed9a4f0f80305a13ce8fcd Mon Sep 17 00:00:00 2001 From: Anik Stark Date: Wed, 11 Mar 2026 09:45:13 +0100 Subject: [PATCH] frappy_psi: add jtccr --- frappy_psi/jtccr.py | 138 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 frappy_psi/jtccr.py diff --git a/frappy_psi/jtccr.py b/frappy_psi/jtccr.py new file mode 100644 index 00000000..e003aded --- /dev/null +++ b/frappy_psi/jtccr.py @@ -0,0 +1,138 @@ +# ***************************************************************************** +# 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: +# Anik Stark +# ***************************************************************************** + +from frappy.core import Parameter, Property, Writable, Attached, EnumType, FloatRange, \ + IDLE, BUSY, WARN + + +STATES = {'manual': 0, + 'high_pressure': 1, + 'circulating': 2, + 'warmup': 3, + } + + +class JTCCR(Writable): + + compressor = Attached() + + value = Parameter('current state', datatype=EnumType(STATES), default=0) + target = Parameter('target state', datatype=EnumType(STATES), default=0) + #p1min = Property('lower limit to switch to high pressure mode', dataype=FloatRange(unit='mbar'), default=1.8) + p1max = Property('limit to switch to circulating mode', datatype=FloatRange(unit='mbar'), default=2.2) + p2min = Property('lower limit to turn compressor off', datatype=FloatRange(unit='mbar'), default=0.12) + p2max = Property('upper limit to turn compressor on', datatype=FloatRange(unit='mbar'), default=0.8) + #p2lim = Property('do not start compressor if p2 is below this value', datatype=FloatRange(unit='mbar'), default=0.15) + pdifmax = Property('max pressure difference of compressor', datatype=FloatRange(unit='mbar'), default=5.0) + pdifmargin = Property('safety margin for pressure difference of compressor', + datatype=FloatRange(unit='mbar'), default=1.0) + p3margin = Property('start compressor when p3 is below pressreg setpoint plus this value', + datatype=FloatRange(unit='mbar'), default=0.01) + p3reg = Parameter('pressure regulation setpoint', datatype=FloatRange(unit='mbar')) + plow = Property('pressure below 5K', datatype=FloatRange(unit='mbar'), default=4.0) + + valves_high_pressure = { + 'close': 'V3 V4 V5 V6 V7 V8 V10', + 'open': 'V1 V2 V9 Vm', + } + valves_circulating = { + 'close': 'V3 V4 V5 V6 V7 V9 V10', + 'open': 'V1 V2 V8 Vm', + } + valves_warmup = { + 'close': 'V6 V7 V8 V9 V10', + 'open': 'V1 V2 V3 V4 V5 Vm', + } + valves_security= { + 'open': '', + 'close': 'V1 V9' + } + valves_overpressure = { + 'open': 'V10', + 'close': '' + } + + def write_target(self, target): + if self.value != target: + self.set_mode(STATES.get(target)) + return target + + def set_mode(self, state): + if state == 'high_pressure': + self.p3reg = 12 + self.handle_valves(**self.valves_high_pressure) + elif state == 'circulating': + self.p3reg = self.plow + self.handle_valves(**self.valves_circulating) + elif state == 'warmup': + self.handle_valves(**self.valves_warmup) + self.value = state + + def security_settings(self): + self.compressor.write_target(False) + self.handle_valves(**self.valves_security) + self.set_mode('manual') + + def handle_valves(self, close=(), open=()): + """set given valves. raises ImpossibleError, when checks fails""" + self._valves_to_wait_for = {} + self._valves_failed = {True: [], False: []} + for flag, valves in enumerate([close, open]): + for vname in valves.split(): + valve = self.secNode.modules[vname] + valve.write_target(flag) + # TODO: do we need to wait for motor valve? + + def doPoll(self): + p1 = self.secNode.module['P1'].read_value() + p2 = self.secNode.module['P2'].read_value() + p3 = self.secNode.module['P3'].read_value() + compressor_state = self.compressor.read_value() + if self.value == 'manual': + return self.set_mode('manual') + if p3 >= p2 + self.pdifmax + self.pdifmargin: + # overpressure protection + self.security_settings() + self.status = WARN, 'overpressure: He recovery output closed?' + return + if p2 < self.p2min and p3 < self.p3reg and self.value != 'high_pressure': + # underpressure protection + self.security_settings() + self.status = WARN, 'underpressure: not enough He' + return + if self.value == 'circulating': + if p3 < self.p3reg and not compressor_state: + self.compressor.write_target(True) + elif (p3 - p2) > self.pdifmax and compressor_state: + self.compressor.write_target(False) + # TODO: do we need to skip overpressure protection for one time? + if self.value == 'high_pressure' and p1 > self.p1max: + self.set_mode('circulating') + self.status = IDLE, '' + if p2 > self.p2max and not compressor_state: + self.compressor.write_target(True) + elif p2 < self.p2min and compressor_state: + self.compressor.write_target(False) + if (p3 - p2) >= self.pdifmax + 0.1: + self.handle_valves(**self.valves_overpressure) + self.status = BUSY, 'release to recovery' + elif self.secNode.modules['V10'].read_value(): + self.secNode.modules['V10'].write_target(False) + self.status = IDLE, 'release finished' + self.status = IDLE, ''