From 1cf8b9eee48a9f5f67c553f8e9cbb418bd7927e6 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 5 Aug 2026 17:33:52 +0200 Subject: [PATCH] DIL5: implement sorb_pump and remove Change-Id: I3701cb6613cdc19621e38fe8c472fbd940c20a52 --- frappy_psi/dilution.py | 132 +++++++++------ frappy_psi/dilution_new.py | 328 ++++++++++++++++++++++++++++++++++++- 2 files changed, 413 insertions(+), 47 deletions(-) diff --git a/frappy_psi/dilution.py b/frappy_psi/dilution.py index d2a19637..46354de3 100644 --- a/frappy_psi/dilution.py +++ b/frappy_psi/dilution.py @@ -16,10 +16,10 @@ # # Module authors: # Andrea Plank +# Markus Zolliker # # ***************************************************************************** -import time from frappy.core import Readable, Drivable, Parameter, Attached, FloatRange, \ Command, IDLE, BUSY, WARN, ERROR, Property from frappy.datatypes import EnumType, IntRange, BoolType, StructOf, StringType @@ -28,8 +28,12 @@ from frappy.lib.enum import Enum from frappy.errors import ImpossibleError, HardwareError from frappy.addrparam import AddrParam, AddrMixin from frappy.lib import formatStatusBits -from frappy.persistent import PersistentMixin, PersistentParam -from frappy_psi.logo import LogoMixin, DigitalActuator +from frappy_psi.logo import LogoMixin +from frappy_psi.picontrol import PImixin +from frappy_psi.convergence import HasConvergence +from frappy.ctrlby import WrapControlledBy +import frappy_psi.ls372 as ls372 + T = Enum( # target states off = 0, @@ -63,22 +67,30 @@ class Dilution(HasStates, Drivable): still_pressure = Attached() still_pressure_turbo = Attached(mandatory=False) + vti_temp = Attached() + sorb_temp = Attached() + dump_pressure = Attached() + mix_temp = Attached() + nv_flow = Attached() + still_htr = Attached() + value = Parameter('current state', EnumType(T), default=0) target = Parameter('target state', EnumType(T), default=0) sorbpumped = Parameter('sorb pump done', BoolType(), default=False) - dump_pressure = Attached() - #ls372 = Attached() - - condensing_p_low = Parameter('Lower limit for condenseline pressure', - FloatRange(unit='mbar'), readonly=False, default=1200) - condensing_p_high = Parameter('Higher limit for condenseline pressure', - FloatRange(unit='mbar'), readonly=False, default=1500) - dump_target = Parameter('low dump pressure limit indicating end of condensation phase', - FloatRange(unit='mbar * min'), readonly=False, default=50) + condensing_p_low = Parameter('lower limit for condenseline pressure', + FloatRange(unit='mbar'), readonly=False, default=1400) + condensing_p_high = Parameter('upper limit for condenseline pressure', + FloatRange(unit='mbar'), readonly=False, default=1700) + sorb_pump_time = Parameter('sorb pump time', FloatRange(unit='s'), default=2400, readonly=False) + sorb_pump_temp = Parameter('sorb pump temperature', FloatRange(unit='K'), default=40, readonly=False) + condense_flow = Parameter('condense nv flow', FloatRange(unit='mbar'), default=12, readonly=False) + circulate_flow = Parameter('circulate nv flow', FloatRange(unit='mbar'), default=8, readonly=False) + dump_low = Parameter('low dump pressure limit for opening V9 fully', + FloatRange(unit='mbar * min'), readonly=False, default=20) pulse_factor = Parameter('factor for calculating V9 pulse length', - FloatRange(unit='mbar'), readonly=False, default=20) + FloatRange(unit='mbar * min'), readonly=False, default=20) end_condense_pressure = Parameter('low condense pressure indicating end of condensation phase', FloatRange(unit='mbar'), readonly=False, default=500) turbo_condense_pressure = Parameter('low condense pressure before turbo start', @@ -96,6 +108,10 @@ class Dilution(HasStates, Drivable): condense_valves = Parameter('valve to act when condensing', valve_set) valves_after_remove = Parameter('valve to act after remove', valve_set) check_after_remove = Parameter('check for manual valves after remove', valve_set) + + # table reads as: up to a dump pressure of 250 mbar heat VTI to 2 K + remove_temp_profile = (250, 2), (400, 3), (500, 5), (9999, 10) + _start_time = 0 init = True _warn_manual_work = None @@ -121,40 +137,41 @@ class Dilution(HasStates, Drivable): return target - """ - @status_code(BUSY, 'sorbpump state') - def sorbpump(self, state): - #Heizt Tsorb auf und wartet ab. - if self.init: - self.ls372.write_target(40) #Setze Tsorb auf 40K - self.start_time = self.now - self.init = false - return Retry - - if self.now - self.start_time < 2400: # 40 Minuten warten - return Retry - - self.ls372.write_target(0) - - if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K - return Retry - - return self.condense - """ + @Command() + def sorb_pump(self): + """start sorb pump followed by condense""" + try: + self.start_machine(self.sorb_pump) + except Exception as e: + self.log.exception('error') + + @Command() + def condense(self): + """condense""" + try: + self.start_machine(self.condense) + except Exception as e: + self.log.exception('error') - @status_code(BUSY, 'start test') - def test(self, state): - """Nur zum testen, ob UI funktioniert""" - self.init = False - if state.init: - state._start = state.now - return self.wait_test @status_code(BUSY) - def wait_test(self, state): - if state.now < state.start + 20: + def sorbpump(self, state): + """ heat up to Tsorb and wait """ + if state.init: + self._start_time = state.now + self.vti_temp.auto_flow = True + self.vti_temp.flowpars = ((1.0, 5.0), (4.0, 20.0)) # increase base flow slighly + self.vti_temp.write_target(6) + self.sorb_temp.write_target(self.sorb_pump_temp) # TODO: make variable return Retry - return self.final_status(IDLE, 'end test') + if state.now - self._start_time < self.sorb_pump_time: + return Retry + self.sorb_temp.write_target(1.25) + self.still_htr.write_target(0) + self.mix_temp.control_off() + self.vti_temp.write_target(1.25) + self.nv_flow.write_target(self.condense_flow) + return self.condense @status_code(BUSY) def condense(self, state): @@ -193,7 +210,7 @@ class Dilution(HasStates, Drivable): self._start_time = state.now self.condense_valve.pulse(pulse_time) - if pdump > self.dump_target: + if pdump > self.dump_low: return Retry self.condense_valve.write_target(1) @@ -222,6 +239,7 @@ class Dilution(HasStates, Drivable): def circulate(self, state): """Zirkuliert die Mischung.""" if state.init: + self.nv_flow.write_target(self.circulate_flow) # self.value = V.circulating self.handle_valves(**self.condense_valves) if self.wait_valves(): @@ -230,11 +248,21 @@ class Dilution(HasStates, Drivable): # self.value = V.circulating return Finish + def _adjust_vti_temp(self): + if self.dump_pressure > self.remove_temp_profile[self._profile_index][0]: + self._profile_index += 1 + self.vti_temp.write_target(self.remove_temp_profile[self._profile_index][1]) + @status_code(BUSY, 'remove (wait for turbo shut down)') def remove(self, state): """Entfernt die Mischung.""" if state.init: + self._profile_index = 0 + self.mix_temp.write_target(self.remove_temp_profile[0][1]) + self.still_htr.write_target(0.004) + self.sorb_temp.write_target(40) + self.vti_temp.auto_flow = True # self.value = V.removing self.handle_valves(**self.remove_valves) if self.turbopump is not None: @@ -242,7 +270,8 @@ class Dilution(HasStates, Drivable): self.turbopump.write_target(0) return Retry - if self.turbopump is not None: + self._adjust_vti_temp() + if self.turbopump is not None: # if (state.now - self._start_time < self.turbo_off_delay or if self.turbopump.read_speed() > self.turbo_off_speed: return Retry @@ -256,6 +285,7 @@ class Dilution(HasStates, Drivable): @status_code(BUSY, 'remove (wait for still pressure low)') def remove_wait_for_still_pressure(self, state): + self._adjust_vti_temp() if self.still_pressure.read_value() > self.safe_turbo_pressure: return Retry self.turbopump.write_target(1) @@ -263,10 +293,12 @@ class Dilution(HasStates, Drivable): @status_code(BUSY) def remove_endsequence(self, state): + self._adjust_vti_temp() if (self.still_pressure_turbo and self.still_pressure_turbo.read_value() > self.end_remove_turbo_pressure): return Retry - if self.still_pressure.read_value() > self.end_remove_pressure: + if self.still_pressure.read_value() > self.end_remove_pressure or ( + self._profile_index < len(self.remove_temp_profile) - 1): return Retry self.circuitshort_valve.write_target(0) self.dump_valve.write_target(0) @@ -453,3 +485,11 @@ class Interlock(LogoMixin, AddrMixin, Readable): self.read_status() +class SorbTemp(HasConvergence, PImixin, ls372.TemperatureChannel): + target = Parameter(datatype=FloatRange(0,unit='K')) + + +class SorbHeater(WrapControlledBy, ls372.SorbHeater): + target = Parameter(datatype=FloatRange(0,unit='K')) + + diff --git a/frappy_psi/dilution_new.py b/frappy_psi/dilution_new.py index c8105eb2..7a05e2f5 100644 --- a/frappy_psi/dilution_new.py +++ b/frappy_psi/dilution_new.py @@ -125,7 +125,7 @@ class Dilution(HasStates, Drivable): raise ImpossibleError(f"failed: {', '.join(result)}") -class DIL4(Dilution): +class DilClassic(Dilution): condenseline_pressure = Attached() # G1 circuitshort_valve = Attached() # V3 still_valve = Attached() # V6 @@ -296,3 +296,329 @@ class DIL4(Dilution): self.handle_valves(**self.valves_after_remove) self._warn_manual_work = True return self.final_status(WARN, 'please check manual valves') + + +class Dilution(HasStates, Drivable): + condenseline_pressure = Attached() + condense_valve = Attached() + dump_valve = Attached() + + forepump = Attached() + compressor = Attached(mandatory=False) + turbopump = Attached(mandatory=False) + condenseline_valve = Attached() + circuitshort_valve = Attached() + still_pressure = Attached() + still_pressure_turbo = Attached(mandatory=False) + + value = Parameter('current state', EnumType(T), default=0) + target = Parameter('target state', EnumType(T), default=0) + + sorbpumped = Parameter('sorb pump done', BoolType(), default=False) + dump_pressure = Attached() + + # ls372 = Attached() + + condensing_p_low = Parameter('Lower limit for condenseline pressure', + FloatRange(unit='mbar'), readonly=False, default=1200) + condensing_p_high = Parameter('Higher limit for condenseline pressure', + FloatRange(unit='mbar'), readonly=False, default=1500) + dump_target = Parameter('low dump pressure limit indicating end of condensation phase', + FloatRange(unit='mbar * min'), readonly=False, default=50) + pulse_factor = Parameter('factor for calculating V9 pulse length', + FloatRange(unit='mbar'), readonly=False, default=20) + end_condense_pressure = Parameter('low condense pressure indicating end of condensation phase', + FloatRange(unit='mbar'), readonly=False, default=500) + turbo_condense_pressure = Parameter('low condense pressure before turbo start', + FloatRange(unit='mbar'), readonly=False, default=900) + safe_turbo_pressure = Parameter('low still pressure before turbo start', + FloatRange(unit='mbar'), readonly=False, default=10) + turbo_off_speed = Parameter('speed to wait for after switching turbo off', + FloatRange(unit='Hz'), readonly=False, default=200) + end_remove_turbo_pressure = Parameter('pressure reached before end of remove (before turbo)', + FloatRange(unit='mbar'), readonly=False, default=1e-4) + end_remove_pressure = Parameter('pressure reached before end of remove (before fore pump)', + FloatRange(unit='mbar'), readonly=False, default=0.02) + st = StringType() + valve_set = StructOf(close=st, open=st, check_open=st, check_closed=st) + condense_valves = Parameter( + 'valve to act when condensing', valve_set, + default={ + 'close': 'V2 V4 V9', + 'check_closed': 'MV10 MV13 MV8 MVB MV2', + 'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14', + 'open': 'V1 V5 compressor forepump', + } + ) + valves_after_remove = Parameter('valve to act after remove', valve_set) + check_after_remove = Parameter('check for manual valves after remove', valve_set) + _start_time = 0 + init = True + _warn_manual_work = None + + def write_target(self, target): + """ + if (target == Targetstates.SORBPUMP): + if self.value == target: + return self.target + self.start_machine(self.sorbpump) + self.value = Targetstates.SORBPUMP + return self.value + """ + self.log.info('start %s', target.name) + if self.value == target: + return target # not sure if this is correct. may be a step wants to be repeated? + + try: + self.start_machine(getattr(self, target.name, None)) + except Exception as e: + self.log.exception('error') + self.log.info('started %s', target.name) + + return target + + """ + @status_code(BUSY, 'sorbpump state') + def sorbpump(self, state): + #Heizt Tsorb auf und wartet ab. + if self.init: + self.ls372.write_target(40) #Setze Tsorb auf 40K + self.start_time = self.now + self.init = false + return Retry + + if self.now - self.start_time < 2400: # 40 Minuten warten + return Retry + + self.ls372.write_target(0) + + if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K + return Retry + + return self.condense + """ + + @status_code(BUSY, 'start test') + def test(self, state): + """Nur zum testen, ob UI funktioniert""" + self.init = False + if state.init: + state._start = state.now + return self.wait_test + + @status_code(BUSY) + def wait_test(self, state): + if state.now < state.start + 20: + return Retry + return self.final_status(IDLE, 'end test') + + @status_code(BUSY) + def condense(self, state): + """Führt das Kondensationsverfahren durch.""" + if state.init: + # self.value = V.condensing + pumpstate = self.forepump.read_value() + if self.turbopump: + self.turbopump.write_target(0) + self.handle_valves(**self.condense_valves) + self._start_time = state.now + if not pumpstate: # wait longer for starting fore pump + self._start_time += 10 + return Retry + if self.wait_valves(): + return Retry + self.check_valve_result() + return self.condensing + + @status_code(BUSY) + def condensing(self, state): + pdump = self.dump_pressure.value # or self.dump_pressure.read_value() ? + pcond = self.condenseline_pressure.read_value() + v9 = self.condense_valve.read_value() + if v9: + if pcond > self.condensing_p_high: + self.log.info('shut V9') + self.condense_valve.write_target(0) + elif pcond < self.condensing_p_low and state.now > self._start_time + 5: + pulse_time = 60 * self.pulse_factor / pdump + if pulse_time > 59: + self.log.info('open V9') + self.condense_valve.write_target(1) + else: + self.log.info('V9 pulse %r', pulse_time) + self._start_time = state.now + self.condense_valve.pulse(pulse_time) + + if pdump > self.dump_target: + return Retry + + self.condense_valve.write_target(1) + + if self.turbopump is not None: + return self.condense_wait_before_turbo_start + + return self.wait_for_condense_line_pressure + + @status_code(BUSY, 'condense (wait before starting turbo)') + def condense_wait_before_turbo_start(self, state): + if (self.condenseline_pressure.read_value() > self.turbo_condense_pressure + and self.still_pressure.read_value() > self.safe_turbo_pressure): + return Retry + self.turbopump.write_target(1) + return self.wait_for_condense_line_pressure + + @status_code(BUSY) + def wait_for_condense_line_pressure(self, state): + if self.condenseline_pressure.read_value() > self.end_condense_pressure: + return Retry + self.condense_valve.write_target(0) + return self.circulate + + @status_code(BUSY) + def circulate(self, state): + """Zirkuliert die Mischung.""" + if state.init: + # self.value = V.circulating + self.handle_valves(**self.condense_valves) + if self.wait_valves(): + return Retry + self.check_valve_result() + # self.value = V.circulating + return Finish + + @status_code(BUSY, 'remove (wait for turbo shut down)') + def remove(self, state): + """Entfernt die Mischung.""" + + if state.init: + # self.value = V.removing + self.handle_valves(**self.remove_valves) + if self.turbopump is not None: + self._start_time = state.now + self.turbopump.write_target(0) + return Retry + + if self.turbopump is not None: + # if (state.now - self._start_time < self.turbo_off_delay or + if self.turbopump.read_speed() > self.turbo_off_speed: + return Retry + + self.circuitshort_valve.write_target(1) + + if self.turbopump is not None: + return self.remove_wait_for_still_pressure + + return self.remove_endsequence + + @status_code(BUSY, 'remove (wait for still pressure low)') + def remove_wait_for_still_pressure(self, state): + if self.still_pressure.read_value() > self.safe_turbo_pressure: + return Retry + self.turbopump.write_target(1) + return self.remove_endsequence + + @status_code(BUSY) + def remove_endsequence(self, state): + if (self.still_pressure_turbo and + self.still_pressure_turbo.read_value() > self.end_remove_turbo_pressure): + return Retry + if self.still_pressure.read_value() > self.end_remove_pressure: + return Retry + self.circuitshort_valve.write_target(0) + self.dump_valve.write_target(0) + + if self.compressor is not None: + self.compressor.write_target(0) + return self.close_valves_after_remove + + @status_code(BUSY) + def close_valves_after_remove(self, state): + if state.init: + self.handle_valves(**self.valves_after_remove) + self.turbopump.write_target(0) + if self.wait_valves(): + return Retry + self.check_valve_result() + self._warn_manual_work = True + # self.value = V.off + return self.final_status(WARN, 'please check manual valves') + + def read_status(self): + status = super().read_status() + if status[0] < ERROR and self._warn_manual_work: + try: + self.handle_valves(**self.check_after_remove) + self._warn_manual_work = False + except ImpossibleError: + return WARN, f'please close manual valves {",".join(self._valves_failed[False])}' + return status + + def handle_valves(self, check_closed=(), check_open=(), close=(), open=()): + """check ot set given valves + + raises ImpossibleError, when checks fails + """ + self._valves_to_wait_for = {} + self._valves_failed = {True: [], False: []} + for flag, valves in enumerate([check_closed, check_open]): + for vname in valves.split(): + if self.secNode.modules[vname].read_value() != flag: + self._valves_failed[flag].append(vname) + for flag, valves in enumerate([close, open]): + for vname in valves.split(): + valve = self.secNode.modules[vname] + valve.write_target(flag) + if valve.isBusy(): + self._valves_to_wait_for[vname] = (valve, flag) + elif valve.read_value() != flag: + self._valves_failed[flag].append(vname) + + def wait_valves(self): + busy = False + for vname, (valve, flag) in dict(self._valves_to_wait_for.items()): + statuscode = valve.read_status()[0] + if statuscode == BUSY: + busy = True + continue + if valve.read_value() == flag and statuscode == IDLE: + self._valves_to_wait_for.pop(vname) + else: + self._valves_failed[flag].append(vname) + return busy + + def check_valve_result(self): + result = [] + for flag, valves in self._valves_failed.items(): + if valves: + result.append(f"{','.join(valves)} not {'open' if flag else 'closed'}") + if result: + raise ImpossibleError(f"failed: {', '.join(result)}") + + +class DIL5(Dilution): + condense_valves = { + 'close': 'V2 V4 V9', + 'check_closed': 'MV10 MV13 MV8 MVB MV2', + 'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14', + 'open': 'V1 V5 compressor forepump', + } + remove_valves = { + 'close': 'V1 V2 V9', + 'check_closed': 'MV10 MV13 MV8 MVB MV2', + 'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14', + 'open': 'V4 V5 compressor forepump', + } + valves_after_remove = { + 'close': 'V1 V2 V4 V5 V9', + 'check_closed': 'MV10 MV13 MV8 MVB MV2', + 'open': '', + 'check_open': '', + } + check_after_remove = { + 'close': '', + 'check_closed': 'MV1 MV9 MV10 MV11 MV12', + 'open': '', + 'check_open': '', + } + +