From 766f15beee26daa7bbd3f567896c27083aa98b4d Mon Sep 17 00:00:00 2001 From: l_samenv Date: Wed, 21 Dec 2022 10:57:41 +0100 Subject: [PATCH] improvements in magfield - use HasTargetLimits instead of HasLimits - move ramp_tmo parameter to SompleMagfield - add last_target method - fix progress check in SimpleMagfield.ramp_to_target - better mechanism for setting to persistent mode after restart - fix switching mode - fix on_error - fix condition for shortcut start_field_change -> check_switch_off - remove direct status updates - move check for manual switch heater operations to ips_mercury --- secop_psi/ips_mercury.py | 18 ++++++++ secop_psi/magfield.py | 98 +++++++++++++++++++++------------------- 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/secop_psi/ips_mercury.py b/secop_psi/ips_mercury.py index 84758c5..4882eec 100644 --- a/secop_psi/ips_mercury.py +++ b/secop_psi/ips_mercury.py @@ -288,6 +288,24 @@ class Field(SimpleField, Magfield): return Retry return sm.after_wait + def wait_for_switch_on(self, sm): + self.read_switch_heater() # trigger switch_on/off_time + if self.switch_heater == self.switch_heater.OFF: + if sm.init: # avoid too many states chained + return Retry + self.log.warning('switch turned off manually?') + return self.start_switch_on + return super().wait_for_switch_on(sm) + + def wait_for_switch_off(self, sm): + self.read_switch_heater() + if self.switch_heater == self.switch_heater.ON: + if sm.init: # avoid too many states chained + return Retry + self.log.warning('switch turned on manually?') + return self.start_switch_off + return super().wait_for_switch_off(sm) + def start_ramp_to_zero(self, sm): try: assert self.write_action(Action.hold) == Action.hold diff --git a/secop_psi/magfield.py b/secop_psi/magfield.py index 4c04136..5f7a82c 100644 --- a/secop_psi/magfield.py +++ b/secop_psi/magfield.py @@ -22,7 +22,7 @@ import time from secop.core import Drivable, Parameter, Done, IDLE, BUSY, ERROR from secop.datatypes import FloatRange, EnumType, ArrayOf, TupleOf, StatusType -from secop.features import HasLimits +from secop.features import HasTargetLimits from secop.errors import ConfigError, ProgrammingError, HardwareError, BadValueError from secop.lib.enum import Enum from secop.states import Retry, HasStates, status_code, Start @@ -48,7 +48,7 @@ OFF = 0 ON = 1 -class SimpleMagfield(HasStates, HasLimits, Drivable): +class SimpleMagfield(HasStates, HasTargetLimits, Drivable): value = Parameter('magnetic field', datatype=FloatRange(unit='T')) ramp = Parameter( 'wanted ramp rate for field', FloatRange(unit='$/min'), readonly=False) @@ -63,6 +63,9 @@ class SimpleMagfield(HasStates, HasLimits, Drivable): readonly=False, default=(0, 0)) wait_stable_field = Parameter( 'wait time to ensure field is stable', FloatRange(0, unit='s'), readonly=False, default=31) + ramp_tmo = Parameter( + 'timeout for field ramp progress', + FloatRange(0, unit='s'), readonly=False, default=30) _last_target = None @@ -80,6 +83,23 @@ class SimpleMagfield(HasStates, HasLimits, Drivable): # let the state machine do the needed steps to finish self.write_target(self.value) + def last_target(self): + """get best known last target + + as long as the guessed last target is within tolerance + with repsect to the main value, it is used, as in general + it has better precision + """ + last = self._last_target + if last is None: + try: + last = self.setpoint # get read back from HW, if available + except Exception: + pass + if last is None or abs(last - self.value) > self.tolerance: + return self.value + return last + def write_target(self, target): self.check_limits(target) self.start_machine(self.start_field_change, target=target) @@ -108,9 +128,10 @@ class SimpleMagfield(HasStates, HasLimits, Drivable): self.init_progress(sm, self.value) # Remarks: assume there is a ramp limiting feature if abs(self.value - sm.target) > self.tolerance: - if self.get_progress(sm, self.value): - return Retry - raise HardwareError('no progress') + if self.get_progress(sm, self.value) > self.ramp_tmo: + raise HardwareError('no progress') + sm.stabilize_start = None # force reset + return Retry sm.stabilize_start = time.time() return self.stabilize_field @@ -127,7 +148,7 @@ class SimpleMagfield(HasStates, HasLimits, Drivable): class Magfield(SimpleMagfield): status = Parameter(datatype=StatusType(Status)) mode = Parameter( - 'persistent mode', EnumType(Mode), readonly=False, default=Mode.PERSISTENT) + 'persistent mode', EnumType(Mode), readonly=False, initwrite=False, default=Mode.PERSISTENT) switch_heater = Parameter('switch heater', EnumType(off=OFF, on=ON), readonly=False, default=0) current = Parameter( @@ -152,26 +173,15 @@ class Magfield(SimpleMagfield): leads_ramp_tmo = Parameter( 'timeout for leads ramp progress', FloatRange(0, unit='s'), readonly=False, default=30) - ramp_tmo = Parameter( - 'timeout for field ramp progress', - FloatRange(0, unit='s'), readonly=False, default=30) - __init_persistency = True + init_persistency = True switch_on_time = None switch_off_time = None def doPoll(self): - if self.__init_persistency: - if self.__init_persistency is True: - self._last_target = self.value - self.__init_persistency = time.time() + 60 - self.read_value() # check for persistent field mismatch - elif self.read_switch_heater() and self.mode != Mode.DRIVEN: - if time.time() > self.__init_persistency: - # switch off heater from previous live or manual intervention - self.log.info('fix mode after startup') - self.write_mode(self.mode) - else: - self.__init_persistency = False + if self.init_persistency: + if self.read_switch_heater() and self.mode != Mode.DRIVEN: + self.start_machine(self.go_persistent_soon, mode=self.mode) + self.init_persistency = False super().doPoll() def initModule(self): @@ -179,8 +189,8 @@ class Magfield(SimpleMagfield): self.registerCallbacks(self) # for update_switch_heater def write_mode(self, value): - self.__init_persistency = False - target = self.value + self.init_persistency = False + target = self.last_target() func = self.start_field_change if value == Mode.DISABLED: target = 0 @@ -188,11 +198,12 @@ class Magfield(SimpleMagfield): func = self.start_switch_off elif value == Mode.PERSISTENT: func = self.start_switch_off + self.target = target self.start_machine(func, target=target, mode=value) return value def write_target(self, target): - self.__init_persistency = False + self.init_persistency = False if self.mode == Mode.DISABLED: if target == 0: return 0 @@ -208,14 +219,22 @@ class Magfield(SimpleMagfield): if sm.mode != Mode.DRIVEN: self.log.warning('turn switch heater off') self.write_switch_heater(OFF) - return self.on_error(sm) + return super().on_error(sm) + + @status_code(Status.WARN) + def go_persistent_soon(self, sm): + if sm.delta(60): + self.target = sm.target = self.last_target() + return self.start_field_change + return Retry @status_code(Status.PREPARING) def start_field_change(self, sm): self.setFastPoll(True, 1.0) - if sm.target == self.value or ( - sm.target == self._last_target and - abs(sm.target - self.value) <= self.tolerance): # short cut + if (sm.target == self.last_target() and + abs(sm.target - self.value) <= self.tolerance and + abs(self.current - self.value) < self.tolerance and + (self.mode != Mode.DRIVEN or self.switch_heater == ON)): # short cut return self.check_switch_off if self.switch_heater == ON: return self.start_switch_on @@ -267,28 +286,19 @@ class Magfield(SimpleMagfield): @status_code(Status.PREPARING) def start_switch_on(self, sm): + if (sm.target == self.last_target() and + abs(sm.target - self.value) <= self.tolerance): # short cut + return self.check_switch_off if self.read_switch_heater() == OFF: - self.status = Status.PREPARING, 'turn switch heater on' try: self.write_switch_heater(ON) except Exception as e: self.log.warning('write_switch_heater %r', e) return Retry - else: - self.status = Status.PREPARING, 'wait for heater on' return self.wait_for_switch_on @status_code(Status.PREPARING) def wait_for_switch_on(self, sm): - if (sm.target == self._last_target and - abs(sm.target - self.value) <= self.tolerance): # short cut - return self.check_switch_off - self.read_switch_heater() # trigger switch_on/off_time - if self.switch_heater == OFF: - if sm.init: # avoid too many states chained - return Retry - self.log.warning('switch turned off manually?') - return self.start_switch_on if sm.now - self.switch_on_time < self.wait_switch_on: if sm.delta(10): self.log.info('waited for %g sec', sm.now - self.switch_on_time) @@ -342,12 +352,6 @@ class Magfield(SimpleMagfield): @status_code(Status.FINALIZING) def wait_for_switch_off(self, sm): - self.read_switch_heater() - if self.switch_heater == ON: - if sm.init: # avoid too many states chained - return Retry - self.log.warning('switch turned on manually?') - return self.start_switch_off if sm.now - self.switch_off_time < self.wait_switch_off: return Retry if abs(self.value) > self.persistent_limit: