changed/fixed behaviour of stop commands in PPMS

- make sure new target created by the stop command is between
  old and new target
- fixed bad behaviour of level reading

Change-Id: I484c0902c694c0edb81e2d9238985c05f92e04f4
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22100
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2020-01-08 14:46:45 +01:00
parent d442c5b6bf
commit f0a3306f9c
3 changed files with 98 additions and 84 deletions

View File

@ -81,7 +81,7 @@ def clamp(_min, value, _max):
"""return the median of 3 values,
i.e. value if min <= value <= max, else min or max depending on which side
value lies outside the [min..max] interval
value lies outside the [min..max] interval. This works even when min > max!
"""
# return median, i.e. clamp the the value between min and max
return sorted([_min, value, _max])[1]

View File

@ -39,6 +39,7 @@ from secop.modules import Module, Readable, Drivable, Parameter, Override,\
from secop.datatypes import EnumType, FloatRange, IntRange, StringType,\
BoolType, StatusType
from secop.lib.enum import Enum
from secop.lib import clamp
from secop.errors import HardwareError
from secop.poller import Poller
import secop.iohandler
@ -54,7 +55,6 @@ except ImportError:
class IOHandler(secop.iohandler.IOHandler):
CMDARGS = ['no']
CMDSEPARATOR = None # no command chaining
READ_BEFORE_WRITE = False
def __init__(self, name, querycmd, replyfmt):
changecmd = querycmd.split('?')[0] + ' '
@ -127,11 +127,12 @@ class Main(Communicator):
class PpmsMixin(HasIodev, Module):
properties = {
'iodev': Attached(),
'general_stop': Property('respect general stop', datatype=BoolType(),
export=True, default=True)
}
pollerClass = Poller
enabled = True # default, if no parameter enable is defined
_last_target_change = 0 # used by several modules
_last_settings = None # used by several modules
slow_pollfactor = 1
@ -303,11 +304,9 @@ class Level(PpmsMixin, Readable):
"""
def analyze_level(self, level, status):
if status:
self.status = [self.Status.IDLE, '']
else:
self.status = [self.Status.ERROR, 'old reading']
return dict(value=level)
# ignore 'old reading' state of the flag, as this happens only for a short time
# during measuring
return dict(value=level, status=[self.Status.IDLE, ''])
class Chamber(PpmsMixin, Drivable):
@ -416,10 +415,6 @@ class Temp(PpmsMixin, Drivable):
Parameter('drive timeout, in addition to ramp time', readonly=False,
datatype=FloatRange(0, unit='sec'), default=3600),
}
properties = {
'general_stop': Property('respect general stop', datatype=BoolType(),
export=True, default=True)
}
# pylint: disable=invalid-name
TempStatus = Enum(
'TempStatus',
@ -447,12 +442,10 @@ class Temp(PpmsMixin, Drivable):
channel = 'temp'
_stopped = False
_expected_target = 0
_expected_target_time = 0
_last_change = 0 # 0 means no target change is pending
def earlyInit(self):
self.setProperty('general_stop', False)
super().earlyInit()
_last_target = None # last reached target
general_stop = False
def update_value_status(self, value, packed_status):
"""update value and status"""
@ -462,13 +455,6 @@ class Temp(PpmsMixin, Drivable):
self.value = value
status = self.STATUS_MAP[packed_status & 0xf]
now = time.time()
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
self._stopped = False
else:
self.status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
return
if self._last_change: # there was a change, which is not yet confirmed by hw
if now > self._last_change + 5:
self._last_change = 0 # give up waiting for busy
@ -477,13 +463,23 @@ class Temp(PpmsMixin, Drivable):
self._last_change = 0
else:
status = [self.Status.BUSY, 'changed target']
if self._expected_target:
if abs(self.value - self.target) < self.target * 0.01:
self._last_target = self.target
elif self._last_target is None:
self._last_target = self.value
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
status = [status[0], 'stopped']
else:
status = [status[0], 'stopping (%s)' % status[1]]
if self._expected_target_time:
# handle timeout
if self.isDriving(status):
if now > self._expected_target + self.timeout:
if now > self._expected_target_time + self.timeout:
status = [self.Status.WARN, 'timeout while %s' % status[1]]
else:
self._expected_target = 0
self._expected_target_time = 0
self.status = status
def analyze_temp(self, target, ramp, approachmode):
@ -500,7 +496,7 @@ class Temp(PpmsMixin, Drivable):
def write_target(self, target):
self._stopped = False
if abs(self.target - self.value) < 2e-5 and target == self.target:
if abs(self.target - self.value) <= 2e-5 * target and target == self.target:
return None
self._status_before_change = self.status
self.status = [self.Status.BUSY, 'changed target']
@ -518,19 +514,18 @@ class Temp(PpmsMixin, Drivable):
return None # change_temp will not be called, as this would trigger an unnecessary T change
def calc_expected(self, target, ramp):
self._expected_target = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
self._expected_target_time = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
def do_stop(self):
if self.isDriving():
if not self.isDriving():
return
if self.status[0] == self.Status.STABILIZING:
# we are already near target
newtarget = self.target
else:
newtarget = self.value
self.log.info('stop at %s K', newtarget)
self.write_target(newtarget)
self.status = [self.Status.IDLE, 'stopped']
if self.status[0] != self.Status.STABILIZING:
# we are not near target
newtarget = clamp(self._last_target, self.value, self.target)
if newtarget != self.target:
self.log.debug('stop at %s K', newtarget)
self.write_target(newtarget)
self.status = [self.status[0], 'stopping (%s)' % self.status[1]]
self._stopped = True
@ -583,7 +578,7 @@ class Field(PpmsMixin, Drivable):
channel = 'field'
_stopped = False
_last_target = 0
_last_target = None # last reached target
_last_change = 0 # means no target change is pending
def update_value_status(self, value, packed_status):
@ -595,14 +590,7 @@ class Field(PpmsMixin, Drivable):
status_code = (packed_status >> 4) & 0xf
status = self.STATUS_MAP[status_code]
now = time.time()
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
self._stopped = False
else:
self.status = [status[0], 'stopped (%s)' % status[1]]
return
elif self._last_change: # there was a change, which is not yet confirmed by hw
if self._last_change: # there was a change, which is not yet confirmed by hw
if status_code == 1: # persistent mode
# leads are ramping (ppms has no extra status code for this!)
if now < self._last_change + 30:
@ -616,6 +604,16 @@ class Field(PpmsMixin, Drivable):
self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
else:
status = [self.Status.BUSY, 'changed target']
if abs(self.target - self.value) <= 1e-4:
self._last_target = self.target
elif self._last_target is None:
self._last_target = self.value
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
status = [status[0], 'stopped']
else:
status = [status[0], 'stopping (%s)' % status[1]]
self.status = status
def analyze_field(self, target, ramp, approachmode, persistentmode):
@ -624,34 +622,49 @@ class Field(PpmsMixin, Drivable):
# not always sent to the hardware
return {}
self._last_settings = target, ramp, approachmode, persistentmode
return dict(target=target * 1e-4, ramp=ramp * 6e-3, approachmode=approachmode,
return dict(target=round(target * 1e-4, 7), ramp=ramp * 6e-3, approachmode=approachmode,
persistentmode=persistentmode)
def change_field(self, change):
if change.doesInclude('target', 'persistentmode'):
if change.doesInclude('target'):
self._last_target = self.target # save for stop command
self._stopped = False
self._last_change = time.time()
self._status_before_change = list(self.status)
else:
# changed ramp or approachmode
if not self.isDriving():
return None # nothing to be written, as this would trigger a ramp up of leads current
return change.target * 1e+4, change.ramp / 6e-3, change.approachmode, change.persistentmode
def write_target(self, target):
if abs(self.target - self.value) <= 2e-5 and target == self.target:
return None # avoid ramping leads
self._status_before_change = list(self.status)
self._stopped = False
self._last_change = time.time()
self.status = [self.Status.BUSY, 'changed target']
return target
def write_persistentmode(self, mode):
if abs(self.target - self.value) <= 2e-5 and mode == self.persistentmode:
return None # avoid ramping leads
self._last_change = time.time()
self._status_before_change = list(self.status)
self._stopped = False
self.status = [self.Status.BUSY, 'changed persistent mode']
return mode
def write_ramp(self, value):
if self.isDriving():
return value
return None # change_field will not be called, as this would trigger a ramp up of leads current
def write_approachmode(self, value):
if self.isDriving():
return value
return None # change_temp will not be called, as this would trigger a ramp up of leads current
def do_stop(self):
if not self.isDriving():
return
self.status = [self.Status.IDLE, '_stopped']
newtarget = clamp(self._last_target, self.value, self.target)
if newtarget != self.target:
self.log.debug('stop at %s T', newtarget)
self.write_target(newtarget)
self.status = [self.status[0], 'stopping (%s)' % self.status[1]]
self._stopped = True
if abs(self.value - self.target) > 1e-4:
# ramping is not yet at end
if abs(self.value - self._last_target) < 1e-4:
# ramping has not started yet, use more precise last target instead of current value
self.target = self.put_settings(self._last_target, 'target')
else:
self.target = self.put_settings(self.value, 'target')
class Position(PpmsMixin, Drivable):
@ -684,7 +697,7 @@ class Position(PpmsMixin, Drivable):
channel = 'position'
_stopped = False
_last_target = 0
_last_target = None # last reached target
_last_change = 0 # means no target change is pending
def update_value_status(self, value, packed_status):
@ -697,12 +710,6 @@ class Position(PpmsMixin, Drivable):
return
self.value = value
status = self.STATUS_MAP[(packed_status >> 12) & 0xf]
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
self._stopped = False
else:
status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
if self._last_change: # there was a change, which is not yet confirmed by hw
now = time.time()
if now > self._last_change + 5:
@ -712,6 +719,16 @@ class Position(PpmsMixin, Drivable):
self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
else:
status = [self.Status.BUSY, 'changed target']
if abs(self.value - self.target) < 0.1:
self._last_target = self.target
elif self._last_target is None:
self._last_target = self.value
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
status = [status[0], 'stopped']
else:
status = [status[0], 'stopping (%s)' % status[1]]
self.status = status
def analyze_move(self, target, mode, speed):
@ -727,7 +744,6 @@ class Position(PpmsMixin, Drivable):
return change.target, 0, speed
def write_target(self, target):
self._last_target = self.target # save for stop command
self._stopped = False
self._last_change = 0
self._status_before_change = self.status
@ -736,17 +752,14 @@ class Position(PpmsMixin, Drivable):
def write_speed(self, value):
if self.isDriving():
return value
return None # change_move not called: as this would trigger an unnecessary move
return None # change_move not called as this would trigger an unnecessary move
def do_stop(self):
if not self.isDriving():
return
self.status = [self.Status.BUSY, '_stopped']
newtarget = clamp(self._last_target, self.value, self.target)
if newtarget != self.target:
self.log.debug('stop at %s T', newtarget)
self.write_target(newtarget)
self.status = [self.status[0], 'stopping (%s)' % self.status[1]]
self._stopped = True
if abs(self.value - self.target) > 1e-2:
# moving is not yet at end
if abs(self.value - self._last_target) < 1e-2:
# moving has not started yet, use more precise last target instead of current value
self.target = self.write_target(self._last_target)
else:
self.target = self.write_target(self.value)

View File

@ -54,7 +54,7 @@ class PpmsSim:
self.status = NamedList('t mf ch pos', 1, 1, 1, 1)
self.st = 0x1111
self.t = 200
self.temp = NamedList('target ramp amode', 295., 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.field = NamedList('target ramp amode pmode', 0, 50, 0, 0)
self.pos = 0
@ -75,6 +75,7 @@ class PpmsSim:
self.start = self.time
self.mf_start = 0
self.ch_start = 0
self.t_start = 0
self.changed = set()
def progress(self):