diff --git a/cfg/main/ori7test_cfg.py b/cfg/main/ori7test_cfg.py index dbba877..4ba7ada 100644 --- a/cfg/main/ori7test_cfg.py +++ b/cfg/main/ori7test_cfg.py @@ -10,7 +10,7 @@ rack = Rack(Mod) rack.lakeshore() rack.sensor('Ts', channel='C', calcurve='x186350') rack.loop('T', channel='B', calcurve='x174786', output_module='htr', target=10) -rack.heater('htr', 1, '100W', 25) +rack.heater('htr', output_no=1, max_heater='100W', resistance=25) rack.he() rack.n2() diff --git a/frappy_psi/ccu4.py b/frappy_psi/ccu4.py index 803c924..511d97f 100644 --- a/frappy_psi/ccu4.py +++ b/frappy_psi/ccu4.py @@ -186,9 +186,9 @@ class N2Level(Base, Readable): mode = Parameter('auto mode', EnumType(A), readonly=False, default=A.manual) threshold = Parameter('threshold triggering start/stop filling', - FloatRange(unit='K'), readonly=False) + FloatRange(unit='K'), readonly=False, default=90) cool_delay = Parameter('max. minutes needed to cool the lower sensor', - FloatRange(unit='s'), readonly=False) + FloatRange(unit='s'), readonly=False, default=30) fill_timeout = Parameter('max. minutes needed to fill', FloatRange(unit='s'), readonly=False) names = Property('''names of attached modules @@ -288,6 +288,64 @@ class N2Level(Base, Readable): self.command(nc=0) +class N2LevelGuess(N2Level): + """guess the current level from hold time""" + value = Parameter('estimated level', FloatRange(unit='%'), default=20) + fill_time = Parameter('min fill time - for raw level indicator', + FloatRange(unit='s'), default=600) + hold_time = Parameter('min hold time - for raw level indicator', + FloatRange(unit='s'), default=24 * 3600) + _full_since = None + _empty_since = None + _fill_state = '' # may also be 'empty', 'full' or 'unknown' + _lower = 0 + _upper = 0 + + def read_status(self): + status = super().read_status() + if status == (IDLE, ''): + return IDLE, self._fill_state + return status + + def read_value(self): + # read sensors + now = time.time() + lower, upper = self.command(nl=float, nu=float) + if self.lower: + self.lower.value = lower + if self.upper: + self.upper.value = upper + if upper < self.threshold: + self._full_since = now + if self._empty_since is not None: + self.fill_time = now - self._empty_since + self._empty_since = None + self._fill_state = 'full' + return 100 + if lower < self.threshold: + if self._empty_since is None: + if self._full_since is None: + self._fill_state = 'unknown' + return 20 + delay = now - self._full_since + value = max(10, 100 * 1 - delay / self.hold_time) + if value < 99: + self._fill_state = '' + return value + delay = now - self._empty_since - self.cool_delay + value = min(90, 100 * max(0, delay / self.fill_time)) + if value >= 10: + self._fill_state = '' + return value + if self._full_since is not None: + self.hold_time = now - self._full_since + self._full_since = None + self.log.info('lower %g upper %g threshold %g', lower, upper, self.threshold) + self._empty_since = now + self._fill_state = 'empty' + return 0 + + class HasFilter: __value1 = None __value = None @@ -326,6 +384,8 @@ class NeedleValveFlow(HasStates, Base, Drivable): use_pressure = Parameter('flag (use pressure instead of flow meter)', BoolType(), readonly=False, default=False) lnm_per_mbar = Parameter('scale factor', FloatRange(unit='lnm/mbar'), readonly=False, default=0.6) + pump_type = Parameter('pump type', EnumType(unknown=0, neodry=1, xds35=2, sv65=3), + readonly=False, value=0) value = Parameter(unit='ln/min') target = Parameter(unit='ln/min') @@ -338,10 +398,17 @@ class NeedleValveFlow(HasStates, Base, Drivable): deriv = Parameter('min progress time constant', FloatRange(unit='s'), default=30, readonly=False) control_active = Parameter('control active flag', BoolType(), readonly=False, default=1) - min_open_pulse = Parameter('minimal open step', FloatRange(0, unit='s'), readonly=False, default=0.02) - min_close_pulse = Parameter('minimal close step', FloatRange(0, unit='s'), readonly=False, default=0.0) + min_open_pulse = Parameter('minimal open step', FloatRange(0, unit='s'), + readonly=False, default=0.02) + min_close_pulse = Parameter('minimal close step', FloatRange(0, unit='s'), + readonly=False, default=0.0) + flow_closed = Parameter('flow when needle valve is closed', FloatRange(unit='ln/min'), + readonly=False, default=0.0) # raw_open_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.12) # raw_close_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.04) + + FLOW_SCALE = {'unknown': 1, 'neodry': 0.55, 'xds35': 0.6, 'sv65': 0.9} + pollinterval = Parameter(datatype=FloatRange(1, unit='s'), default=5) _last_dirchange = 0 _ref_time = 0 @@ -405,9 +472,11 @@ class NeedleValveFlow(HasStates, Base, Drivable): return False def write_target(self, value): - self.log.info('change target') - self.target = value - self.start_machine(self.change_target) + self.log.debug('change target') + self.start_machine(self.change_target, target=value, try_close=True) + + def write_pump_type(self, value): + self.pressure_scale = self.FLOW_SCALE[value.name] def write_prop_open(self, value): self._prop_open = Interpolation(value) @@ -423,46 +492,32 @@ class NeedleValveFlow(HasStates, Base, Drivable): @status_code(BUSY) def change_target(self, sm): + self.target = sm.target sm.last_progress = sm.now sm.ref_time = 0 sm.ref_dif = 0 sm.last_pulse_time = 0 sm.no_progress_pulse = (0.1, -0.05) - self.log.info('target %s value %s', self.target, self._value) - if abs(self.target - self._value) < self._tolerance(self._value): - self.log.info('go to at_target') + self.log.debug('target %s value %s', self.target, self._value) + tol = self._tolerance(self.target) + if abs(self.target - self._value) < tol: + self.log.debug('go to at_target') return self.at_target - self.log.info('go to controlling') + self.log.debug('go to controlling') return self.controlling - def filtered(self, n=60, m=5, nsigma=2): - """return mean and tolerance, augmented by noise""" - # TODO: better idea: use median over last minute and last value and treat them both - n = len(self._last[-n:]) - mean = np.median(self._last[-m:]) - tol = self._tolerance(mean) - span = 0 - if len(self._last) >= n + m: - # get span over the last n points - span = max(self._last[-n:]) - min(self._last[-n:]) - slope = mean - np.median(self._last[-n-m:-n]) - # in case there is a slope, subtract it - tol = math.sqrt(tol ** 2 + max(0, span-abs(slope)) ** 2) - self.log.info('filt %d %d %d %g %g', len(self._last), n, m, self._value, span) - m = min(m, n) - narr = np.array(self._last[-n:]) - mdif = np.median(np.abs(narr[1:-1] - 0.5 * (narr[:-2] + narr[2:]))) - return mean, tol + def _dif_medians(self): + return np.array([self.target - np.median(self._last[-m:]) for m in (1, 5, 12, 30, 60)]) @status_code(BUSY) def controlling(self, sm): tol = self._tolerance(self.target) - dif = np.array([self.target - np.median(self._last[-m:]) for m in (1,5,60)]) + dif = self._dif_medians() if sm.init: - self.log.info('restart controlling') + self.log.debug('restart controlling') direction = math.copysign(1, dif[1]) if direction != self._dir: - self.log.info('new dir %g dif=%g', direction, dif[1]) + self.log.debug('new dir %g dif=%g', direction, dif[1]) self._dir = direction self._last_dirchange = sm.now sm.ref_dif = abs(dif[1]) @@ -474,17 +529,25 @@ class NeedleValveFlow(HasStates, Base, Drivable): if np.all(difdir < tol): if np.all(difdir < -tol): - self.log.info('overshoot %r', dif) + self.log.debug('overshoot %r', dif) return self.controlling - # within tolerance - self.log.info('at target %r tol %g', dif, tol) - return self.at_target + if not np.any(difdir < -tol): + # within tolerance + self.log.debug('at target %r tol %g', dif, tol) + return self.at_target if np.all(difdir > expected_dif): # not enough progress if sm.now > sm.last_progress + self.deriv: + lim = self.flow_closed + 0.5 + if sm.try_close and self._value <= lim - tol and self.target >= lim + tol: + sm.try_close = False + self.command(mp=-60) + sm.after_close = self.open_until_flow_increase + self.log.debug('go to closing / open_until_flow_increase') + return self.closing if sm.no_progress_pulse: pulse = abs(sm.no_progress_pulse[self._dir < 0]) * self._dir - self.log.info('not enough progress %g', pulse) + self.log.debug('not enough progress %g %r', pulse, sm.try_close) self.pulse(pulse) sm.last_progress = sm.now if sm.now < sm.last_pulse_time + 2.5: @@ -508,7 +571,7 @@ class NeedleValveFlow(HasStates, Base, Drivable): else: step = minstep * difd / tol step *= self._dir - self.log.info('MP %g dif=%g tol=%g', step, difd * self._dir, tol) + self.log.debug('MP %g dif=%g tol=%g', step, difd * self._dir, tol) self.command(mp=step) self._speed_sum += step return Retry @@ -524,8 +587,9 @@ class NeedleValveFlow(HasStates, Base, Drivable): @status_code(IDLE) def at_target(self, sm): tol = self._tolerance(self.target) - dif = np.array([self.target - np.median(self._last[-m:]) for m in (1,5,60)]) + dif = self._dif_medians() if np.all(dif > tol) or np.all(dif < -tol): + self.log.debug('unstable %r %g', dif, tol) return self.unstable return Retry @@ -542,7 +606,7 @@ class NeedleValveFlow(HasStates, Base, Drivable): """close valve fully""" self.command(mp=-60) self.motor_state = self.command(fm=int) - self.start_machine(self.closing, fast_poll=0.1) + self.start_machine(self.closing, after_close=None, fast_poll=0.1) @status_code(BUSY) def closing(self, sm): @@ -553,6 +617,8 @@ class NeedleValveFlow(HasStates, Base, Drivable): if self.motor_state == M.closing: return Retry if self.motor_state == M.closed: + if sm.after_close: + return sm.after_close return self.final_status(IDLE, 'closed') if sm.now < sm.start_time + 1: return Retry @@ -580,14 +646,24 @@ class NeedleValveFlow(HasStates, Base, Drivable): return self.final_status(IDLE, 'fixed') @Command - def lim_pulse(self): - """try to open until pressure increases""" - p = self.command(f=float) - self.start_machine(self.lim_open, threshold=0.5, - prev=[p], ref=p, fast_poll=0.1, cnt=0) + def close_test(self): + """close and then try to open until the flow starts to increase + + save a + """ + + self.command(mp=-60) + self.start_machine(self.closing, fast_poll=0.1, after_close=self.open_until_flow_increase, target=0) @status_code(BUSY) - def lim_open(self, sm): + def open_until_flow_increase(self, sm): + if sm.init: + p = self.command(f=float) + sm.threshold = 0.5 + sm.prev = [p] + sm.ref = p + sm.cnt = 0 + sm.low_flow = 0 self.read_motor_state() if self.motor_state == M.opening: return Retry @@ -598,16 +674,20 @@ class NeedleValveFlow(HasStates, Base, Drivable): if press > sm.ref + 0.2: sm.cnt += 1 if sm.cnt > 5 or press > sm.ref + 0.5: - self.log.info('flow increased %g', press) - return self.final_status(IDLE, 'flow increased') - self.log.info('wait count %g', press) + self.flow_closed = sm.low_flow + self.log.debug('flow increased %g', press) + if sm.target == 0: + sm.target = sm.low_flow + 0.5 + return self.change_target + self.log.debug('wait count %g', press) return Retry + sm.low_flow = self.value sm.cnt = 0 last5 = sm.prev[-5:] median = sorted(last5)[len(last5) // 2] if press > median: # avoid to pulse again after an even small increase - self.log.info('wait %g', press) + self.log.debug('wait %g', press) return Retry sm.ref = min(sm.prev[0], median) if measured: @@ -616,10 +696,10 @@ class NeedleValveFlow(HasStates, Base, Drivable): sm.threshold = round(sm.threshold * 1.1, 2) elif measured > 0.3: sm.threshold = round(sm.threshold * 0.9, 2) - self.log.info('measured %g new threshold %g press %g', measured, sm.threshold, press) + self.log.debug('measured %g new threshold %g press %g', measured, sm.threshold, press) else: self._speed_sum += 1 - self.log.info('full pulse') + self.log.debug('full pulse') sm.cnt = 0 self.command(mft=sm.ref + sm.threshold, mp=1) return Retry @@ -679,7 +759,7 @@ class NeedleValveFlow(HasStates, Base, Drivable): prevmin = min(sm.last) sm.last.append(v) del sm.last[:-n] - self.log.info('unstable %g >? %g ? %g prevmax + tol: return 1 if v < prevmin - tol: @@ -717,8 +797,6 @@ class NeedleValveFlow(HasStates, Base, Drivable): def auto_close(self, sm): if not self.is_stable(sm, 10, 0.01): return Retry - self.log.info('before %g pulse %g, flowstep %g', sm.flow_before, sm.open_pulse, sm.last[-1] - sm.flow_before) + self.log.debug('before %g pulse %g, flowstep %g', sm.flow_before, sm.open_pulse, sm.last[-1] - sm.flow_before) self.close() return self.final_status(IDLE, '') - - \ No newline at end of file diff --git a/frappy_psi/hepump.py b/frappy_psi/hepump.py index 5ef6358..b88a44b 100644 --- a/frappy_psi/hepump.py +++ b/frappy_psi/hepump.py @@ -30,16 +30,13 @@ class ValveMotor(Motor): class HePump(Writable): valvemotor = Attached(Motor) - flow = Attached(NeedleValveFlow) valve = Attached(Writable) value = Parameter(datatype=BoolType()) target = Parameter(datatype=BoolType()) - pump_type = Parameter('pump type', EnumType(no=0, neodry=1, xds35=2, sv65=3), readonly=False, default=0) + pump_type = NeedleValveFlow.pump_type eco_mode = Parameter('eco mode', BoolType(), readonly=False) has_feedback = Parameter('feedback works', BoolType(), readonly=False, default=True) - FLOW_SCALE = {'no': 0, 'neodry': 0.55, 'xds35': 0.6, 'sv65': 0.9} - def write_target(self, value): self.valvemotor.write_output0(value) @@ -51,9 +48,6 @@ class HePump(Writable): return not self.valvemotor.read_input3() return self.target - def write_pump_type(self, value): - self.flow.pressure_scale = self.FLOW_SCALE[value.name] - def read_eco_mode(self): if self.pump_type == 'xds35': return self.valvemotor.read_output1()