From 9c7b6aeb943ab45a43b3a67287f115f847873e3e Mon Sep 17 00:00:00 2001 From: zebra Date: Tue, 7 Jun 2022 11:45:25 +0200 Subject: [PATCH] various fixes on mb11/dil5 --- cfg/main/mb11.cfg | 38 ++++++++++----- cfg/stick/dil5.cfg | 33 ++++++++----- secop_psi/magfield.py | 5 +- secop_psi/mercury.py | 108 +++++++++++++++++++++++++++++++++--------- secop_psi/triton.py | 19 +++++--- 5 files changed, 146 insertions(+), 57 deletions(-) diff --git a/cfg/main/mb11.cfg b/cfg/main/mb11.cfg index 6c082e1..0f3db0e 100644 --- a/cfg/main/mb11.cfg +++ b/cfg/main/mb11.cfg @@ -21,11 +21,13 @@ description = IPS for magnet and levels uri = mb11-ts:3003 [T_stat] -class = secop_psi.mercury.TemperatureLoop +class = secop_psi.mercury.TemperatureAutoFlow description = static heat exchanger temperature output_module = htr_stat +needle_valve = p_stat slot = DB6.T1,DB1.H1 io = itc1 +tolerance = 0.1 [htr_stat] class = secop_psi.mercury.HeaterOutput @@ -37,8 +39,11 @@ io = itc1 class = secop_psi.mercury.PressureLoop description = static needle valve pressure output_module = pos_stat +settling_time = 60 slot = DB5.P1,DB3.G1 io = itc1 +tolerance = 1 +value.unit = mbar_flow [pos_stat] class = secop_psi.mercury.ValvePos @@ -47,11 +52,13 @@ slot = DB5.P1,DB3.G1 io = itc1 [T_dyn] -class = secop_psi.mercury.TemperatureLoop +class = secop_psi.mercury.TemperatureAutoFlow description = dynamic heat exchanger temperature output_module = htr_dyn +needle_valve = p_dyn slot = DB7.T1,DB2.H1 io = itc1 +tolerance = 0.1 [htr_dyn] class = secop_psi.mercury.HeaterOutput @@ -63,8 +70,11 @@ io = itc1 class = secop_psi.mercury.PressureLoop description = dynamic needle valve pressure output_module = pos_dyn +settling_time = 60 slot = DB8.P1,DB4.G1 io = itc1 +tolerance = 1 +value.unit = mbar_flow [pos_dyn] class = secop_psi.mercury.ValvePos @@ -90,6 +100,7 @@ description = neck heater 1 temperature output_module = htr_neck1 slot = MB1.T1,MB0.H1 io = itc2 +tolerance = 1 [htr_neck1] class = secop_psi.mercury.HeaterOutput @@ -103,6 +114,7 @@ description = neck heater 2 temperature output_module = htr_neck2 slot = DB6.T1,DB1.H1 io = itc2 +tolerance = 1 [htr_neck2] class = secop_psi.mercury.HeaterOutput @@ -116,6 +128,7 @@ description = static needle valve temperature output_module = htr_nvs slot = DB7.T1,DB2.H1 io = itc2 +tolerance = 0.1 [htr_nvs] class = secop_psi.mercury.HeaterOutput @@ -129,6 +142,7 @@ description = dynamic needle valve heater temperature output_module = htr_nvd slot = DB8.T1,DB3.H1 io = itc2 +tolerance = 0.1 [htr_nvd] class = secop_psi.mercury.HeaterOutput @@ -149,15 +163,15 @@ slot = GRPZ io = ips target.max = 11 -#[om_io] -#description = dom motor IO -#class = secop_psi.phytron.PhytronIO -#uri = mb11-ts.psi.ch:3004 +[om_io] +description = dom motor IO +class = secop_psi.phytron.PhytronIO +uri = mb11-ts.psi.ch:3004 -#[om] -#description = stick rotation, typically used for omega -#class = secop_psi.phytron.Motor -#io = om_io -#sign = -1 -#encoder_mode = NO +[om] +description = stick rotation, typically used for omega +class = secop_psi.phytron.Motor +io = om_io +sign = -1 +encoder_mode = NO diff --git a/cfg/stick/dil5.cfg b/cfg/stick/dil5.cfg index 05d70a1..c52793f 100644 --- a/cfg/stick/dil5.cfg +++ b/cfg/stick/dil5.cfg @@ -10,11 +10,18 @@ class = secop_psi.mercury.IO description = connection to triton software uri = tcp://linse-dil5:33576 -[action] -class = secop_psi.triton.Action -description = higher level scripts +#[ts] +#class = secop_psi.switching_sensor.Sensor +#description = either T_mix or T_mix_wup, depending on T +#lower = T_mix +#upper = T_mix_wup +#switch_range = (1.5, 4) + +[ts] +class = secop_psi.triton.TemperatureSensor +description = mix. chamber temperature +slot = T5 io = triton -slot = DR [T_sorb] class = secop_psi.triton.TemperatureSensor @@ -40,11 +47,11 @@ description = cold plate temperature slot = T4 io = triton -[T_mix] -class = secop_psi.triton.TemperatureSensor -description = mix. chamber temperature -slot = T5 +[action] +class = secop_psi.triton.Action +description = higher level scripts io = triton +slot = DR [p_dump] class = secop_psi.mercury.PressureSensor @@ -87,7 +94,7 @@ description = still warmup temperature slot = MB1.T1 io = itc -[P_still_wup] +[htr_still_wup] class = secop_psi.mercury.HeaterOutput description = still warmup heater slot = MB0.H1 @@ -99,7 +106,7 @@ description = 1 K plate warmup temperature slot = DB5.T1 io = itc -[P_one_K] +[htr_one_K] class = secop_psi.mercury.HeaterOutput description = 1 K plate warmup heater slot = DB3.H1 @@ -111,7 +118,7 @@ description = mix. chamber warmup temperature slot = DB6.T1 io = itc -[P_mix_wup] +[htr_mix_wup] class = secop_psi.mercury.HeaterOutput description = mix. chamber warmup heater slot = DB1.H1 @@ -123,7 +130,7 @@ description = IVC warmup temperature slot = DB7.T1 io = itc -[P_ivc_wup] +[htr_ivc_wup] class = secop_psi.mercury.HeaterOutput description = IVC warmup heater slot = DB2.H1 @@ -135,7 +142,7 @@ description = condenser temperature slot = DB8.T1 io = itc -[P_cond] +[htr_cond] class = secop_psi.mercury.HeaterOutput description = condenser heater slot = DB3.H1 diff --git a/secop_psi/magfield.py b/secop_psi/magfield.py index 7858505..2a97cb7 100644 --- a/secop_psi/magfield.py +++ b/secop_psi/magfield.py @@ -225,12 +225,13 @@ class Magfield(HasLimits, Drivable): def stabilize_field(self, state): """stabilize field""" + self.persistent_field = self.value if state.now - state.stabilize_start < self.wait_stable_field: if state.init: self.status = Status.STABILIZING, 'stabilizing field' - self.persistent_field = self.value return Retry() - self.persistent_field = state.set_point + if abs(self.value - state.set_point) < self.tolerance: + self.persistent_field = state.set_point return self.check_switch_off def check_switch_off(self, state): diff --git a/secop_psi/mercury.py b/secop_psi/mercury.py index c05904e..6dc78bc 100644 --- a/secop_psi/mercury.py +++ b/secop_psi/mercury.py @@ -27,7 +27,7 @@ import time from secop.core import Drivable, HasIO, Writable, \ Parameter, Property, Readable, StringIO, Attached, Done, IDLE, nopoll -from secop.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType +from secop.datatypes import EnumType, FloatRange, StringType, StructOf, BoolType, TupleOf from secop.errors import HardwareError from secop_psi.convergence import HasConvergence from secop.lib.enum import Enum @@ -172,26 +172,29 @@ class TemperatureSensor(MercuryChannel, Readable): class HasInput(MercuryChannel): controlled_by = Parameter('source of target value', EnumType(members={'self': SELF}), default=0) - target = Parameter(readonly=False) - input_modules = () + # do not know why this? target = Parameter(readonly=False) + input_callbacks = () - def add_input(self, modobj): - if not self.input_modules: - self.input_modules = [] - self.input_modules.append(modobj) + def register_input(self, name, control_off): + """register input + + :param name: the name of the module (for controlled_by enum) + :param control_off: a method on the input module to switch off control + """ + if not self.input_callbacks: + self.input_callbacks = [] + self.input_callbacks.append(control_off) prev_enum = self.parameters['controlled_by'].datatype._enum # add enum member, using autoincrement feature of Enum - self.parameters['controlled_by'].datatype = EnumType(Enum(prev_enum, **{modobj.name: None})) + self.parameters['controlled_by'].datatype = EnumType(Enum(prev_enum, **{name: None})) def write_controlled_by(self, value): if self.controlled_by == value: return Done self.controlled_by = value if value == SELF: - self.log.warning('switch to manual mode') - for input_module in self.input_modules: - if input_module.control_active: - input_module.write_control_active(False) + for control_off in self.input_callbacks: + control_off() return Done @@ -209,12 +212,17 @@ class Loop(HasConvergence, MercuryChannel, Drivable): def initModule(self): super().initModule() if self.output_module: - self.output_module.add_input(self) + self.output_module.register_input(self.name, self.control_off) + + def control_off(self): + if self.control_active: + self.log.warning('switch to manual mode') + self.write_control_active(False) def set_output(self, active): if active: if self.output_module and self.output_module.controlled_by != self.name: - self.output_module.controlled_by = self.name + self.output_module.write_controlled_by(self.name) else: if self.output_module and self.output_module.controlled_by != SELF: self.output_module.write_controlled_by(SELF) @@ -340,7 +348,6 @@ class TemperatureLoop(TemperatureSensor, Loop, Drivable): ramp = Parameter('ramp rate', FloatRange(0, unit='K/min'), readonly=False) enable_ramp = Parameter('enable ramp rate', BoolType(), readonly=False) setpoint = Parameter('working setpoint (differs from target when ramping)', FloatRange(0, unit='$')) - auto_flow = Parameter('enable auto flow', BoolType(), readonly=False) tolerance = Parameter(default=0.1) _last_setpoint_change = None @@ -394,11 +401,16 @@ class TemperatureLoop(TemperatureSensor, Loop, Drivable): def write_enable_ramp(self, value): return self.change('TEMP:LOOP:RENA', value, off_on) - def read_auto_flow(self): - return self.query('TEMP:LOOP:FAUT', off_on) - - def write_auto_flow(self, value): - return self.change('TEMP:LOOP:FAUT', value, off_on) + def set_output(self, active): + if active: + if self.output_module and self.output_module.controlled_by != self.name: + self.output_module.write_controlled_by(self.name) + else: + if self.output_module and self.output_module.controlled_by != SELF: + self.output_module.write_controlled_by(SELF) + status = IDLE, 'control inactive' + if self.status != status: + self.status = status def read_ramp(self): result = self.query('TEMP:LOOP:RSET') @@ -450,7 +462,7 @@ class ValvePos(HasInput, MercuryChannel, Drivable): return self.change('PRES:LOOP:FSET', value) -class PressureLoop(PressureSensor, Loop, Drivable): +class PressureLoop(HasInput, PressureSensor, Loop, Drivable): channel_type = 'PRES,AUX' output_module = Attached(ValvePos, mandatory=False) tolerance = Parameter(default=0.1) @@ -467,12 +479,62 @@ class PressureLoop(PressureSensor, Loop, Drivable): def read_target(self): return self.query('PRES:LOOP:PRST') + def set_target(self, value): + """set the target without switching to manual + + might be used by a software loop + """ + self.change('PRES:LOOP:PRST', value) + super().set_target(value) + def write_target(self, value): - target = self.change('PRES:LOOP:PRST', value) - self.set_target(target) + self.write_controlled_by(SELF) + self.set_target(value) return Done +class HasAutoFlow: + needle_valve = Attached(PressureLoop, mandatory=False) + auto_flow = Parameter('enable auto flow', BoolType(), readonly=False, default=0) + flowpars = Parameter('Tdif(min, max), FlowSet(min, max)', + TupleOf(TupleOf(FloatRange(unit='K'), FloatRange(unit='K')), + TupleOf(FloatRange(unit='mbar'), FloatRange(unit='mbar'))), + readonly=False, default=((1,5), (4,20))) + + def read_value(self): + value = super().read_value() + if self.auto_flow: + (dmin, dmax), (fmin, fmax) = self.flowpars + flowset = min(dmax - dmin, max(0, value - self.target - dmin)) / (dmax - dmin) * (fmax - fmin) + fmin + self.needle_valve.set_target(flowset) + return Done + + def initModule(self): + super().initModule() + if self.needle_valve: + self.needle_valve.register_input(self.name, self.auto_flow_off) + + def write_auto_flow(self, value): + if value: + if self.needle_valve and self.needle_valve.controlled_by != self.name: + self.needle_valve.write_controlled_by(self.name) + else: + if self.needle_valve and self.needle_valve.controlled_by != SELF: + self.needle_valve.write_controlled_by(SELF) + _, (fmin, _) = self.flowpars + self.needle_valve.write_target(fmin) + return value + + def auto_flow_off(self): + if self.auto_flow: + self.log.warning('switch auto flow off') + self.write_auto_flow(False) + + +class TemperatureAutoFlow(HasAutoFlow, TemperatureLoop): + pass + + class HeLevel(MercuryChannel, Readable): """He level meter channel diff --git a/secop_psi/triton.py b/secop_psi/triton.py index dbf8e9d..5ed6d4f 100644 --- a/secop_psi/triton.py +++ b/secop_psi/triton.py @@ -29,7 +29,8 @@ import secop_psi.mercury as mercury actions = Enum(none=0, condense=1, circulate=2, collect=3) open_close = Mapped(CLOSE=False, OPEN=True) -actions_map = Mapped(NONE=actions.none, COND=actions.condense, COLL=actions.collect) +actions_map = Mapped(STOP=actions.none, COND=actions.condense, COLL=actions.collect) +actions_map.mapping['NONE'] = actions.none # when writing, STOP is used instead of NONE class Action(MercuryChannel, Writable): @@ -49,15 +50,18 @@ class Action(MercuryChannel, Writable): return self.change('SYS:DR:ACTN', value, actions_map) # actions: + # NONE (no action) + # COND (condense mixture) + # COLL (collect mixture) + # STOP (go to NONE) + # + # not yet used (would need a subclass of Action): # CLDN (cool down) # PCL (precool automation) - # COND (condense mixture) - # PCOND (pause pre-cool (not condense?) automation - # RCOND (resume pre-cool (not condense?) automation + # PCOND (pause pre-cool (not condense?) automation) + # RCOND (resume pre-cool (not condense?) automation) # WARM (warm-up) - # COLL (collect mixture) # EPCL (empty pre-coll automation) - # STOP class Valve(MercuryChannel, Writable): @@ -173,6 +177,7 @@ class FlowMeter(MercuryChannel, Readable): class TemperatureSensor(mercury.TemperatureSensor): # TODO: excitation, enable + # TODO: switch on/off filter, check filter_time = Parameter('filter time', FloatRange(1, 200, unit='sec'), readonly=False) dwell_time = Parameter('dwell time', FloatRange(1, 200, unit='sec'), readonly=False) pause_time = Parameter('pause time', FloatRange(3, 200, unit='sec'), readonly=False) @@ -199,4 +204,4 @@ class TemperatureSensor(mercury.TemperatureSensor): class TemperatureLoop(mercury.TemperatureLoop): - pass # TODO: switch on/off filter, check + pass