remove UNKNOWN, UNSTABLE and DISABLED from Readable.status

- re-add them where needed (epics, entangle ...)

Change-Id: I2b8af9f5f86285f081d5418211f6940e80a1dbd7
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30718
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2023-03-20 14:14:12 +01:00
parent 11a3bed8b8
commit 361f9ac4fc
8 changed files with 169 additions and 108 deletions

View File

@ -58,3 +58,4 @@ FINALIZING = StatusType.FINALIZING
ERROR = StatusType.ERROR ERROR = StatusType.ERROR
ERROR_STANDBY = StatusType.ERROR_STANDBY ERROR_STANDBY = StatusType.ERROR_STANDBY
ERROR_PREPARED = StatusType.ERROR_PREPARED ERROR_PREPARED = StatusType.ERROR_PREPARED
UNKNOWN = StatusType.UNKNOWN # no SECoP standard (yet)

View File

@ -834,10 +834,7 @@ class Readable(Module):
Status = Enum('Status', Status = Enum('Status',
IDLE=StatusType.IDLE, IDLE=StatusType.IDLE,
WARN=StatusType.WARN, WARN=StatusType.WARN,
UNSTABLE=270, # not SECoP standard. TODO: remove and adapt entangle
ERROR=StatusType.ERROR, ERROR=StatusType.ERROR,
DISABLED=StatusType.DISABLED,
UNKNOWN=401, # not SECoP standard. TODO: remove and adapt entangle and epics
) #: status code Enum: extended automatically in inherited modules ) #: status code Enum: extended automatically in inherited modules
value = Parameter('current value of the module', FloatRange()) value = Parameter('current value of the module', FloatRange())
status = Parameter('current status of the module', StatusType(Status), status = Parameter('current status of the module', StatusType(Status),

View File

@ -21,7 +21,7 @@
# ***************************************************************************** # *****************************************************************************
from frappy.datatypes import EnumType, FloatRange, StringType from frappy.datatypes import EnumType, FloatRange, StringType, StatusType
from frappy.modules import Drivable, Parameter, Readable from frappy.modules import Drivable, Parameter, Readable
try: try:
@ -71,7 +71,7 @@ class EpicsReadable(Readable):
status_pv = Parameter('EPICS pv_name of status', status_pv = Parameter('EPICS pv_name of status',
datatype=StringType(), datatype=StringType(),
default="unset", export=False) default="unset", export=False)
status = Parameter(datatype=StatusType(Readable, 'UNKNOWN'))
# Generic read and write functions # Generic read and write functions
def _read_pv(self, pv_name): def _read_pv(self, pv_name):
@ -132,6 +132,7 @@ class EpicsDrivable(Drivable):
default="unset", export=False) default="unset", export=False)
status_pv = Parameter('EPICS pv_name of status', datatype=StringType(), status_pv = Parameter('EPICS pv_name of status', datatype=StringType(),
default="unset", export=False) default="unset", export=False)
status = Parameter(datatype=StatusType(Drivable, 'UNKNOWN'))
# Generic read and write functions # Generic read and write functions

View File

@ -41,7 +41,7 @@ from frappy.datatypes import ArrayOf, EnumType, FloatRange, \
from frappy.errors import CommunicationFailedError, \ from frappy.errors import CommunicationFailedError, \
ConfigError, HardwareError, ProgrammingError ConfigError, HardwareError, ProgrammingError
from frappy.lib import lazy_property from frappy.lib import lazy_property
from frappy.modules import Command, \ from frappy.modules import Command, StatusType, \
Drivable, Module, Parameter, Readable Drivable, Module, Parameter, Readable
##### #####
@ -173,11 +173,11 @@ class BasePyTangoDevice:
) )
tango_status_mapping = { tango_status_mapping = {
PyTango.DevState.ON: Drivable.Status.IDLE, PyTango.DevState.ON: StatusType.IDLE,
PyTango.DevState.ALARM: Drivable.Status.WARN, PyTango.DevState.ALARM: StatusType.WARN,
PyTango.DevState.OFF: Drivable.Status.DISABLED, PyTango.DevState.OFF: StatusType.DISABLED,
PyTango.DevState.FAULT: Drivable.Status.ERROR, PyTango.DevState.FAULT: StatusType.ERROR,
PyTango.DevState.MOVING: Drivable.Status.BUSY, PyTango.DevState.MOVING: StatusType.BUSY,
} }
@lazy_property @lazy_property
@ -364,13 +364,15 @@ class BasePyTangoDevice:
class PyTangoDevice(BasePyTangoDevice): class PyTangoDevice(BasePyTangoDevice):
"""Base for "normal" devices with status.""" """Base for "normal" devices with status."""
status = Parameter(datatype=StatusType(Readable, 'UNKNOWN', 'DISABLED'))
def read_status(self): def read_status(self):
# Query status code and string # Query status code and string
tangoState = self._dev.State() tangoState = self._dev.State()
tangoStatus = self._dev.Status() tangoStatus = self._dev.Status()
# Map status # Map status
myState = self.tango_status_mapping.get(tangoState, Drivable.Status.UNKNOWN) myState = self.tango_status_mapping.get(tangoState, StatusType.UNKNOWN)
return (myState, tangoStatus) return (myState, tangoStatus)
@ -456,6 +458,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
default=60.0, readonly=False, default=60.0, readonly=False,
datatype=FloatRange(0, 900, unit='s'), group='stability', datatype=FloatRange(0, 900, unit='s'), group='stability',
) )
status = Parameter(datatype=StatusType(PyTangoDevice, 'BUSY', 'UNSTABLE'))
_history = () _history = ()
_timeout = None _timeout = None
@ -520,7 +523,7 @@ class AnalogOutput(PyTangoDevice, Drivable):
def read_status(self): def read_status(self):
status = super().read_status() status = super().read_status()
if status[0] in (Readable.Status.DISABLED, Readable.Status.ERROR): if status[0] in (StatusType.DISABLED, StatusType.ERROR):
self.setFastPoll(False) self.setFastPoll(False)
return status return status
if self._isAtTarget(): if self._isAtTarget():
@ -857,7 +860,7 @@ class PartialDigitalInput(NamedDigitalInput):
return value # mapping is done by datatype upon export() return value # mapping is done by datatype upon export()
class DigitalOutput(PyTangoDevice, Drivable): class DigitalOutput(PyTangoDevice):
"""A device that can set and read a digital value corresponding to a """A device that can set and read a digital value corresponding to a
bitfield. bitfield.
""" """

View File

@ -23,7 +23,7 @@
"""drivers for CCU4, the cryostat control unit at SINQ""" """drivers for CCU4, the cryostat control unit at SINQ"""
# the most common Frappy classes can be imported from frappy.core # the most common Frappy classes can be imported from frappy.core
from frappy.core import EnumType, FloatRange, \ from frappy.core import EnumType, FloatRange, \
HasIO, Parameter, Readable, StringIO HasIO, Parameter, Readable, StringIO, StatusType
class CCU4IO(StringIO): class CCU4IO(StringIO):
@ -51,16 +51,16 @@ class HeLevel(HasIO, Readable):
readonly=False) readonly=False)
sample_rate = Parameter('sample rate', EnumType(slow=0, fast=1), readonly=False) sample_rate = Parameter('sample rate', EnumType(slow=0, fast=1), readonly=False)
Status = Readable.Status status = Parameter(datatype=StatusType(Readable, 'DISABLED'))
# conversion of the code from the CCU4 parameter 'hsf' # conversion of the code from the CCU4 parameter 'hsf'
STATUS_MAP = { STATUS_MAP = {
0: (Status.IDLE, 'sensor ok'), 0: (StatusType.IDLE, 'sensor ok'),
1: (Status.ERROR, 'sensor warm'), 1: (StatusType.ERROR, 'sensor warm'),
2: (Status.ERROR, 'no sensor'), 2: (StatusType.ERROR, 'no sensor'),
3: (Status.ERROR, 'timeout'), 3: (StatusType.ERROR, 'timeout'),
4: (Status.ERROR, 'not yet read'), 4: (StatusType.ERROR, 'not yet read'),
5: (Status.DISABLED, 'disabled'), 5: (StatusType.DISABLED, 'disabled'),
} }
def query(self, cmd): def query(self, cmd):

View File

@ -31,7 +31,7 @@ import time
from ast import literal_eval from ast import literal_eval
import frappy.io import frappy.io
from frappy.datatypes import BoolType, EnumType, FloatRange, IntRange from frappy.datatypes import BoolType, EnumType, FloatRange, IntRange, StatusType
from frappy.lib import formatStatusBits from frappy.lib import formatStatusBits
from frappy.core import Done, Drivable, Parameter, Property, CommonReadHandler, CommonWriteHandler from frappy.core import Done, Drivable, Parameter, Property, CommonReadHandler, CommonWriteHandler
from frappy.io import HasIO from frappy.io import HasIO
@ -158,6 +158,7 @@ class ResChannel(Channel):
channel = Property('the Lakeshore channel', datatype=IntRange(1, 16), export=False) channel = Property('the Lakeshore channel', datatype=IntRange(1, 16), export=False)
value = Parameter(datatype=FloatRange(unit='Ohm')) value = Parameter(datatype=FloatRange(unit='Ohm'))
status = Parameter(datatype=StatusType(Drivable, 'DISABLED'))
pollinterval = Parameter(visibility=3, default=1) pollinterval = Parameter(visibility=3, default=1)
range = Parameter('reading range', readonly=False, range = Parameter('reading range', readonly=False,
datatype=EnumType(**RES_RANGE)) datatype=EnumType(**RES_RANGE))

View File

@ -28,7 +28,7 @@ The PPMS hardware has some special requirements:
needing a mechanism to treat a single parameter change correctly. needing a mechanism to treat a single parameter change correctly.
Polling of value and status is done commonly for all modules. For each registered module Polling of value and status is done commonly for all modules. For each registered module
<module>.update_value_status() is called in order to update their value and status. <module>.update_value_status() is called in order to update their value and StatusType.
""" """
import threading import threading
@ -39,7 +39,6 @@ from frappy.datatypes import BoolType, EnumType, \
FloatRange, IntRange, StatusType, StringType FloatRange, IntRange, StatusType, StringType
from frappy.errors import HardwareError from frappy.errors import HardwareError
from frappy.lib import clamp from frappy.lib import clamp
from frappy.lib.enum import Enum
from frappy.modules import Communicator, \ from frappy.modules import Communicator, \
Drivable, Parameter, Property, Readable Drivable, Parameter, Property, Readable
from frappy.io import HasIO from frappy.io import HasIO
@ -117,7 +116,7 @@ class Main(Communicator):
class PpmsBase(HasIO, Readable): class PpmsBase(HasIO, Readable):
"""common base for all ppms modules""" """common base for all ppms modules"""
value = Parameter(needscfg=False) value = Parameter(needscfg=False)
status = Parameter(needscfg=False) status = Parameter(datatype=StatusType(Readable, 'DISABLED'), needscfg=False)
enabled = True # default, if no parameter enable is defined enabled = True # default, if no parameter enable is defined
_last_settings = None # used by several modules _last_settings = None # used by several modules
@ -140,13 +139,13 @@ class PpmsBase(HasIO, Readable):
# update value and status # update value and status
# to be reimplemented for modules looking at packed_status # to be reimplemented for modules looking at packed_status
if not self.enabled: if not self.enabled:
self.status = (self.Status.DISABLED, 'disabled') self.status = (StatusType.DISABLED, 'disabled')
return return
if value is None: if value is None:
self.status = (self.Status.ERROR, 'invalid value') self.status = (StatusType.ERROR, 'invalid value')
else: else:
self.value = value self.value = value
self.status = (self.Status.IDLE, '') self.status = (StatusType.IDLE, '')
def comm_write(self, command): def comm_write(self, command):
"""write command and check if result is OK""" """write command and check if result is OK"""
@ -293,19 +292,18 @@ class Chamber(PpmsDrivable):
value is an Enum, which is redundant with the status text value is an Enum, which is redundant with the status text
""" """
Status = Drivable.Status
code_table = [ code_table = [
# valuecode, status, statusname, opcode, targetname # valuecode, status, statusname, opcode, targetname
(0, Status.IDLE, 'unknown', 10, 'noop'), (0, StatusType.IDLE, 'unknown', 10, 'noop'),
(1, Status.IDLE, 'purged_and_sealed', 1, 'purge_and_seal'), (1, StatusType.IDLE, 'purged_and_sealed', 1, 'purge_and_seal'),
(2, Status.IDLE, 'vented_and_sealed', 2, 'vent_and_seal'), (2, StatusType.IDLE, 'vented_and_sealed', 2, 'vent_and_seal'),
(3, Status.WARN, 'sealed_unknown', 0, 'seal_immediately'), (3, StatusType.WARN, 'sealed_unknown', 0, 'seal_immediately'),
(4, Status.BUSY, 'purge_and_seal', None, None), (4, StatusType.BUSY, 'purge_and_seal', None, None),
(5, Status.BUSY, 'vent_and_seal', None, None), (5, StatusType.BUSY, 'vent_and_seal', None, None),
(6, Status.BUSY, 'pumping_down', None, None), (6, StatusType.BUSY, 'pumping_down', None, None),
(8, Status.IDLE, 'pumping_continuously', 3, 'pump_continuously'), (8, StatusType.IDLE, 'pumping_continuously', 3, 'pump_continuously'),
(9, Status.IDLE, 'venting_continuously', 4, 'vent_continuously'), (9, StatusType.IDLE, 'venting_continuously', 4, 'vent_continuously'),
(15, Status.ERROR, 'general_failure', None, None), (15, StatusType.ERROR, 'general_failure', None, None),
] ]
value_codes = {k: v for v, _, k, _, _ in code_table} value_codes = {k: v for v, _, k, _, _ in code_table}
target_codes = {k: v for v, _, _, _, k in code_table if k} target_codes = {k: v for v, _, _, _, k in code_table if k}
@ -324,7 +322,7 @@ class Chamber(PpmsDrivable):
self.status = self.status_map[status_code] self.status = self.status_map[status_code]
else: else:
self.value = self.value_map['unknown'] self.value = self.value_map['unknown']
self.status = (self.Status.ERROR, 'unknown status code %d' % status_code) self.status = (StatusType.ERROR, 'unknown status code %d' % status_code)
def read_target(self): def read_target(self):
opcode = int(self.communicate('CHAMBER?')) opcode = int(self.communicate('CHAMBER?'))
@ -341,13 +339,8 @@ class Chamber(PpmsDrivable):
class Temp(PpmsDrivable): class Temp(PpmsDrivable):
"""temperature""" """temperature"""
Status = Enum(
Drivable.Status,
RAMPING=370,
STABILIZING=380,
)
value = Parameter(datatype=FloatRange(unit='K')) value = Parameter(datatype=FloatRange(unit='K'))
status = Parameter(datatype=StatusType(Status)) status = Parameter(datatype=StatusType(Drivable, 'RAMPING', 'STABILIZING'))
target = Parameter(datatype=FloatRange(1.7, 402.0, unit='K'), needscfg=False) target = Parameter(datatype=FloatRange(1.7, 402.0, unit='K'), needscfg=False)
setpoint = Parameter('intermediate set point', setpoint = Parameter('intermediate set point',
datatype=FloatRange(1.7, 402.0, unit='K')) datatype=FloatRange(1.7, 402.0, unit='K'))
@ -362,15 +355,15 @@ class Temp(PpmsDrivable):
general_stop = Property('respect general stop', datatype=BoolType(), general_stop = Property('respect general stop', datatype=BoolType(),
default=True, value=False) default=True, value=False)
STATUS_MAP = { STATUS_MAP = {
1: (Status.IDLE, 'stable at target'), 1: (StatusType.IDLE, 'stable at target'),
2: (Status.RAMPING, 'ramping'), 2: (StatusType.RAMPING, 'ramping'),
5: (Status.STABILIZING, 'within tolerance'), 5: (StatusType.STABILIZING, 'within tolerance'),
6: (Status.STABILIZING, 'outside tolerance'), 6: (StatusType.STABILIZING, 'outside tolerance'),
7: (Status.STABILIZING, 'filling/emptying reservoir'), 7: (StatusType.STABILIZING, 'filling/emptying reservoir'),
10: (Status.WARN, 'standby'), 10: (StatusType.WARN, 'standby'),
13: (Status.WARN, 'control disabled'), 13: (StatusType.WARN, 'control disabled'),
14: (Status.ERROR, 'can not complete'), 14: (StatusType.ERROR, 'can not complete'),
15: (Status.ERROR, 'general failure'), 15: (StatusType.ERROR, 'general failure'),
} }
channel = 'temp' channel = 'temp'
@ -420,11 +413,11 @@ class Temp(PpmsDrivable):
def update_value_status(self, value, packed_status): def update_value_status(self, value, packed_status):
if value is None: if value is None:
self.status = (self.Status.ERROR, 'invalid value') self.status = (StatusType.ERROR, 'invalid value')
return return
self.value = value self.value = value
status_code = packed_status & 0xf status_code = packed_status & 0xf
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code)) status = self.STATUS_MAP.get(status_code, (StatusType.ERROR, 'unknown status code %d' % status_code))
now = time.time() now = time.time()
if value > 11: if value > 11:
# when starting from T > 50, this will be 15 min. # when starting from T > 50, this will be 15 min.
@ -436,7 +429,7 @@ class Temp(PpmsDrivable):
self._wait_at10 = False self._wait_at10 = False
self._last_change = now self._last_change = now
self._write_params(self.target, self.ramp, self.approachmode) self._write_params(self.target, self.ramp, self.approachmode)
status = (self.Status.STABILIZING, 'waiting at 10 K') status = (StatusType.STABILIZING, 'waiting at 10 K')
if 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 now > self._last_change + 5: if now > self._last_change + 5:
self._last_change = 0 # give up waiting for busy self._last_change = 0 # give up waiting for busy
@ -444,14 +437,14 @@ class Temp(PpmsDrivable):
self.log.debug('time needed to change to busy: %.3g', now - self._last_change) self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
self._last_change = 0 self._last_change = 0
else: else:
status = (self.Status.BUSY, 'changed target') status = (StatusType.BUSY, 'changed target')
if abs(self.value - self.target) < self.target * 0.01: if abs(self.value - self.target) < self.target * 0.01:
self._last_target = self.target self._last_target = self.target
elif self._last_target is None: elif self._last_target is None:
self._last_target = self.value self._last_target = self.value
if self._stopped: if self._stopped:
# combine 'stopped' with current status text # combine 'stopped' with current status text
if status[0] == self.Status.IDLE: if status[0] == StatusType.IDLE:
status = (status[0], 'stopped') status = (status[0], 'stopped')
else: else:
status = (status[0], 'stopping (%s)' % status[1]) status = (status[0], 'stopping (%s)' % status[1])
@ -459,7 +452,7 @@ class Temp(PpmsDrivable):
# handle timeout # handle timeout
if self.isDriving(status): if self.isDriving(status):
if now > self._expected_target_time + self.timeout: if now > self._expected_target_time + self.timeout:
status = (self.Status.WARN, 'timeout while %s' % status[1]) status = (StatusType.WARN, 'timeout while %s' % status[1])
else: else:
self._expected_target_time = 0 self._expected_target_time = 0
self.status = status self.status = status
@ -469,7 +462,7 @@ class Temp(PpmsDrivable):
if abs(self.target - self.value) <= 2e-5 * target and target == self.target: if abs(self.target - self.value) <= 2e-5 * target and target == self.target:
return None return None
self._status_before_change = self.status self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target') self.status = (StatusType.BUSY, 'changed target')
self._last_change = time.time() self._last_change = time.time()
self._write_params(target, self.ramp, self.approachmode) self._write_params(target, self.ramp, self.approachmode)
self.log.debug('write_target %s' % repr((self.setpoint, target, self._wait_at10))) self.log.debug('write_target %s' % repr((self.setpoint, target, self._wait_at10)))
@ -493,7 +486,7 @@ class Temp(PpmsDrivable):
def stop(self): def stop(self):
if not self.isDriving(): if not self.isDriving():
return return
if self.status[0] != self.Status.STABILIZING: if self.status[0] != StatusType.STABILIZING:
# we are not near target # we are not near target
newtarget = clamp(self._last_target, self.value, self.target) newtarget = clamp(self._last_target, self.value, self.target)
if newtarget != self.target: if newtarget != self.target:
@ -506,16 +499,8 @@ class Temp(PpmsDrivable):
class Field(PpmsDrivable): class Field(PpmsDrivable):
"""magnetic field""" """magnetic field"""
Status = Enum(
Drivable.Status,
PREPARED=150,
PREPARING=340,
RAMPING=370,
STABILIZING=380,
FINALIZING=390,
)
value = Parameter(datatype=FloatRange(unit='T')) value = Parameter(datatype=FloatRange(unit='T'))
status = Parameter(datatype=StatusType(Status)) status = Parameter(datatype=StatusType(Drivable, 'PREPARED', 'PREPARING', 'RAMPING', 'STABILIZING', 'FINALIZING'))
target = Parameter(datatype=FloatRange(-15, 15, unit='T')) # poll only one parameter target = Parameter(datatype=FloatRange(-15, 15, unit='T')) # poll only one parameter
ramp = Parameter('ramping speed', readonly=False, ramp = Parameter('ramping speed', readonly=False,
datatype=FloatRange(0.064, 1.19, unit='T/min'), default=0.19) datatype=FloatRange(0.064, 1.19, unit='T/min'), default=0.19)
@ -525,16 +510,16 @@ class Field(PpmsDrivable):
datatype=EnumType(persistent=0, driven=1), default=0) datatype=EnumType(persistent=0, driven=1), default=0)
STATUS_MAP = { STATUS_MAP = {
1: (Status.IDLE, 'persistent mode'), 1: (StatusType.IDLE, 'persistent mode'),
2: (Status.PREPARING, 'switch warming'), 2: (StatusType.PREPARING, 'switch warming'),
3: (Status.FINALIZING, 'switch cooling'), 3: (StatusType.FINALIZING, 'switch cooling'),
4: (Status.IDLE, 'driven stable'), 4: (StatusType.IDLE, 'driven stable'),
5: (Status.STABILIZING, 'driven final'), 5: (StatusType.STABILIZING, 'driven final'),
6: (Status.RAMPING, 'charging'), 6: (StatusType.RAMPING, 'charging'),
7: (Status.RAMPING, 'discharging'), 7: (StatusType.RAMPING, 'discharging'),
8: (Status.ERROR, 'current error'), 8: (StatusType.ERROR, 'current error'),
11: (Status.ERROR, 'probably quenched'), 11: (StatusType.ERROR, 'probably quenched'),
15: (Status.ERROR, 'general failure'), 15: (StatusType.ERROR, 'general failure'),
} }
channel = 'field' channel = 'field'
@ -563,33 +548,33 @@ class Field(PpmsDrivable):
def update_value_status(self, value, packed_status): def update_value_status(self, value, packed_status):
if value is None: if value is None:
self.status = (self.Status.ERROR, 'invalid value') self.status = (StatusType.ERROR, 'invalid value')
return return
self.value = round(value * 1e-4, 7) self.value = round(value * 1e-4, 7)
status_code = (packed_status >> 4) & 0xf status_code = (packed_status >> 4) & 0xf
status = self.STATUS_MAP.get(status_code, (self.Status.ERROR, 'unknown status code %d' % status_code)) status = self.STATUS_MAP.get(status_code, (StatusType.ERROR, 'unknown status code %d' % status_code))
now = time.time() now = time.time()
if 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 if status_code == 1: # persistent mode
# leads are ramping (ppms has no extra status code for this!) # leads are ramping (ppms has no extra status code for this!)
if now < self._last_change + 30: if now < self._last_change + 30:
status = (self.Status.PREPARING, 'ramping leads') status = (StatusType.PREPARING, 'ramping leads')
else: else:
status = (self.Status.WARN, 'timeout when ramping leads') status = (StatusType.WARN, 'timeout when ramping leads')
elif now > self._last_change + 5: elif now > self._last_change + 5:
self._last_change = 0 # give up waiting for driving self._last_change = 0 # give up waiting for driving
elif self.isDriving(status) and status != self._status_before_change: elif self.isDriving(status) and status != self._status_before_change:
self._last_change = 0 self._last_change = 0
self.log.debug('time needed to change to busy: %.3g', now - self._last_change) self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
else: else:
status = (self.Status.BUSY, 'changed target') status = (StatusType.BUSY, 'changed target')
if abs(self.target - self.value) <= 1e-4: if abs(self.target - self.value) <= 1e-4:
self._last_target = self.target self._last_target = self.target
elif self._last_target is None: elif self._last_target is None:
self._last_target = self.value self._last_target = self.value
if self._stopped: if self._stopped:
# combine 'stopped' with current status text # combine 'stopped' with current status text
if status[0] == self.Status.IDLE: if status[0] == StatusType.IDLE:
status = (status[0], 'stopped') status = (status[0], 'stopped')
else: else:
status = (status[0], 'stopping (%s)' % status[1]) status = (status[0], 'stopping (%s)' % status[1])
@ -602,7 +587,7 @@ class Field(PpmsDrivable):
self._status_before_change = self.status self._status_before_change = self.status
self._stopped = False self._stopped = False
self._last_change = time.time() self._last_change = time.time()
self.status = (self.Status.BUSY, 'changed target') self.status = (StatusType.BUSY, 'changed target')
self._write_params(target, self.ramp, self.approachmode, self.persistentmode) self._write_params(target, self.ramp, self.approachmode, self.persistentmode)
return self.target return self.target
@ -613,7 +598,7 @@ class Field(PpmsDrivable):
self._last_change = time.time() self._last_change = time.time()
self._status_before_change = self.status self._status_before_change = self.status
self._stopped = False self._stopped = False
self.status = (self.Status.BUSY, 'changed persistent mode') self.status = (StatusType.BUSY, 'changed persistent mode')
self._write_params(self.target, self.ramp, self.approachmode, mode) self._write_params(self.target, self.ramp, self.approachmode, mode)
return self.persistentmode return self.persistentmode
@ -642,8 +627,6 @@ class Field(PpmsDrivable):
class Position(PpmsDrivable): class Position(PpmsDrivable):
"""rotator position""" """rotator position"""
Status = Drivable.Status
value = Parameter(datatype=FloatRange(unit='deg')) value = Parameter(datatype=FloatRange(unit='deg'))
target = Parameter(datatype=FloatRange(-720., 720., unit='deg')) target = Parameter(datatype=FloatRange(-720., 720., unit='deg'))
enabled = Parameter('is this channel used?', readonly=False, enabled = Parameter('is this channel used?', readonly=False,
@ -651,11 +634,11 @@ class Position(PpmsDrivable):
speed = Parameter('motor speed', readonly=False, default=12, speed = Parameter('motor speed', readonly=False, default=12,
datatype=FloatRange(0.8, 12, unit='deg/sec')) datatype=FloatRange(0.8, 12, unit='deg/sec'))
STATUS_MAP = { STATUS_MAP = {
1: (Status.IDLE, 'at target'), 1: (StatusType.IDLE, 'at target'),
5: (Status.BUSY, 'moving'), 5: (StatusType.BUSY, 'moving'),
8: (Status.IDLE, 'at limit'), 8: (StatusType.IDLE, 'at limit'),
9: (Status.IDLE, 'at index'), 9: (StatusType.IDLE, 'at index'),
15: (Status.ERROR, 'general failure'), 15: (StatusType.ERROR, 'general failure'),
} }
channel = 'position' channel = 'position'
@ -683,14 +666,14 @@ class Position(PpmsDrivable):
def update_value_status(self, value, packed_status): def update_value_status(self, value, packed_status):
if not self.enabled: if not self.enabled:
self.status = (self.Status.DISABLED, 'disabled') self.status = (StatusType.DISABLED, 'disabled')
return return
if value is None: if value is None:
self.status = (self.Status.ERROR, 'invalid value') self.status = (StatusType.ERROR, 'invalid value')
return return
self.value = value self.value = value
status_code = (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)) status = self.STATUS_MAP.get(status_code, (StatusType.ERROR, 'unknown status code %d' % status_code))
if 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
now = time.time() now = time.time()
if now > self._last_change + 5: if now > self._last_change + 5:
@ -699,20 +682,20 @@ class Position(PpmsDrivable):
self.log.debug('time needed to change to busy: %.3g', now - self._last_change) self.log.debug('time needed to change to busy: %.3g', now - self._last_change)
self._last_change = 0 self._last_change = 0
else: else:
status = (self.Status.BUSY, 'changed target') status = (StatusType.BUSY, 'changed target')
# BUSY can not reliably be determined from the status code, we have to do it on our own # 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: if abs(value - self.target) < 0.1:
self._last_target = self.target self._last_target = self.target
if not self._within_target: if not self._within_target:
self._within_target = time.time() self._within_target = time.time()
if time.time() > self._within_target + 1: if time.time() > self._within_target + 1:
if status[0] != self.Status.IDLE: if status[0] != StatusType.IDLE:
status = (self.Status.IDLE, status[1]) status = (StatusType.IDLE, status[1])
elif status[0] != self.Status.BUSY: elif status[0] != StatusType.BUSY:
status = (self.Status.BUSY, status[1]) status = (StatusType.BUSY, status[1])
if self._stopped: if self._stopped:
# combine 'stopped' with current status text # combine 'stopped' with current status text
if status[0] == self.Status.IDLE: if status[0] == StatusType.IDLE:
status = (status[0], 'stopped') status = (status[0], 'stopped')
else: else:
status = (status[0], 'stopping (%s)' % status[1]) status = (status[0], 'stopping (%s)' % status[1])
@ -722,7 +705,7 @@ class Position(PpmsDrivable):
self._stopped = False self._stopped = False
self._last_change = 0 self._last_change = 0
self._status_before_change = self.status self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target') self.status = (StatusType.BUSY, 'changed target')
self._write_params(target, self.speed) self._write_params(target, self.speed)
return self.target return self.target

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# *****************************************************************************
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Module authors:
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from frappy.core import Readable, Drivable, StatusType
from frappy_ess.epics import EpicsReadable, EpicsDrivable
readable_codes = {m.name: m.value for m in Readable.Status.members}
drivable_codes = {m.name: m.value for m in Drivable.Status.members}
def test_entangle_status():
try:
# pylint: disable=import-outside-toplevel
# pylint: disable=unused-import
import PyTango
except ImportError:
return
# pylint: disable=import-outside-toplevel
from frappy_mlz.entangle import AnalogInput, AnalogOutput, TemperatureController
assert AnalogInput.status.datatype.members[0].export_datatype() == {
"type": "enum",
"members": {"UNKNOWN": StatusType.UNKNOWN,
"DISABLED": StatusType.DISABLED,
**readable_codes},
}
assert AnalogOutput.status.datatype.members[0].export_datatype() == {
"type": "enum",
"members": {"UNKNOWN": StatusType.UNKNOWN,
"DISABLED": StatusType.DISABLED,
"UNSTABLE": StatusType.UNSTABLE,
**drivable_codes},
}
assert TemperatureController.status.datatype.members[0].export_datatype() == {
"type": "enum",
"members": {"UNKNOWN": StatusType.UNKNOWN,
"DISABLED": StatusType.DISABLED,
"UNSTABLE": StatusType.UNSTABLE,
**drivable_codes},
}
def test_epics_status():
assert EpicsReadable.status.datatype.members[0].export_datatype() == {
"type": "enum",
"members": {"UNKNOWN": StatusType.UNKNOWN,
**readable_codes},
}
assert EpicsDrivable.status.datatype.members[0].export_datatype() == {
"type": "enum",
"members": {"UNKNOWN": StatusType.UNKNOWN,
**drivable_codes},
}