improvements on PPMS and LS370
- PPMS: improved machanism for 10 K waiting - LS370: fixed an issue with auto range + LS370: show test for all status bits Change-Id: Ia6454141917893f0e5c6c4351df3a864942bb629 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/23495 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
a520e6e1e4
commit
aa4c8f1f04
@ -247,3 +247,14 @@ def getfqdn(name=''):
|
|||||||
|
|
||||||
def getGeneralConfig():
|
def getGeneralConfig():
|
||||||
return CONFIG
|
return CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
def formatStatusBits(sword, labels, start=0):
|
||||||
|
"""Return a list of labels according to bit state in `sword` starting
|
||||||
|
with bit `start` and the first label in `labels`.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for i, lbl in enumerate(labels, start):
|
||||||
|
if sword & (1 << i) and lbl:
|
||||||
|
result.append(lbl)
|
||||||
|
return result
|
||||||
|
@ -27,6 +27,7 @@ from secop.metaclass import Done
|
|||||||
from secop.datatypes import FloatRange, IntRange, EnumType, BoolType
|
from secop.datatypes import FloatRange, IntRange, EnumType, BoolType
|
||||||
from secop.stringio import HasIodev
|
from secop.stringio import HasIodev
|
||||||
from secop.poller import Poller, REGULAR
|
from secop.poller import Poller, REGULAR
|
||||||
|
from secop.lib import formatStatusBits
|
||||||
import secop.iohandler
|
import secop.iohandler
|
||||||
|
|
||||||
Status = Drivable.Status
|
Status = Drivable.Status
|
||||||
@ -49,10 +50,7 @@ filterhdl = IOHandler('filter', 'FILTER?%(channel)d', '%d,%d,%d')
|
|||||||
scan = IOHandler('scan', 'SCAN?', '%d,%d')
|
scan = IOHandler('scan', 'SCAN?', '%d,%d')
|
||||||
|
|
||||||
|
|
||||||
STATUS_TEXT = {0: ''}
|
STATUS_BIT_LABELS = 'CS_OVL VCM_OVL VMIX_OVL VDIF_OVL R_OVER R_UNDER T_OVER T_UNDER'.split()
|
||||||
for bit, text in enumerate('CS_OVL VCM_OVL VMIX_OVL R_OVER R_UNDER T_OVER T_UNDER'.split()):
|
|
||||||
for i in range(1 << bit, 2 << bit):
|
|
||||||
STATUS_TEXT[i] = text
|
|
||||||
|
|
||||||
|
|
||||||
class StringIO(secop.stringio.StringIO):
|
class StringIO(secop.stringio.StringIO):
|
||||||
@ -177,13 +175,13 @@ class ResChannel(HasIodev, Readable):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
if self.channel != self._main.channel:
|
|
||||||
return Done
|
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
return [self.Status.DISABLED, 'disabled']
|
return [self.Status.DISABLED, 'disabled']
|
||||||
|
if self.channel != self._main.channel:
|
||||||
|
return Done
|
||||||
result = int(self.sendRecv('RDGST?%d' % self.channel))
|
result = int(self.sendRecv('RDGST?%d' % self.channel))
|
||||||
result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities)
|
result &= 0x37 # mask T_OVER and T_UNDER (change this when implementing temperatures instead of resistivities)
|
||||||
statustext = STATUS_TEXT[result]
|
statustext = ' '.join(formatStatusBits(result, STATUS_BIT_LABELS))
|
||||||
if statustext:
|
if statustext:
|
||||||
return [self.Status.ERROR, statustext]
|
return [self.Status.ERROR, statustext]
|
||||||
return [self.Status.IDLE, '']
|
return [self.Status.IDLE, '']
|
||||||
@ -191,10 +189,11 @@ class ResChannel(HasIodev, Readable):
|
|||||||
def analyze_rdgrng(self, iscur, exc, rng, autorange, excoff):
|
def analyze_rdgrng(self, iscur, exc, rng, autorange, excoff):
|
||||||
result = dict(range=rng)
|
result = dict(range=rng)
|
||||||
if autorange:
|
if autorange:
|
||||||
result['auotrange'] = 'hard'
|
result['autorange'] = 'hard'
|
||||||
elif self.autorange == 'hard':
|
#elif self.autorange == 'hard':
|
||||||
result['autorange'] = 'soft'
|
# result['autorange'] = 'soft'
|
||||||
# else: do not change autorange
|
# else: do not change autorange
|
||||||
|
self.log.info('%s range %r %r %r' % (self.name, rng, autorange, self.autorange))
|
||||||
if excoff:
|
if excoff:
|
||||||
result.update(iexc=0, vexc=0)
|
result.update(iexc=0, vexc=0)
|
||||||
elif iscur:
|
elif iscur:
|
||||||
@ -225,6 +224,7 @@ class ResChannel(HasIodev, Readable):
|
|||||||
if change.autorange == 'soft':
|
if change.autorange == 'soft':
|
||||||
if rng < self.minrange:
|
if rng < self.minrange:
|
||||||
rng = self.minrange
|
rng = self.minrange
|
||||||
|
self.autorange = change.autorange
|
||||||
return iscur, exc, rng, autorange, excoff
|
return iscur, exc, rng, autorange, excoff
|
||||||
|
|
||||||
def analyze_inset(self, on, dwell, pause, curve, tempco):
|
def analyze_inset(self, on, dwell, pause, curve, tempco):
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
"""a very simple simulator for a LakeShore Model 370"""
|
"""a very simple simulator for a LakeShore Model 370"""
|
||||||
|
|
||||||
from secop.modules import Communicator
|
from secop.modules import Communicator
|
||||||
#from secop.lib import mkthread
|
|
||||||
|
|
||||||
class Ls370Sim(Communicator):
|
class Ls370Sim(Communicator):
|
||||||
CHANNEL_COMMANDS = [
|
CHANNEL_COMMANDS = [
|
||||||
@ -47,7 +46,7 @@ class Ls370Sim(Communicator):
|
|||||||
for channel in range(1,17):
|
for channel in range(1,17):
|
||||||
_, _, _, _, excoff = self._data['RDGRNG?%d' % channel].split(',')
|
_, _, _, _, excoff = self._data['RDGRNG?%d' % channel].split(',')
|
||||||
if excoff == '1':
|
if excoff == '1':
|
||||||
self._data['RDGST?%d' % channel] = '4'
|
self._data['RDGST?%d' % channel] = '6'
|
||||||
else:
|
else:
|
||||||
self._data['RDGST?%d' % channel] = '0'
|
self._data['RDGST?%d' % channel] = '0'
|
||||||
|
|
||||||
@ -70,8 +69,3 @@ class Ls370Sim(Communicator):
|
|||||||
#if command.startswith('R'):
|
#if command.startswith('R'):
|
||||||
# print('> %s\t< %s' % (command, reply))
|
# print('> %s\t< %s' % (command, reply))
|
||||||
return ';'.join(reply)
|
return ';'.join(reply)
|
||||||
|
|
||||||
#def run(self):
|
|
||||||
# # time dependent simulation
|
|
||||||
# while True:
|
|
||||||
# time.sleep(1)
|
|
||||||
|
@ -209,8 +209,18 @@ class UserChannel(Channel):
|
|||||||
'no':
|
'no':
|
||||||
Property('channel number',
|
Property('channel number',
|
||||||
datatype=IntRange(0, 0), export=False, default=0),
|
datatype=IntRange(0, 0), export=False, default=0),
|
||||||
|
'linkenable':
|
||||||
|
Property('name of linked channel for enabling',
|
||||||
|
datatype=StringType(), export=False, default=''),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def write_enabled(self, enabled):
|
||||||
|
other = self._iodev.modules.get(self.linkenable, None)
|
||||||
|
if other:
|
||||||
|
other.enabled = enabled
|
||||||
|
return enabled
|
||||||
|
|
||||||
|
|
||||||
class DriverChannel(Channel):
|
class DriverChannel(Channel):
|
||||||
drvout = IOHandler('drvout', 'DRVOUT? %(no)d', '%d,%g,%g')
|
drvout = IOHandler('drvout', 'DRVOUT? %(no)d', '%d,%g,%g')
|
||||||
@ -410,8 +420,11 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
Parameter('intermediate set point',
|
Parameter('intermediate set point',
|
||||||
datatype=FloatRange(1.7, 402.0, unit='K'), handler=temp),
|
datatype=FloatRange(1.7, 402.0, unit='K'), handler=temp),
|
||||||
'ramp':
|
'ramp':
|
||||||
Parameter('ramping speed', readonly=False, handler=temp, default=0,
|
Parameter('ramping speed', readonly=False, default=0,
|
||||||
datatype=FloatRange(0, 20, unit='K/min')),
|
datatype=FloatRange(0, 20, unit='K/min')),
|
||||||
|
'workingramp':
|
||||||
|
Parameter('intermediate ramp value',
|
||||||
|
datatype=FloatRange(0, 20, unit='K/min'), handler=temp),
|
||||||
'approachmode':
|
'approachmode':
|
||||||
Parameter('how to approach target!', readonly=False, handler=temp,
|
Parameter('how to approach target!', readonly=False, handler=temp,
|
||||||
datatype=EnumType(ApproachMode)),
|
datatype=EnumType(ApproachMode)),
|
||||||
@ -458,6 +471,7 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
general_stop = False
|
general_stop = False
|
||||||
_cool_deadline = 0
|
_cool_deadline = 0
|
||||||
_wait_at10 = False
|
_wait_at10 = False
|
||||||
|
_ramp_at_limit = False
|
||||||
|
|
||||||
def update_value_status(self, value, packed_status):
|
def update_value_status(self, value, packed_status):
|
||||||
"""update value and status"""
|
"""update value and status"""
|
||||||
@ -469,10 +483,10 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code))
|
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code))
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if value > 11:
|
if value > 11:
|
||||||
# when starting from T > 40, this will be 15 min.
|
# when starting from T > 50, this will be 15 min.
|
||||||
# when starting from lower T, it will be less
|
# when starting from lower T, it will be less
|
||||||
# when ramping with 2 K/min or less, the deadline is now
|
# when ramping with 2 K/min or less, the deadline is now
|
||||||
self._cool_deadline = max(self._cool_deadline, now + min(30, value - 10) * 30) # 30 sec / K
|
self._cool_deadline = max(self._cool_deadline, now + min(40, value - 10) * 30) # 30 sec / K
|
||||||
elif self._wait_at10:
|
elif self._wait_at10:
|
||||||
if now > self._cool_deadline:
|
if now > self._cool_deadline:
|
||||||
self._wait_at10 = False
|
self._wait_at10 = False
|
||||||
@ -506,24 +520,40 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
self._expected_target_time = 0
|
self._expected_target_time = 0
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
def analyze_temp(self, setpoint, ramp, approachmode):
|
def analyze_temp(self, setpoint, workingramp, approachmode):
|
||||||
|
if (setpoint, workingramp, approachmode) == self._last_settings:
|
||||||
|
# update parameters only on change, as 'ramp' and 'approachmode' are
|
||||||
|
# not always sent to the hardware
|
||||||
|
return {}
|
||||||
|
self._last_settings = setpoint, workingramp, approachmode
|
||||||
if setpoint != 10 or not self._wait_at10:
|
if setpoint != 10 or not self._wait_at10:
|
||||||
|
self.log.debug('read back target %g %r' % (setpoint, self._wait_at10))
|
||||||
self.target = setpoint
|
self.target = setpoint
|
||||||
result = dict(setpoint=setpoint)
|
if workingramp != 2 or not self._ramp_at_limit:
|
||||||
# we update ramp and approachmode only at init
|
self.log.debug('read back ramp %g %r' % (workingramp, self._ramp_at_limit))
|
||||||
if self.ramp == 0:
|
self.ramp = workingramp
|
||||||
result['ramp'] = ramp
|
result = dict(setpoint=setpoint, workingramp=workingramp)
|
||||||
result['approachmode'] = approachmode
|
self.log.debug('analyze_temp %r %r' % (result, (self.target, self.ramp)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def change_temp(self, change):
|
def change_temp(self, change):
|
||||||
if 10 >= self.value > change.setpoint:
|
|
||||||
ramp = min(2, change.ramp)
|
|
||||||
print('ramplimit', change.ramp, self.value, ramp)
|
|
||||||
else:
|
|
||||||
ramp = change.ramp
|
ramp = change.ramp
|
||||||
self.calc_expected(change.setpoint, ramp)
|
setpoint = change.setpoint
|
||||||
return change.setpoint, ramp, change.approachmode
|
wait_at10 = False
|
||||||
|
ramp_at_limit = False
|
||||||
|
if self.value > 11:
|
||||||
|
if setpoint <= 10:
|
||||||
|
wait_at10 = True
|
||||||
|
setpoint = 10
|
||||||
|
elif self.value > setpoint:
|
||||||
|
if ramp >= 2:
|
||||||
|
ramp = 2
|
||||||
|
ramp_at_limit = True
|
||||||
|
self._wait_at10 = wait_at10
|
||||||
|
self._ramp_at_limit = ramp_at_limit
|
||||||
|
self.calc_expected(setpoint, ramp)
|
||||||
|
self.log.debug('change_temp v %r s %r r %r w %r l %r' % (self.value, setpoint, ramp, wait_at10, ramp_at_limit))
|
||||||
|
return setpoint, ramp, change.approachmode
|
||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
self._stopped = False
|
self._stopped = False
|
||||||
@ -532,24 +562,22 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
self._status_before_change = self.status
|
self._status_before_change = self.status
|
||||||
self.status = (self.Status.BUSY, 'changed target')
|
self.status = (self.Status.BUSY, 'changed target')
|
||||||
self._last_change = time.time()
|
self._last_change = time.time()
|
||||||
if self.value > 10 > target and self.ramp > 2:
|
|
||||||
self._wait_at10 = True
|
|
||||||
self.temp.write(self, 'setpoint', 10)
|
|
||||||
else:
|
|
||||||
self._wait_at10 = False
|
|
||||||
self.temp.write(self, 'setpoint', target)
|
self.temp.write(self, 'setpoint', target)
|
||||||
|
self.log.debug('write_target %s' % repr((self.setpoint, target, self._wait_at10)))
|
||||||
return target
|
return target
|
||||||
|
|
||||||
def write_approachmode(self, value):
|
def write_approachmode(self, value):
|
||||||
if self.isDriving():
|
if self.isDriving():
|
||||||
self.temp.write(self, 'approachmode', value)
|
self.temp.write(self, 'approachmode', value)
|
||||||
return Done
|
return Done
|
||||||
|
self.approachmode = value
|
||||||
return None # do not execute TEMP command, as this would trigger an unnecessary T change
|
return None # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||||
|
|
||||||
def write_ramp(self, value):
|
def write_ramp(self, value):
|
||||||
if self.isDriving():
|
if self.isDriving():
|
||||||
self.temp.write(self, 'ramp', value)
|
self.temp.write(self, 'ramp', value)
|
||||||
return Done
|
return Done
|
||||||
|
# self.ramp = value
|
||||||
return None # do not execute TEMP command, as this would trigger an unnecessary T change
|
return None # do not execute TEMP command, as this would trigger an unnecessary T change
|
||||||
|
|
||||||
def calc_expected(self, target, ramp):
|
def calc_expected(self, target, ramp):
|
||||||
@ -656,6 +684,7 @@ class Field(PpmsMixin, Drivable):
|
|||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
def analyze_field(self, target, ramp, approachmode, persistentmode):
|
def analyze_field(self, target, ramp, approachmode, persistentmode):
|
||||||
|
# print('last_settings tt %s' % repr(self._last_settings))
|
||||||
if (target, ramp, approachmode, persistentmode) == self._last_settings:
|
if (target, ramp, approachmode, persistentmode) == self._last_settings:
|
||||||
# we update parameters only on change, as 'ramp' and 'approachmode' are
|
# we update parameters only on change, as 'ramp' and 'approachmode' are
|
||||||
# not always sent to the hardware
|
# not always sent to the hardware
|
||||||
@ -669,6 +698,7 @@ class Field(PpmsMixin, Drivable):
|
|||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
if abs(self.target - self.value) <= 2e-5 and target == self.target:
|
if abs(self.target - self.value) <= 2e-5 and target == self.target:
|
||||||
|
self.target = target
|
||||||
return None # avoid ramping leads
|
return None # avoid ramping leads
|
||||||
self._status_before_change = self.status
|
self._status_before_change = self.status
|
||||||
self._stopped = False
|
self._stopped = False
|
||||||
@ -679,6 +709,7 @@ class Field(PpmsMixin, Drivable):
|
|||||||
|
|
||||||
def write_persistentmode(self, mode):
|
def write_persistentmode(self, mode):
|
||||||
if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode:
|
if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode:
|
||||||
|
self.persistentmode = mode
|
||||||
return None # avoid ramping leads
|
return None # avoid ramping leads
|
||||||
self._last_change = time.time()
|
self._last_change = time.time()
|
||||||
self._status_before_change = self.status
|
self._status_before_change = self.status
|
||||||
@ -688,6 +719,7 @@ class Field(PpmsMixin, Drivable):
|
|||||||
return Done
|
return Done
|
||||||
|
|
||||||
def write_ramp(self, value):
|
def write_ramp(self, value):
|
||||||
|
self.ramp = value
|
||||||
if self.isDriving():
|
if self.isDriving():
|
||||||
self.field.write(self, 'ramp', value)
|
self.field.write(self, 'ramp', value)
|
||||||
return Done
|
return Done
|
||||||
@ -805,6 +837,7 @@ class Position(PpmsMixin, Drivable):
|
|||||||
if self.isDriving():
|
if self.isDriving():
|
||||||
self.move.write(self, 'speed', value)
|
self.move.write(self, 'speed', value)
|
||||||
return Done
|
return Done
|
||||||
|
self.speed = value
|
||||||
return None # do not execute MOVE command, as this would trigger an unnecessary move
|
return None # do not execute MOVE command, as this would trigger an unnecessary move
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
|
@ -53,7 +53,7 @@ class PpmsSim:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.status = NamedList('t mf ch pos', 1, 1, 1, 1)
|
self.status = NamedList('t mf ch pos', 1, 1, 1, 1)
|
||||||
self.st = 0x1111
|
self.st = 0x1111
|
||||||
self.t = 200
|
self.t = 15
|
||||||
self.temp = NamedList('target ramp amode', 200., 1, 0, fast=self.t, delay=10)
|
self.temp = NamedList('target ramp amode', 200., 1, 0, fast=self.t, delay=10)
|
||||||
self.mf = 100
|
self.mf = 100
|
||||||
self.field = NamedList('target ramp amode pmode', 0, 50, 0, 0)
|
self.field = NamedList('target ramp amode pmode', 0, 50, 0, 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user