improved cyoltd (flame magnet)
- use a statemachine - do more control in frappy, switching from persistent to driven internally - a lot of fixes
This commit is contained in:
parent
766f15beee
commit
ad4a6d2e09
@ -10,10 +10,11 @@ io= cio
|
||||
|
||||
[B]
|
||||
description = 'magnetic field'
|
||||
class = secop_psi.cryoltd.MainMagfield
|
||||
class = secop_psi.cryoltd.MainField
|
||||
channel = Main
|
||||
constraint = 80000
|
||||
target.max = 35000
|
||||
mode = 'PERSISTENT'
|
||||
hw_units = T
|
||||
# A_to_G is needed for ramp rate
|
||||
A_to_G = 285.73
|
||||
@ -21,6 +22,7 @@ ramp.max = 412
|
||||
overshoot = {'o': 1, 't': 180}
|
||||
degauss = {'s': 500, 'd': 30, 'f': 5, 't': 120}
|
||||
tolerance = 5
|
||||
wait_stable_field = 180
|
||||
|
||||
[Bx]
|
||||
description = 'magnetic field x component'
|
||||
|
@ -29,10 +29,12 @@ changed from the client is fixed for at least 10 seconds.
|
||||
import re
|
||||
import time
|
||||
from secop.core import HasIO, StringIO, Readable, Drivable, Parameter, Command, \
|
||||
Module, Property, Attached, Enum, IDLE, BUSY, ERROR
|
||||
from secop.features import HasLimits
|
||||
Module, Property, Attached, Enum, IDLE, BUSY, ERROR, Done
|
||||
from secop.errors import ConfigError, BadValueError, HardwareError
|
||||
from secop.datatypes import FloatRange, StringType, EnumType, StructOf, OrType
|
||||
from secop.datatypes import FloatRange, StringType, EnumType, StructOf
|
||||
from secop.states import HasStates, status_code, Retry
|
||||
from secop.features import HasTargetLimits
|
||||
import secop_psi.magfield as magfield
|
||||
|
||||
# floating point value followed with unit
|
||||
VALUE_UNIT = re.compile(r'([-0-9.E]*\d|inf)([A-Za-z/%]*)$')
|
||||
@ -135,10 +137,10 @@ class Channel:
|
||||
cmd = cmd.replace('<CH>', self.channel)
|
||||
reply = self.main.communicate(cmd)
|
||||
if not reply.startswith(cmd):
|
||||
print('MISMATCH', cmd, reply)
|
||||
self.log.warn('reply %r does not match command %r', reply, cmd)
|
||||
|
||||
def block(self, pname, value=None):
|
||||
self.block_until[pname] = time.time() + 10
|
||||
def block(self, pname, value=None, delay=10):
|
||||
self.block_until[pname] = time.time() + delay
|
||||
if value is not None:
|
||||
setattr(self, pname, value)
|
||||
|
||||
@ -183,14 +185,26 @@ class Temperature(Channel, Readable):
|
||||
self.main.register_module(self, value=self.channel)
|
||||
|
||||
|
||||
class Magfield(HasLimits, Channel, Drivable):
|
||||
CsMode = Enum(
|
||||
PERSISTENT=1,
|
||||
SEMIPERSISTENT=2,
|
||||
DRIVEN=0,
|
||||
)
|
||||
|
||||
PersistencyMode = Enum(
|
||||
DISABLED=0,
|
||||
PERSISTENT=30,
|
||||
SEMIPERSISTENT=31,
|
||||
DRIVEN=50,
|
||||
)
|
||||
|
||||
|
||||
class BaseMagfield(HasStates, HasTargetLimits, Channel):
|
||||
_status_text = ''
|
||||
_ready_text = ''
|
||||
_error_text = ''
|
||||
_last_error = ''
|
||||
_rate_units = ''
|
||||
_next_target = None
|
||||
_last_target = None
|
||||
hw_units = Property('hardware units: A or T', EnumType(A=0, T=1))
|
||||
A_to_G = Property('A_to_G = "Gauss Value / Amps Value"', FloatRange(0))
|
||||
tolerance = Parameter('tolerance', FloatRange(0), readonly=False, default=1)
|
||||
@ -201,10 +215,18 @@ class Magfield(HasLimits, Channel, Drivable):
|
||||
if dt.min < 1e-99: # make limits symmetric
|
||||
dt.min = - dt.max
|
||||
min_, max_ = self.target_limits
|
||||
self.target_limits = [max(min_, dt.min), max_]
|
||||
self.target_limits = [max(min_, dt.min), min(max_, dt.max)]
|
||||
dt = self.parameters['ramp'].datatype
|
||||
if self.ramp == 0: # unconfigured: take max.
|
||||
self.ramp = dt.max
|
||||
if not isinstance(self, magfield.Magfield):
|
||||
# add unneeded attributes, as the appear in GetAll
|
||||
self.switch_heater = None
|
||||
self.current = None
|
||||
|
||||
def doPoll(self):
|
||||
super().doPoll() # does not call cycle_machine
|
||||
self.cycle_machine()
|
||||
|
||||
def to_gauss(self, value):
|
||||
value, unit = VALUE_UNIT.match(value).groups()
|
||||
@ -237,57 +259,34 @@ class Magfield(HasLimits, Channel, Drivable):
|
||||
_rate_units=('<CH>_Rate Units', str),
|
||||
current=('<CH>_PSU Output', self.to_gauss),
|
||||
voltage='<CH>_Voltage',
|
||||
working_ramp=('<CH>_Ramp Rate', self.to_gauss_min),
|
||||
workingramp=('<CH>_Ramp Rate', self.to_gauss_min),
|
||||
setpoint=('<CH>_Setpoint', self.to_gauss),
|
||||
switch_heater=('<CH>_Heater', self.cvt_switch_heater),
|
||||
mode=('<CH>_Persistent Mode', self.cvt_mode),
|
||||
cs_mode=('<CH>_Persistent Mode', self.cvt_cs_mode),
|
||||
approach_mode=('<CH>_Approach', self.cvt_approach_mode),
|
||||
)
|
||||
|
||||
def cvt_error(self, text):
|
||||
if text != self._last_error:
|
||||
self._last_error = text
|
||||
self.log.error(text)
|
||||
return text
|
||||
return self._error_text
|
||||
|
||||
def trigger_update(self):
|
||||
# called after treating result of GetAll message
|
||||
if self._error_text:
|
||||
status = ERROR, '%s while %s' % (self._error_text, self._status_text)
|
||||
elif self._ready_text == 'TRUE':
|
||||
with self.accessLock: # must not be in parallel with write_target
|
||||
target = self._next_target
|
||||
if target is not None: # target change pending
|
||||
if target == self._last_target and abs(self.value - target) <= self.tolerance:
|
||||
# we are already there
|
||||
self._last_target = None
|
||||
status = IDLE, self._status_text
|
||||
else:
|
||||
if self.hw_units == 'A':
|
||||
self.sendcmd('Set:<CH>:Sweep %gA' % (target / self.A_to_G))
|
||||
else:
|
||||
self.sendcmd('Set:<CH>:Sweep %gT' % (target / 10000))
|
||||
self._next_target = None
|
||||
self._last_target = target
|
||||
status = BUSY, 'changed target'
|
||||
else:
|
||||
status = IDLE, self._status_text
|
||||
elif self._next_target is None:
|
||||
txt = self._status_text
|
||||
if txt.startswith('Ramping Magnet'):
|
||||
if txt.endswith(': ') or ' 1 seconds' in txt:
|
||||
txt = 'stabilizing'
|
||||
else:
|
||||
txt = 'ramping'
|
||||
status = BUSY, txt
|
||||
else:
|
||||
return # do not change status when aborting
|
||||
self.status = status
|
||||
if self.status[0] == ERROR:
|
||||
errtxt = self._error_text
|
||||
else:
|
||||
errtxt = '%s while %s' % (self._error_text, self.status[1])
|
||||
# self.stop_machine((ERROR, errtxt))
|
||||
|
||||
value = Parameter('magnetic field in the coil', FloatRange(unit='G'))
|
||||
|
||||
setpoint = Parameter('setpoint', FloatRange(unit='G'), default=0)
|
||||
ramp = Parameter('ramp rate', FloatRange(0, unit='$/min'), default=0, readonly=False)
|
||||
ramp = Parameter()
|
||||
target = Parameter()
|
||||
|
||||
def write_ramp(self, ramp):
|
||||
if self._rate_units != 'A/s':
|
||||
@ -297,48 +296,63 @@ class Magfield(HasLimits, Channel, Drivable):
|
||||
|
||||
def write_target(self, target):
|
||||
self.reset_error()
|
||||
self.check_limits(target)
|
||||
self.write_ramp(self.ramp)
|
||||
super().write_target(target)
|
||||
return Done
|
||||
|
||||
def start_sweep(self, target):
|
||||
if self.approach_mode == self.approach_mode.OVERSHOOT:
|
||||
o = self.overshoot['o']
|
||||
if (target - self.value) * o < 0:
|
||||
self.write_overshoot(dict(self.overshoot, o=-o))
|
||||
self.block('_error_text', '')
|
||||
if self._ready_text == 'FALSE':
|
||||
if target != self._last_target or abs(self.value - target) > self.tolerance:
|
||||
self.status = BUSY, 'aborting'
|
||||
self.sendcmd('Set:<CH>:Abort')
|
||||
self._next_target = target
|
||||
self.write_ramp(self.ramp)
|
||||
if self.hw_units == 'A':
|
||||
self.sendcmd('Set:<CH>:Sweep %gA' % (target / self.A_to_G))
|
||||
else:
|
||||
self._next_target = target
|
||||
self.trigger_update() # update status
|
||||
return target
|
||||
self.sendcmd('Set:<CH>:Sweep %gT' % (target / 10000))
|
||||
self.block('_ready_text', 'FALSE')
|
||||
|
||||
working_ramp = Parameter('actual ramp rate', FloatRange(0, unit='$/min'))
|
||||
def start_field_change(self, sm):
|
||||
if self._ready_text == 'FALSE':
|
||||
self.sendcmd('Set:<CH>:Abort')
|
||||
return self.wait_ready
|
||||
return super().start_field_change
|
||||
|
||||
Mode = Enum(
|
||||
# DISABLED=0,
|
||||
PERSISTENT=30,
|
||||
SEMIPERSISTENT=31,
|
||||
DRIVEN=50,
|
||||
)
|
||||
mode = Parameter('persistent mode', EnumType(Mode), readonly=False, default=30)
|
||||
mode_map = Mapped(DRIVEN=0, PERSISTENT=1, SEMIPERSISTENT=2)
|
||||
def wait_ready(self, sm):
|
||||
if self._ready_text == 'FALSE':
|
||||
return Retry
|
||||
return super().start_field_change
|
||||
|
||||
def write_mode(self, value):
|
||||
code = self.mode_map(value)
|
||||
self.sendcmd('Set:<CH>:SetPM %d' % code)
|
||||
self.block('mode')
|
||||
return value
|
||||
def start_ramp_to_target(self, sm):
|
||||
self.start_sweep(sm.target)
|
||||
return self.ramp_to_target # -> stabilize_field
|
||||
|
||||
def stabilize_field(self, sm):
|
||||
if self._ready_text == 'FALSE':
|
||||
# wait for overshoot/degauss/cycle
|
||||
sm.stabilize_start = sm.now
|
||||
return Retry
|
||||
if sm.now - sm.stabilize_start < self.wait_stable_field:
|
||||
return Retry
|
||||
return self.end_stablilize
|
||||
|
||||
def end_stablilize(self, sm):
|
||||
return self.final_status()
|
||||
|
||||
cs_mode = Parameter('persistent mode', EnumType(CsMode), readonly=False, default=0)
|
||||
|
||||
@staticmethod
|
||||
def cvt_mode(text):
|
||||
def cvt_cs_mode(text):
|
||||
text = text.lower()
|
||||
if 'off' in text:
|
||||
if '0' in text:
|
||||
return 30
|
||||
return 31
|
||||
return 50
|
||||
return CsMode.PERSISTENT
|
||||
return CsMode.SEMIPERSISTENT
|
||||
return CsMode.DRIVEN
|
||||
|
||||
def write_cs_mode(self, value):
|
||||
self.sendcmd('Set:<CH>:SetPM %d' % int(value))
|
||||
self.block('cs_mode')
|
||||
return value
|
||||
|
||||
ApproachMode = Enum(
|
||||
DIRECT=0,
|
||||
@ -393,12 +407,8 @@ class Magfield(HasLimits, Channel, Drivable):
|
||||
(value['s'] * 1e-4, value['d'], value['f'] * 1e-4, value['t']))
|
||||
return value
|
||||
|
||||
current = Parameter(
|
||||
'leads current (in units of field)', FloatRange(unit='$'))
|
||||
voltage = Parameter(
|
||||
'voltage over leads', FloatRange(unit='V'))
|
||||
switch_heater = Parameter(
|
||||
'voltage over leads', EnumType(OFF=0, ON=1))
|
||||
|
||||
@staticmethod
|
||||
def cvt_switch_heater(text):
|
||||
@ -419,29 +429,94 @@ class Magfield(HasLimits, Channel, Drivable):
|
||||
self._error_text = ''
|
||||
|
||||
|
||||
class MainMagfield(Magfield):
|
||||
class MainField(BaseMagfield, magfield.Magfield):
|
||||
checked_modules = None
|
||||
|
||||
def earlyInit(self):
|
||||
super().earlyInit()
|
||||
self.checked_modules = []
|
||||
|
||||
def check_limits(self, value):
|
||||
super().check_limits(value)
|
||||
self.check_combined(None, 0, value)
|
||||
|
||||
# TODO: turn into a property
|
||||
constraint = Parameter('product check', FloatRange(unit='G^2'), default=80000)
|
||||
|
||||
def check_combined(self, obj, value, main_target):
|
||||
sumvalue2 = sum((max(o.value ** 2, value ** 2 if o == obj else 0)
|
||||
sumvalue2 = sum(((value ** 2 if o == obj else o.value ** 2)
|
||||
for o in self.checked_modules))
|
||||
if sumvalue2 * max(self.value ** 2, main_target) > self.constraint ** 2:
|
||||
raise BadValueError('outside constraint (B * Bxyz > %g G^2' * self.constraint)
|
||||
if sumvalue2 * max(self.value ** 2, main_target ** 2) > self.constraint ** 2:
|
||||
raise BadValueError('outside constraint (B * Bxyz > %g G^2' % self.constraint)
|
||||
|
||||
def check_limits(self, value):
|
||||
super().check_limits(value)
|
||||
self.check_combined(None, 0, value)
|
||||
|
||||
mode = Parameter(datatype=EnumType(PersistencyMode))
|
||||
|
||||
class ComponentField(Magfield):
|
||||
check_against = Attached(MainMagfield)
|
||||
def write_mode(self, mode):
|
||||
self.reset_error()
|
||||
super().write_mode(mode) # updates mode
|
||||
return Done
|
||||
|
||||
@status_code('PREPARING')
|
||||
def start_ramp_to_field(self, sm):
|
||||
self.start_sweep(self.value)
|
||||
return self.ramp_to_field # -> stabilize_current -> start_switch_on
|
||||
|
||||
@status_code('PREPARING')
|
||||
def start_switch_on(self, sm):
|
||||
self.write_cs_mode(CsMode.DRIVEN)
|
||||
# self.block('switch_heater', 1, 60)
|
||||
self.start_sweep(self.value)
|
||||
self.block('_ready_text', 'FALSE')
|
||||
return self.wait_for_switch_on # -> start_ramp_to_target -> ramp_to_target -> stabilize_field
|
||||
|
||||
@status_code('PREPARING')
|
||||
def wait_for_switch_on(self, sm):
|
||||
if self._ready_text == 'FALSE':
|
||||
return Retry
|
||||
self.last_target(sm.target)
|
||||
return self.start_ramp_to_target
|
||||
|
||||
def end_stablilize(self, sm):
|
||||
return self.start_switch_off
|
||||
|
||||
@status_code('FINALIZING')
|
||||
def start_switch_off(self, sm):
|
||||
if self.mode == PersistencyMode.DRIVEN:
|
||||
return self.final_status(IDLE, 'driven')
|
||||
if self.mode == PersistencyMode.SEMIPERSISTENT:
|
||||
self.write_cs_mode(CsMode.SEMIPERSISTENT)
|
||||
else: # PERSISTENT or DISABLED
|
||||
self.write_cs_mode(CsMode.PERSISTENT)
|
||||
self.start_sweep(sm.target)
|
||||
self.block('_ready_text', 'FALSE')
|
||||
self.block('switch_heater', 1)
|
||||
return self.wait_for_switch_off # -> start_ramp_to_zero
|
||||
|
||||
@status_code('PREPARING')
|
||||
def wait_for_switch_off(self, sm):
|
||||
if self.switch_heater:
|
||||
return Retry
|
||||
self.last_target(sm.target)
|
||||
if self.mode == PersistencyMode.SEMIPERSISTENT:
|
||||
return self.final_status(IDLE, 'semipersistent')
|
||||
return self.ramp_to_zero
|
||||
|
||||
@status_code('FINALIZING')
|
||||
def ramp_to_zero(self, sm):
|
||||
if self._ready_text == 'FALSE':
|
||||
return Retry
|
||||
if self.mode == PersistencyMode.DISABLED:
|
||||
return self.final_status(PersistencyMode.DISABLED, 'disabled')
|
||||
return self.final_status(IDLE, 'persistent')
|
||||
|
||||
|
||||
class ComponentField(BaseMagfield, magfield.SimpleMagfield):
|
||||
check_against = Attached(MainField)
|
||||
# status = Parameter(datatype=EnumType(Drivable.Status, RAMPING=370, STABILIZING=380, FINALIZING=390))
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
@ -495,9 +570,9 @@ class Compressor(Channel, Drivable):
|
||||
|
||||
def write_target(self, value):
|
||||
if value:
|
||||
self.sendcmd('SetCompressor:Start <CH>')
|
||||
self.sendcmd('Set:Compressor:Start <CH>')
|
||||
else:
|
||||
self.sendcmd('SetCompressor:Stop <CH>')
|
||||
self.sendcmd('Set:Compressor:Stop <CH>')
|
||||
self.block('target')
|
||||
self.read_status()
|
||||
return value
|
||||
@ -507,6 +582,3 @@ class Compressor(Channel, Drivable):
|
||||
"""reset error"""
|
||||
self.sendcmd('Set:Compressor:Reset <CH>')
|
||||
self._error_text = ''
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user