ppms: improve status and temperature

- treat unknown status values similar to status 0
- wait at 10 K when cooling below
- better error message

Change-Id: Ic07826e31f36abc72ee5d72da001fb1f3d2fe8aa
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/23121
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2020-05-20 15:55:23 +02:00
parent 7953826fac
commit 31ae0a88b4
2 changed files with 86 additions and 44 deletions

View File

@ -352,7 +352,6 @@ class Chamber(PpmsMixin, Drivable):
Override(visibility=3),
}
STATUS_MAP = {
StatusCode.unknown: (Status.WARN, 'unknown'),
StatusCode.purged_and_sealed: (Status.IDLE, 'purged and sealed'),
StatusCode.vented_and_sealed: (Status.IDLE, 'vented and sealed'),
StatusCode.sealed_unknown: (Status.WARN, 'sealed unknown'),
@ -369,8 +368,13 @@ class Chamber(PpmsMixin, Drivable):
def update_value_status(self, value, packed_status):
"""update value and status"""
self.value = (packed_status >> 8) & 0xf
self.status = self.STATUS_MAP[self.value]
status_code = (packed_status >> 8) & 0xf
if status_code in self.STATUS_MAP:
self.value = status_code
self.status = self.STATUS_MAP[status_code]
else:
self.value = self.StatusCode.unknown
self.status = (self.Status.ERROR, 'unknown status code %d' % status_code)
def analyze_chamber(self, target):
return dict(target=target)
@ -401,9 +405,12 @@ class Temp(PpmsMixin, Drivable):
'status':
Override(datatype=StatusType(Status), poll=True),
'target':
Override(datatype=FloatRange(1.7, 402.0, unit='K'), handler=temp),
Override(datatype=FloatRange(1.7, 402.0, unit='K'), poll=False, needscfg=False),
'setpoint':
Parameter('intermediate set point',
datatype=FloatRange(1.7, 402.0, unit='K'), handler=temp),
'ramp':
Parameter('ramping speed', readonly=False, handler=temp,
Parameter('ramping speed', readonly=False, handler=temp, default=0,
datatype=FloatRange(0, 20, unit='K/min')),
'approachmode':
Parameter('how to approach target!', readonly=False, handler=temp,
@ -417,22 +424,22 @@ class Temp(PpmsMixin, Drivable):
# pylint: disable=invalid-name
TempStatus = Enum(
'TempStatus',
unknown=0,
stable_at_target=1,
changing=2,
within_tolerance=5,
outside_tolerance=6,
filling_emptying_reservoir=7,
standby=10,
control_disabled=13,
can_not_complete=14,
general_failure=15,
)
STATUS_MAP = {
0: (Status.ERROR, 'unknown'),
1: (Status.IDLE, 'stable at target'),
2: (Status.RAMPING, 'ramping'),
5: (Status.STABILIZING, 'within tolerance'),
6: (Status.STABILIZING, 'outside tolerance'),
7: (Status.STABILIZING, 'filling/emptying reservoir'),
10: (Status.WARN, 'standby'),
13: (Status.WARN, 'control disabled'),
14: (Status.ERROR, 'can not complete'),
@ -450,6 +457,8 @@ class Temp(PpmsMixin, Drivable):
_last_change = 0 # 0 means no target change is pending
_last_target = None # last reached target
general_stop = False
_cool_deadline = 0
_wait_at10 = False
def update_value_status(self, value, packed_status):
"""update value and status"""
@ -457,8 +466,20 @@ class Temp(PpmsMixin, Drivable):
self.status = (self.Status.ERROR, 'invalid value')
return
self.value = value
status = self.STATUS_MAP[packed_status & 0xf]
status_code = packed_status & 0xf
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code))
now = time.time()
if value > 11:
# when starting from T > 40, this will be 15 min.
# when starting from lower T, it will be less
# 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
elif self._wait_at10:
if now > self._cool_deadline:
self._wait_at10 = False
self._last_change = now
self.temp.write(self, 'setpoint', self.target)
status = (self.Status.STABILIZING, 'waiting at 10 K')
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
@ -486,17 +507,24 @@ class Temp(PpmsMixin, Drivable):
self._expected_target_time = 0
self.status = status
def analyze_temp(self, target, ramp, approachmode):
if (target, ramp, approachmode) == self._last_settings:
# we update parameters only on change, as 'approachmode'
# is not always sent to the hardware
return {}
self._last_settings = target, ramp, approachmode
return dict(target=target, ramp=ramp, approachmode=approachmode)
def analyze_temp(self, setpoint, ramp, approachmode):
if setpoint != 10 or not self._wait_at10:
self.target = setpoint
result = dict(setpoint=setpoint)
# we update ramp and approachmode only at init
if self.ramp == 0:
result['ramp'] = ramp
result['approachmode'] = approachmode
return result
def change_temp(self, change):
self.calc_expected(change.target, self.ramp)
return change.target, change.ramp, change.approachmode
if 10 >= self.value > change.setpoint:
ramp = min(2, change.ramp)
print('ramplimit', change.ramp, self.value, ramp)
else:
ramp = change.ramp
self.calc_expected(change.setpoint, ramp)
return change.setpoint, ramp, change.approachmode
def write_target(self, target):
self._stopped = False
@ -505,8 +533,13 @@ class Temp(PpmsMixin, Drivable):
self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target')
self._last_change = time.time()
self.temp.write(self, 'target', target)
return Done
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)
return target
def write_approachmode(self, value):
if self.isDriving():
@ -571,7 +604,6 @@ class Field(PpmsMixin, Drivable):
}
STATUS_MAP = {
0: (Status.ERROR, 'unknown'),
1: (Status.IDLE, 'persistent mode'),
2: (Status.PREPARING, 'switch warming'),
3: (Status.FINALIZING, 'switch cooling'),
@ -580,6 +612,7 @@ class Field(PpmsMixin, Drivable):
6: (Status.RAMPING, 'charging'),
7: (Status.RAMPING, 'discharging'),
8: (Status.ERROR, 'current error'),
11: (Status.ERROR, 'probably quenched'),
15: (Status.ERROR, 'general failure'),
}
@ -591,26 +624,26 @@ class Field(PpmsMixin, Drivable):
def update_value_status(self, value, packed_status):
"""update value and status"""
if value is None:
self.status = [self.Status.ERROR, 'invalid value']
self.status = (self.Status.ERROR, 'invalid value')
return
self.value = round(value * 1e-4, 7)
status_code = (packed_status >> 4) & 0xf
status = self.STATUS_MAP[status_code]
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code))
now = time.time()
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:
status = [self.Status.PREPARING, 'ramping leads']
status = (self.Status.PREPARING, 'ramping leads')
else:
status = [self.Status.WARN, 'timeout when ramping leads']
status = (self.Status.WARN, 'timeout when ramping leads')
elif now > self._last_change + 5:
self._last_change = 0 # give up waiting for driving
elif self.isDriving(status) and status != self._status_before_change:
self._last_change = 0
self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
else:
status = [self.Status.BUSY, 'changed target']
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:
@ -618,9 +651,9 @@ class Field(PpmsMixin, Drivable):
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
status = [status[0], 'stopped']
status = (status[0], 'stopped')
else:
status = [status[0], 'stopping (%s)' % status[1]]
status = (status[0], 'stopping (%s)' % status[1])
self.status = status
def analyze_field(self, target, ramp, approachmode, persistentmode):
@ -638,10 +671,10 @@ class Field(PpmsMixin, Drivable):
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._status_before_change = self.status
self._stopped = False
self._last_change = time.time()
self.status = [self.Status.BUSY, 'changed target']
self.status = (self.Status.BUSY, 'changed target')
self.field.write(self, 'target', target)
return Done
@ -649,9 +682,9 @@ class Field(PpmsMixin, Drivable):
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._status_before_change = self.status
self._stopped = False
self.status = [self.Status.BUSY, 'changed persistent mode']
self.status = (self.Status.BUSY, 'changed persistent mode')
self.field.write(self, 'persistentmode', mode)
return Done
@ -674,7 +707,7 @@ class Field(PpmsMixin, Drivable):
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.status = (self.status[0], 'stopping (%s)' % self.status[1])
self._stopped = True
@ -698,7 +731,6 @@ class Position(PpmsMixin, Drivable):
Override(visibility=3),
}
STATUS_MAP = {
0: (Status.ERROR, 'unknown'),
1: (Status.IDLE, 'at target'),
5: (Status.BUSY, 'moving'),
8: (Status.IDLE, 'at limit'),
@ -709,7 +741,8 @@ class Position(PpmsMixin, Drivable):
channel = 'position'
_stopped = False
_last_target = None # last reached target
_last_change = 0 # means no target change is pending
_last_change = 0
_within_target = 0 # time since we are within target
def update_value_status(self, value, packed_status):
"""update value and status"""
@ -720,26 +753,33 @@ class Position(PpmsMixin, Drivable):
self.status = (self.Status.ERROR, 'invalid value')
return
self.value = value
status = self.STATUS_MAP[(packed_status >> 12) & 0xf]
status_code = (packed_status >> 12) & 0xf
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code))
if self._last_change: # there was a change, which is not yet confirmed by hw
now = time.time()
if now > self._last_change + 5:
self._last_change = 0 # give up waiting for busy
elif self.isDriving() and status != self._status_before_change:
self._last_change = 0
elif self.isDriving(status) and status != self._status_before_change:
self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
self._last_change = 0
else:
status = [self.Status.BUSY, 'changed target']
if abs(self.value - self.target) < 0.1:
status = (self.Status.BUSY, 'changed target')
# BUSY can not reliably be determined from the status code, we have to do it on our own
if abs(value - self.target) < 0.1:
self._last_target = self.target
elif self._last_target is None:
self._last_target = self.value
if not self._within_target:
self._within_target = time.time()
if time.time() > self._within_target + 1:
if status[0] != self.Status.IDLE:
status = (self.Status.IDLE, status[1])
elif status[0] != self.Status.BUSY:
status = (self.Status.BUSY, status[1])
if self._stopped:
# combine 'stopped' with current status text
if status[0] == self.Status.IDLE:
status = [status[0], 'stopped']
status = (status[0], 'stopped')
else:
status = [status[0], 'stopping (%s)' % status[1]]
status = (status[0], 'stopping (%s)' % status[1])
self.status = status
def analyze_move(self, target, mode, speed):
@ -758,6 +798,7 @@ class Position(PpmsMixin, Drivable):
self._stopped = False
self._last_change = 0
self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target')
self.move.write(self, 'target', target)
return Done

View File

@ -58,7 +58,8 @@ class QDevice:
if err == 1:
# print '<done'
return "OK"
raise Error(args[2].value.replace('\n', ' '))
raise Error('%s on cmd "%s" %s' % (args[2].value.replace('\n', ' '), command,
getattr(args[2], 'value', 'noreply')))
if __name__ == "__main__": # test only