# ***************************************************************************** # # 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: # Andrea Plank # Anik Stark # # ***************************************************************************** from frappy.core import Drivable, Parameter, Attached, FloatRange, \ IDLE, BUSY, WARN, ERROR from frappy.datatypes import EnumType, BoolType, StructOf, StringType from frappy.states import Retry, Finish, status_code, HasStates from frappy.lib.enum import Enum from frappy.errors import ImpossibleError from frappy.lib.mathparser import MathParser T = Enum( # target states off = 0, sorbpump = 2, condense = 5, remove = 7, remove_and_sorbpump = 9, remove_and_condense = 10, manual = 11, test = 12, ) V = Enum(T, # value status inherits from target status sorbpumping=1, condensing=4, circulating=6, removing=8, ) class Dilution(HasStates, Drivable): condenseline_pressure = Attached() # G1 condense_valve = Attached() # V1 dump_valve = Attached() # V9 forepump = Attached() # rotary_pump_He3 (24) condenseline_valve = Attached() # V1 circuitshort_valve = Attached() # V3 still_valve = Attached() # V6 pumpout_valve = Attached() # V14 still_pressure = Attached() # P1 dump_pressure = Attached() # G3 oneK_temp = Attached() still_temp = Attached() mix_temp = Attached() value = Parameter('current state', EnumType(V), default=0) target = Parameter('target state', EnumType(T), default=0) sorbpumped = Parameter('sorb pump done', BoolType(), default=False, readonly=False) sorb_cond = Parameter('sorb condition', StringType(), default='oneK>4', readonly=False) sorb_pump_time = Parameter('sorb pump time', FloatRange(), default=2400, readonly=False) 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 pump out 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=50) end_remove_pressure = Parameter('pressure reached before end of remove (before fore pump)', FloatRange(unit='mbar'), readonly=False, default=0.02) condensing_p_low = Parameter('when to start pumping dump', datatype=FloatRange(unit='mbar'), default=500, readonly=False) 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) valves_after_remove = Parameter('valve to act after remove', valve_set) check_after_remove = Parameter('check for manual valves after remove', valve_set) init = True _start_time = 0 _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 try: self.start_machine(getattr(self, target.name, None)) except Exception as e: self.log.exception('error %s', e) self.log.info('started %s', target.name) return target @status_code(BUSY, 'sorbpump state') def sorbpump(self, state): """ heat up to Tsorb and wait """ if state.init: #self.ls372.write_target(40) # set Tsorb to 40K self.start_time = state.now self.handle_valves(**self.condense_valves) return Retry parser = MathParser(oneK=self.oneK_temp.value, still=self.still_temp.value, mix=self.mix_temp.value) if parser.calculate(self.sorb_cond): self.start_time = state.now if state.now - self.start_time < self.sorb_pump_time: return Retry return self.condense @status_code(BUSY) def condense(self, state): """ condense process """ if state.init: # self.value = V.condensing self.handle_valves(**self.condense_valves) self.still_valve.write_target(100) self._start_time = state.now return Retry pdump = self.dump_pressure.value pcond = self.condenseline_pressure.read_value() if pcond < self.condensing_p_low and state.now > self._start_time + 5: pulse_time = 60 * self.pulse_factor / pdump if pulse_time > 59: pulse_time = 3600 self.pumpout_valve.delay = pulse_time self.pumpout_valve.write_target(1) if pdump > self.dump_target: return Retry 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.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.handle_valves(**self.remove_valves) return Retry self.circuitshort_valve.write_target(1) return self.remove_endsequence @status_code(BUSY) def remove_endsequence(self, state): if self.still_pressure.read_value() > self.end_remove_pressure: return Retry self.circuitshort_valve.write_target(0) self.dump_valve.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._warn_manual_work = True 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 DIL4(Dilution): condense_valves = { 'close': 'V2 V3 V4 V7 V8 V10 V11A V12A V13A', 'check_closed': '', 'check_open': '', 'open': 'V1 V5 V9', } remove_valves = { 'close': '', 'check_closed': '', 'check_open': '', 'open': '', } valves_after_remove = { 'close': '', 'check_closed': '', 'open': '', 'check_open': '', } check_after_remove = { 'close': '', 'check_closed': '', 'open': '', 'check_open': '', }