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
This commit is contained in:
l_samenv 2022-12-21 10:57:41 +01:00
parent 79b8cd7b2d
commit 766f15beee
2 changed files with 69 additions and 47 deletions

View File

@ -288,6 +288,24 @@ class Field(SimpleField, Magfield):
return Retry return Retry
return sm.after_wait 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): def start_ramp_to_zero(self, sm):
try: try:
assert self.write_action(Action.hold) == Action.hold assert self.write_action(Action.hold) == Action.hold

View File

@ -22,7 +22,7 @@
import time import time
from secop.core import Drivable, Parameter, Done, IDLE, BUSY, ERROR from secop.core import Drivable, Parameter, Done, IDLE, BUSY, ERROR
from secop.datatypes import FloatRange, EnumType, ArrayOf, TupleOf, StatusType 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.errors import ConfigError, ProgrammingError, HardwareError, BadValueError
from secop.lib.enum import Enum from secop.lib.enum import Enum
from secop.states import Retry, HasStates, status_code, Start from secop.states import Retry, HasStates, status_code, Start
@ -48,7 +48,7 @@ OFF = 0
ON = 1 ON = 1
class SimpleMagfield(HasStates, HasLimits, Drivable): class SimpleMagfield(HasStates, HasTargetLimits, Drivable):
value = Parameter('magnetic field', datatype=FloatRange(unit='T')) value = Parameter('magnetic field', datatype=FloatRange(unit='T'))
ramp = Parameter( ramp = Parameter(
'wanted ramp rate for field', FloatRange(unit='$/min'), readonly=False) 'wanted ramp rate for field', FloatRange(unit='$/min'), readonly=False)
@ -63,6 +63,9 @@ class SimpleMagfield(HasStates, HasLimits, Drivable):
readonly=False, default=(0, 0)) readonly=False, default=(0, 0))
wait_stable_field = Parameter( wait_stable_field = Parameter(
'wait time to ensure field is stable', FloatRange(0, unit='s'), readonly=False, default=31) '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 _last_target = None
@ -80,6 +83,23 @@ class SimpleMagfield(HasStates, HasLimits, Drivable):
# let the state machine do the needed steps to finish # let the state machine do the needed steps to finish
self.write_target(self.value) 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): def write_target(self, target):
self.check_limits(target) self.check_limits(target)
self.start_machine(self.start_field_change, target=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) self.init_progress(sm, self.value)
# Remarks: assume there is a ramp limiting feature # Remarks: assume there is a ramp limiting feature
if abs(self.value - sm.target) > self.tolerance: if abs(self.value - sm.target) > self.tolerance:
if self.get_progress(sm, self.value): if self.get_progress(sm, self.value) > self.ramp_tmo:
return Retry
raise HardwareError('no progress') raise HardwareError('no progress')
sm.stabilize_start = None # force reset
return Retry
sm.stabilize_start = time.time() sm.stabilize_start = time.time()
return self.stabilize_field return self.stabilize_field
@ -127,7 +148,7 @@ class SimpleMagfield(HasStates, HasLimits, Drivable):
class Magfield(SimpleMagfield): class Magfield(SimpleMagfield):
status = Parameter(datatype=StatusType(Status)) status = Parameter(datatype=StatusType(Status))
mode = Parameter( 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), switch_heater = Parameter('switch heater', EnumType(off=OFF, on=ON),
readonly=False, default=0) readonly=False, default=0)
current = Parameter( current = Parameter(
@ -152,26 +173,15 @@ class Magfield(SimpleMagfield):
leads_ramp_tmo = Parameter( leads_ramp_tmo = Parameter(
'timeout for leads ramp progress', 'timeout for leads ramp progress',
FloatRange(0, unit='s'), readonly=False, default=30) FloatRange(0, unit='s'), readonly=False, default=30)
ramp_tmo = Parameter( init_persistency = True
'timeout for field ramp progress',
FloatRange(0, unit='s'), readonly=False, default=30)
__init_persistency = True
switch_on_time = None switch_on_time = None
switch_off_time = None switch_off_time = None
def doPoll(self): def doPoll(self):
if self.__init_persistency: if self.init_persistency:
if self.__init_persistency is True: if self.read_switch_heater() and self.mode != Mode.DRIVEN:
self._last_target = self.value self.start_machine(self.go_persistent_soon, mode=self.mode)
self.__init_persistency = time.time() + 60 self.init_persistency = False
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
super().doPoll() super().doPoll()
def initModule(self): def initModule(self):
@ -179,8 +189,8 @@ class Magfield(SimpleMagfield):
self.registerCallbacks(self) # for update_switch_heater self.registerCallbacks(self) # for update_switch_heater
def write_mode(self, value): def write_mode(self, value):
self.__init_persistency = False self.init_persistency = False
target = self.value target = self.last_target()
func = self.start_field_change func = self.start_field_change
if value == Mode.DISABLED: if value == Mode.DISABLED:
target = 0 target = 0
@ -188,11 +198,12 @@ class Magfield(SimpleMagfield):
func = self.start_switch_off func = self.start_switch_off
elif value == Mode.PERSISTENT: elif value == Mode.PERSISTENT:
func = self.start_switch_off func = self.start_switch_off
self.target = target
self.start_machine(func, target=target, mode=value) self.start_machine(func, target=target, mode=value)
return value return value
def write_target(self, target): def write_target(self, target):
self.__init_persistency = False self.init_persistency = False
if self.mode == Mode.DISABLED: if self.mode == Mode.DISABLED:
if target == 0: if target == 0:
return 0 return 0
@ -208,14 +219,22 @@ class Magfield(SimpleMagfield):
if sm.mode != Mode.DRIVEN: if sm.mode != Mode.DRIVEN:
self.log.warning('turn switch heater off') self.log.warning('turn switch heater off')
self.write_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) @status_code(Status.PREPARING)
def start_field_change(self, sm): def start_field_change(self, sm):
self.setFastPoll(True, 1.0) self.setFastPoll(True, 1.0)
if sm.target == self.value or ( if (sm.target == self.last_target() and
sm.target == self._last_target and abs(sm.target - self.value) <= self.tolerance and
abs(sm.target - self.value) <= self.tolerance): # short cut abs(self.current - self.value) < self.tolerance and
(self.mode != Mode.DRIVEN or self.switch_heater == ON)): # short cut
return self.check_switch_off return self.check_switch_off
if self.switch_heater == ON: if self.switch_heater == ON:
return self.start_switch_on return self.start_switch_on
@ -267,28 +286,19 @@ class Magfield(SimpleMagfield):
@status_code(Status.PREPARING) @status_code(Status.PREPARING)
def start_switch_on(self, sm): 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: if self.read_switch_heater() == OFF:
self.status = Status.PREPARING, 'turn switch heater on'
try: try:
self.write_switch_heater(ON) self.write_switch_heater(ON)
except Exception as e: except Exception as e:
self.log.warning('write_switch_heater %r', e) self.log.warning('write_switch_heater %r', e)
return Retry return Retry
else:
self.status = Status.PREPARING, 'wait for heater on'
return self.wait_for_switch_on return self.wait_for_switch_on
@status_code(Status.PREPARING) @status_code(Status.PREPARING)
def wait_for_switch_on(self, sm): 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.now - self.switch_on_time < self.wait_switch_on:
if sm.delta(10): if sm.delta(10):
self.log.info('waited for %g sec', sm.now - self.switch_on_time) self.log.info('waited for %g sec', sm.now - self.switch_on_time)
@ -342,12 +352,6 @@ class Magfield(SimpleMagfield):
@status_code(Status.FINALIZING) @status_code(Status.FINALIZING)
def wait_for_switch_off(self, sm): 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: if sm.now - self.switch_off_time < self.wait_switch_off:
return Retry return Retry
if abs(self.value) > self.persistent_limit: if abs(self.value) > self.persistent_limit: