ppms: replace IOHandler by Read/WriteHandler

- add MultiWriteHandler
- the target and value type of secop_psi.ppms.Chamber are enums.
  make the code for them compatible.
+ fix a bug overriding exportname with export=True in parameter

Change-Id: Iec1daf19b3fdf2c017f967e45019867b77c6c59a
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27583
Tested-by: Jenkins Automated Tests <pedersen+jenkins@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:
zolliker 2022-02-01 07:41:50 +01:00
parent c1307cdd03
commit bbc4663266
3 changed files with 252 additions and 242 deletions

View File

@ -216,6 +216,9 @@ class Parameter(Accessible):
self.export = '_' + self.name
else:
raise ProgrammingError('can not use %r as name of a Parameter' % self.name)
if 'export' in self.ownProperties:
# avoid export=True overrides export=<name>
self.ownProperties['export'] = self.export
def copy(self):
"""return a (deep) copy of ourselfs"""
@ -381,6 +384,9 @@ class Command(Accessible):
self.export = '_' + name
else:
raise ProgrammingError('can not use %r as name of a Command' % name) from None
if 'export' in self.ownProperties:
# avoid export=True overrides export=<name>
self.ownProperties['export'] = self.export
if not self._inherit:
for key, pobj in self.properties.items():
if key not in self.propertyValues:

View File

@ -105,7 +105,8 @@ class ReadHandler(Handler):
wrapped.wrapped = True
method = 'read_' + key
if hasattr(owner, method):
rfunc = getattr(owner, method, None)
if rfunc and not rfunc.wrapped:
raise ProgrammingError('superfluous method %s.%s (overwritten by ReadHandler)'
% (owner.__name__, method))
setattr(owner, method, wrapped)
@ -129,7 +130,56 @@ class WriteHandler(Handler):
wrapped.wrapped = True
method = 'write_' + key
if hasattr(owner, method):
wfunc = getattr(owner, method, None)
if wfunc and not wfunc.wrapped:
raise ProgrammingError('superfluous method %s.%s (overwritten by WriteHandler)'
% (owner.__name__, method))
setattr(owner, method, wrapped)
class WriteParameters(dict):
def __init__(self, modobj):
super().__init__()
self.obj = modobj
def __missing__(self, key):
try:
return self.obj.writeDict.pop(key)
except KeyError:
return getattr(self.obj, key)
def as_tuple(self, *keys):
"""return values of given keys as a tuple"""
return tuple(self[k] for k in keys)
class MultiWriteHandler(Handler):
"""decorator for common write handler
calls the wrapped write method function with values as an argument.
- values[pname] returns the to be written value
- values['key'] returns a value taken from writeDict
- or, if not available return obj.key
- values.as_tuple() returns a tuple with the items in the same order as keys
"""
def __set_name__(self, owner, name):
"""create the wrapped write_* methods"""
self.method_names.discard(self.func.__qualname__)
for key in self.keys:
@wraps(self.func)
def wrapped(module, value, pname=key, func=self.func):
values = WriteParameters(module)
values[pname] = value
func(module, values)
return Done
wrapped.wrapped = True
method = 'write_' + key
wfunc = getattr(owner, method, None)
if wfunc and not wfunc.wrapped:
raise ProgrammingError('superfluous method %s.%s (overwritten by WriteHandler)'
% (owner.__name__, method))
setattr(owner, method, wrapped)

View File

@ -33,8 +33,8 @@ Polling of value and status is done commonly for all modules. For each registere
import threading
import time
from ast import literal_eval # convert string as comma separated numbers into tuple
import secop.iohandler
from secop.datatypes import BoolType, EnumType, \
FloatRange, IntRange, StatusType, StringType
from secop.errors import HardwareError
@ -44,6 +44,7 @@ from secop.modules import Communicator, Done, \
Drivable, Parameter, Property, Readable
from secop.poller import Poller
from secop.io import HasIO
from secop.rwhandler import ReadHandler, MultiWriteHandler
try:
import secop_psi.ppmswindows as ppmshw
@ -52,23 +53,6 @@ except ImportError:
import secop_psi.ppmssim as ppmshw
class IOHandler(secop.iohandler.IOHandler):
"""IO handler for PPMS commands
deals with typical format:
- query command: ``<command>?``
- reply: ``<value1>,<value2>, ..``
- change command: ``<command> <value1>,<value2>,...``
"""
CMDARGS = ['no'] # the channel number is needed in channel commands
CMDSEPARATOR = None # no command chaining
def __init__(self, name, querycmd, replyfmt):
changecmd = querycmd.split('?')[0] + ' '
super().__init__(name, querycmd, replyfmt, changecmd)
class Main(Communicator):
"""ppms communicator module"""
@ -180,15 +164,10 @@ class Channel(PpmsBase):
if not self.channel:
self.channel = self.name
def get_settings(self, pname):
return ''
class UserChannel(Channel):
"""user channel"""
# pollinterval = Parameter(visibility=3)
no = Property('channel number',
datatype=IntRange(0, 0), export=False, default=0)
linkenable = Property('name of linked channel for enabling',
@ -204,71 +183,83 @@ class UserChannel(Channel):
class DriverChannel(Channel):
"""driver channel"""
drvout = IOHandler('drvout', 'DRVOUT? %(no)d', '%d,%g,%g')
current = Parameter('driver current', readonly=False, handler=drvout,
current = Parameter('driver current', readonly=False, poll=True, # poll only one parameter
datatype=FloatRange(0., 5000., unit='uA'))
powerlimit = Parameter('power limit', readonly=False, handler=drvout,
powerlimit = Parameter('power limit', readonly=False,
datatype=FloatRange(0., 1000., unit='uW'))
# pollinterval = Parameter(visibility=3)
def analyze_drvout(self, no, current, powerlimit):
param_names = 'current', 'powerlimit'
@ReadHandler(param_names)
def read_params(self, pname=None):
no, self.current, self.powerlimit = literal_eval(
self.communicate('DRVOUT? %d' % self.no))
if self.no != no:
raise HardwareError('DRVOUT command: channel number in reply does not match')
return dict(current=current, powerlimit=powerlimit)
return Done
def change_drvout(self, change):
change.readValues()
return change.current, change.powerlimit
@MultiWriteHandler(param_names)
def write_params(self, values):
"""write parameters
:param values: a dict like object containing the parameters to be written
"""
self.read_params() # make sure parameters are up to date
self.set_params('DRVOUT %(no)d,%(current)g,%(powerlimit)g' % values)
return self.read_params(None) # read back
class BridgeChannel(Channel):
"""bridge channel"""
bridge = IOHandler('bridge', 'BRIDGE? %(no)d', '%d,%g,%g,%d,%d,%g')
# pylint: disable=invalid-name
ReadingMode = Enum('ReadingMode', standard=0, fast=1, highres=2)
enabled = Parameter(handler=bridge)
excitation = Parameter('excitation current', readonly=False, handler=bridge,
excitation = Parameter('excitation current', readonly=False, poll=True, # poll only one parameter
datatype=FloatRange(0.01, 5000., unit='uA'))
powerlimit = Parameter('power limit', readonly=False, handler=bridge,
powerlimit = Parameter('power limit', readonly=False,
datatype=FloatRange(0.001, 1000., unit='uW'))
dcflag = Parameter('True when excitation is DC (else AC)', readonly=False, handler=bridge,
dcflag = Parameter('True when excitation is DC (else AC)', readonly=False,
datatype=BoolType())
readingmode = Parameter('reading mode', readonly=False, handler=bridge,
datatype=EnumType(ReadingMode))
voltagelimit = Parameter('voltage limit', readonly=False, handler=bridge,
readingmode = Parameter('reading mode', readonly=False,
datatype=EnumType(standard=0, fast=1, highres=2))
voltagelimit = Parameter('voltage limit', readonly=False,
datatype=FloatRange(0.0001, 100., unit='mV'))
# pollinterval = Parameter(visibility=3)
def analyze_bridge(self, no, excitation, powerlimit, dcflag, readingmode, voltagelimit):
param_names = 'enabled', 'enabled', 'powerlimit', 'dcflag', 'readingmode', 'voltagelimit'
@ReadHandler(param_names)
def read_params(self, pname=None):
no, excitation, powerlimit, self.dcflag, self.readingmode, voltagelimit = literal_eval(
self.communicate('BRIDGE? %d' % self.no))
if self.no != no:
raise HardwareError('DRVOUT command: channel number in reply does not match')
return dict(
enabled=excitation != 0 and powerlimit != 0 and voltagelimit != 0,
excitation=excitation or self.excitation,
powerlimit=powerlimit or self.powerlimit,
dcflag=dcflag,
readingmode=readingmode,
voltagelimit=voltagelimit or self.voltagelimit,
)
self.enabled = excitation != 0 and powerlimit != 0 and voltagelimit != 0
if excitation:
self.excitation = excitation
if powerlimit:
self.powerlimit = powerlimit
if voltagelimit:
self.voltagelimit = voltagelimit
return Done
def change_bridge(self, change):
change.readValues()
if change.enabled:
return self.no, change.excitation, change.powerlimit, change.dcflag, change.readingmode, change.voltagelimit
return self.no, 0, 0, change.dcflag, change.readingmode, 0
@MultiWriteHandler(param_names)
def write_params(self, values):
"""write parameters
:param values: a dict like object containing the parameters to be written
"""
self.read_params() # make sure parameters are up to date
if not values['enabled']:
values['excitation'] = 0
values['powerlimit'] = 0
values['voltagelimit'] = 0
assert self.communicate('BRIDGE %(no)d,%(enabled)g,%(powerlimit)g,%(dcflag)d,'
'%(readingmode)d,%(voltagelimit)g' % values) == 'OK'
return self.read_params() # read back
class Level(PpmsBase):
"""helium level"""
level = IOHandler('level', 'LEVEL?', '%g,%d')
value = Parameter(datatype=FloatRange(unit='%'), handler=level)
status = Parameter(handler=level)
# pollinterval = Parameter(visibility=3)
value = Parameter(datatype=FloatRange(unit='%'), poll=True)
channel = 'level'
@ -278,10 +269,9 @@ class Level(PpmsBase):
# when called from Main.read_data, value is always None
# value and status is polled via settings
def analyze_level(self, level, status):
def read_value(self):
# 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, ''))
return literal_eval(self.communicate('LEVEL?'))[0]
class Chamber(PpmsBase, Drivable):
@ -290,115 +280,74 @@ class Chamber(PpmsBase, Drivable):
value is an Enum, which is redundant with the status text
"""
chamber = IOHandler('chamber', 'CHAMBER?', '%d')
Status = Drivable.Status
# pylint: disable=invalid-name
Operation = Enum(
'Operation',
seal_immediately=0,
purge_and_seal=1,
vent_and_seal=2,
pump_continuously=3,
vent_continuously=4,
hi_vacuum=5,
noop=10,
)
StatusCode = Enum(
'StatusCode',
unknown=0,
purged_and_sealed=1,
vented_and_sealed=2,
sealed_unknown=3,
purge_and_seal=4,
vent_and_seal=5,
pumping_down=6,
at_hi_vacuum=7,
pumping_continuously=8,
venting_continuously=9,
general_failure=15,
)
value = Parameter(description='chamber state', # handler=chamber,
datatype=EnumType(StatusCode))
target = Parameter(description='chamber command', handler=chamber,
datatype=EnumType(Operation))
# pollinterval = Parameter(visibility=3)
STATUS_MAP = {
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'),
StatusCode.purge_and_seal: (Status.BUSY, 'purge and seal'),
StatusCode.vent_and_seal: (Status.BUSY, 'vent and seal'),
StatusCode.pumping_down: (Status.BUSY, 'pumping down'),
StatusCode.at_hi_vacuum: (Status.IDLE, 'at hi vacuum'),
StatusCode.pumping_continuously: (Status.IDLE, 'pumping continuously'),
StatusCode.venting_continuously: (Status.IDLE, 'venting continuously'),
StatusCode.general_failure: (Status.ERROR, 'general failure'),
}
code_table = [
# valuecode, status, statusname, opcode, targetname
(0, Status.IDLE, 'unknown', 10, 'noop'),
(1, Status.IDLE, 'purged_and_sealed', 1, 'purge_and_seal'),
(2, Status.IDLE, 'vented_and_sealed', 2, 'vent_and_seal'),
(3, Status.WARN, 'sealed_unknown', 0, 'seal_immediately'),
(4, Status.BUSY, 'purge_and_seal', None, None),
(5, Status.BUSY, 'vent_and_seal', None, None),
(6, Status.BUSY, 'pumping_down', None, None),
(8, Status.IDLE, 'pumping_continuously', 3, 'pump_continuously'),
(9, Status.IDLE, 'venting_continuously', 4, 'vent_continuously'),
(15, Status.ERROR, 'general_failure', None, None),
]
value_codes = {k: v for v, _, k, _, _ in code_table}
target_codes = {k: v for v, _, _, _, k in code_table if k}
name2opcode = {k: v for _, _, _, v, k in code_table if k}
opcode2name = {v: k for _, _, _, v, k in code_table if k}
status_map = {v: (c, k.replace('_', ' ')) for v, c, k, _, _ in code_table}
value = Parameter(description='chamber state', datatype=EnumType(**value_codes), default=0, poll=True)
target = Parameter(description='chamber command', datatype=EnumType(**target_codes), default='noop', poll=True)
channel = 'chamber'
def update_value_status(self, value, packed_status):
status_code = (packed_status >> 8) & 0xf
if status_code in self.STATUS_MAP:
if status_code in self.status_map:
self.value = status_code
self.status = self.STATUS_MAP[status_code]
self.status = self.status_map[status_code]
else:
self.value = self.StatusCode.unknown
self.value = self.value_map['unknown']
self.status = (self.Status.ERROR, 'unknown status code %d' % status_code)
def analyze_chamber(self, target):
return dict(target=target)
def read_target(self):
opcode = int(self.communicate('CHAMBER?'))
return self.opcode2name[opcode]
def change_chamber(self, change):
# write settings, combining <pname>=<value> and current attributes
# and request updated settings
if change.target == self.Operation.noop:
return None
return (change.target,)
def write_target(self, value):
if value == self.target.noop:
return self.target.noop
opcode = self.name2opcode[self.target.enum(value).name]
assert self.communicate('CHAMBER %d' % opcode) == 'OK'
return self.read_target()
class Temp(PpmsBase, Drivable):
"""temperature"""
temp = IOHandler('temp', 'TEMP?', '%g,%g,%d')
Status = Enum(
Drivable.Status,
RAMPING=370,
STABILIZING=380,
)
# pylint: disable=invalid-name
ApproachMode = Enum('ApproachMode', fast_settle=0, no_overshoot=1)
value = Parameter(datatype=FloatRange(unit='K'))
status = Parameter(datatype=StatusType(Status))
target = Parameter(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)
datatype=FloatRange(1.7, 402.0, unit='K'), poll=True) # poll only one parameter
ramp = Parameter('ramping speed', readonly=False, default=0,
datatype=FloatRange(0, 20, unit='K/min'))
workingramp = Parameter('intermediate ramp value',
datatype=FloatRange(0, 20, unit='K/min'), handler=temp)
approachmode = Parameter('how to approach target!', readonly=False, handler=temp,
datatype=EnumType(ApproachMode))
# pollinterval = Parameter(visibility=3)
datatype=FloatRange(0, 20, unit='K/min'), default=0)
approachmode = Parameter('how to approach target!', readonly=False,
datatype=EnumType(fast_settle=0, no_overshoot=1), default=0)
timeout = Parameter('drive timeout, in addition to ramp time', readonly=False,
datatype=FloatRange(0, unit='sec'), default=3600)
# pylint: disable=invalid-name
TempStatus = Enum(
'TempStatus',
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,
)
general_stop = Property('respect general stop', datatype=BoolType(),
default=True, value=False)
STATUS_MAP = {
1: (Status.IDLE, 'stable at target'),
2: (Status.RAMPING, 'ramping'),
@ -410,8 +359,6 @@ class Temp(PpmsBase, Drivable):
14: (Status.ERROR, 'can not complete'),
15: (Status.ERROR, 'general failure'),
}
general_stop = Property('respect general stop', datatype=BoolType(),
default=True, value=False)
channel = 'temp'
_stopped = False
@ -422,6 +369,43 @@ class Temp(PpmsBase, Drivable):
_wait_at10 = False
_ramp_at_limit = False
param_names = 'setpoint', 'workingramp', 'approachmode'
@ReadHandler(param_names)
def read_params(self, pname):
settings = literal_eval(self.communicate('TEMP?'))
if settings == self._last_settings:
# update parameters only on change, as 'ramp' and 'approachmode' are
# not always sent to the hardware
return Done
self.setpoint, self.workingramp, self.approachmode = self._last_settings = settings
if self.setpoint != 10 or not self._wait_at10:
self.log.debug('read back target %g %r' % (self.setpoint, self._wait_at10))
self.target = self.setpoint
if self.workingramp != 2 or not self._ramp_at_limit:
self.log.debug('read back ramp %g %r' % (self.workingramp, self._ramp_at_limit))
self.ramp = self.workingramp
return Done
def _write_params(self, setpoint, ramp, 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))
assert self.communicate('TEMP %g,%g,%d' % (setpoint, ramp, approachmode)) == 'OK'
self.read_params('')
def update_value_status(self, value, packed_status):
if value is None:
self.status = (self.Status.ERROR, 'invalid value')
@ -439,7 +423,7 @@ class Temp(PpmsBase, Drivable):
if now > self._cool_deadline:
self._wait_at10 = False
self._last_change = now
self.temp.write(self, 'setpoint', self.target)
self._write_params(self.target, self.ramp, self.approachmode)
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:
@ -468,41 +452,6 @@ class Temp(PpmsBase, Drivable):
self._expected_target_time = 0
self.status = status
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:
self.log.debug('read back target %g %r' % (setpoint, self._wait_at10))
self.target = setpoint
if workingramp != 2 or not self._ramp_at_limit:
self.log.debug('read back ramp %g %r' % (workingramp, self._ramp_at_limit))
self.ramp = workingramp
result = dict(setpoint=setpoint, workingramp=workingramp, approachmode=approachmode)
self.log.debug('analyze_temp %r %r' % (result, (self.target, self.ramp)))
return result
def change_temp(self, change):
ramp = change.ramp
setpoint = change.setpoint
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):
self._stopped = False
if abs(self.target - self.value) <= 2e-5 * target and target == self.target:
@ -510,23 +459,23 @@ class Temp(PpmsBase, Drivable):
self._status_before_change = self.status
self.status = (self.Status.BUSY, 'changed target')
self._last_change = time.time()
self.temp.write(self, 'setpoint', target)
self._write_params(target, self.ramp, self.approachmode)
self.log.debug('write_target %s' % repr((self.setpoint, target, self._wait_at10)))
return target
def write_approachmode(self, value):
if self.isDriving():
self.temp.write(self, 'approachmode', value)
self._write_params(self.setpoint, self.ramp, value)
return Done
self.approachmode = value
return None # do not execute TEMP command, as this would trigger an unnecessary T change
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
def write_ramp(self, value):
if self.isDriving():
self.temp.write(self, 'ramp', value)
self._write_params(self.setpoint, value, self.approachmode)
return Done
# self.ramp = value
return None # do not execute TEMP command, as this would trigger an unnecessary T change
self.ramp = value
return Done # do not execute TEMP command, as this would trigger an unnecessary T change
def calc_expected(self, target, ramp):
self._expected_target_time = time.time() + abs(target - self.value) * 60.0 / max(0.1, ramp)
@ -547,35 +496,30 @@ class Temp(PpmsBase, Drivable):
class Field(PpmsBase, Drivable):
"""magnetic field"""
field = IOHandler('field', 'FIELD?', '%g,%g,%d,%d')
Status = Enum(
Drivable.Status,
PREPARED=150,
PREPARING=340,
RAMPING=370,
STABILIZING=380,
FINALIZING=390,
)
# pylint: disable=invalid-name
PersistentMode = Enum('PersistentMode', persistent=0, driven=1)
ApproachMode = Enum('ApproachMode', linear=0, no_overshoot=1, oscillate=2)
value = Parameter(datatype=FloatRange(unit='T'))
status = Parameter(datatype=StatusType(Status))
target = Parameter(datatype=FloatRange(-15, 15, unit='T'), handler=field)
ramp = Parameter('ramping speed', readonly=False, handler=field,
datatype=FloatRange(0.064, 1.19, unit='T/min'))
approachmode = Parameter('how to approach target', readonly=False, handler=field,
datatype=EnumType(ApproachMode))
persistentmode = Parameter('what to do after changing field', readonly=False, handler=field,
datatype=EnumType(PersistentMode))
# pollinterval = Parameter(visibility=3)
target = Parameter(datatype=FloatRange(-15, 15, unit='T'), poll=True) # poll only one parameter
ramp = Parameter('ramping speed', readonly=False,
datatype=FloatRange(0.064, 1.19, unit='T/min'), default=0.19)
approachmode = Parameter('how to approach target', readonly=False,
datatype=EnumType(linear=0, no_overshoot=1, oscillate=2), default=0)
persistentmode = Parameter('what to do after changing field', readonly=False,
datatype=EnumType(persistent=0, driven=1), default=0)
STATUS_MAP = {
1: (Status.IDLE, 'persistent mode'),
2: (Status.PREPARING, 'switch warming'),
3: (Status.FINALIZING, 'switch cooling'),
4: (Status.IDLE, 'driven stable'),
5: (Status.FINALIZING, 'driven final'),
5: (Status.STABILIZING, 'driven final'),
6: (Status.RAMPING, 'charging'),
7: (Status.RAMPING, 'discharging'),
8: (Status.ERROR, 'current error'),
@ -588,6 +532,26 @@ class Field(PpmsBase, Drivable):
_last_target = None # last reached target
_last_change = 0 # means no target change is pending
param_names = 'target', 'ramp', 'approachmode', 'persistentmode'
@ReadHandler(param_names)
def read_params(self, pname):
settings = literal_eval(self.communicate('FIELD?'))
# print('last_settings tt %s' % repr(self._last_settings))
if settings == self._last_settings:
# we update parameters only on change, as 'ramp' and 'approachmode' are
# not always sent to the hardware
return Done
target, ramp, self.approachmode, self.persistentmode = self._last_settings = settings
self.target = round(target * 1e-4, 7)
self.ramp = ramp * 6e-3
return Done
def _write_params(self, target, ramp, approachmode, persistentmode):
assert self.communicate('FIELD %g,%g,%d,%d' % (
target * 1e+4, ramp / 6e-3, approachmode, persistentmode)) == 'OK'
self.read_params('')
def update_value_status(self, value, packed_status):
if value is None:
self.status = (self.Status.ERROR, 'invalid value')
@ -622,19 +586,6 @@ class Field(PpmsBase, Drivable):
status = (status[0], 'stopping (%s)' % status[1])
self.status = status
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:
# we update parameters only on change, as 'ramp' and 'approachmode' are
# not always sent to the hardware
return {}
self._last_settings = target, ramp, approachmode, persistentmode
return dict(target=round(target * 1e-4, 7), ramp=ramp * 6e-3, approachmode=approachmode,
persistentmode=persistentmode)
def change_field(self, change):
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:
self.target = target
@ -643,7 +594,7 @@ class Field(PpmsBase, Drivable):
self._stopped = False
self._last_change = time.time()
self.status = (self.Status.BUSY, 'changed target')
self.field.write(self, 'target', target)
self._write_params(target, self.ramp, self.approachmode, self.persistentmode)
return Done
def write_persistentmode(self, mode):
@ -654,19 +605,19 @@ class Field(PpmsBase, Drivable):
self._status_before_change = self.status
self._stopped = False
self.status = (self.Status.BUSY, 'changed persistent mode')
self.field.write(self, 'persistentmode', mode)
self._write_params(self.target, self.ramp, self.approachmode, mode)
return Done
def write_ramp(self, value):
self.ramp = value
if self.isDriving():
self.field.write(self, 'ramp', value)
self._write_params(self.target, value, self.approachmode, self.persistentmode)
return Done
return None # do not execute FIELD command, as this would trigger a ramp up of leads current
def write_approachmode(self, value):
if self.isDriving():
self.field.write(self, 'approachmode', value)
self._write_params(self.target, self.ramp, value, self.persistentmode)
return Done
return None # do not execute FIELD command, as this would trigger a ramp up of leads current
@ -684,17 +635,14 @@ class Field(PpmsBase, Drivable):
class Position(PpmsBase, Drivable):
"""rotator position"""
move = IOHandler('move', 'MOVE?', '%g,%g,%g')
Status = Drivable.Status
value = Parameter(datatype=FloatRange(unit='deg'))
target = Parameter(datatype=FloatRange(-720., 720., unit='deg'), handler=move)
target = Parameter(datatype=FloatRange(-720., 720., unit='deg'), poll=True) # poll only one parameter
enabled = Parameter('is this channel used?', readonly=False, poll=False,
datatype=BoolType(), default=True)
speed = Parameter('motor speed', readonly=False, handler=move,
speed = Parameter('motor speed', readonly=False, default=12,
datatype=FloatRange(0.8, 12, unit='deg/sec'))
# pollinterval = Parameter(visibility=3)
STATUS_MAP = {
1: (Status.IDLE, 'at target'),
5: (Status.BUSY, 'moving'),
@ -709,6 +657,24 @@ class Position(PpmsBase, Drivable):
_last_change = 0
_within_target = 0 # time since we are within target
param_names = 'target', 'speed'
@ReadHandler(param_names)
def read_params(self, pname):
settings = literal_eval(self.communicate('MOVE?'))
if settings == self._last_settings:
# we update parameters only on change, as 'speed' is
# not always sent to the hardware
return Done
self.target, _, speed = self._last_settings = settings
self.speed = (15 - speed) * 0.8
return Done
def _write_params(self, target, speed):
speed = int(round(min(14, max(0, 15 - speed / 0.8)), 0))
assert self.communicate('MOVE %g,%d,%d' % (target, 0, speed)) == 'OK'
return self.read_params('')
def update_value_status(self, value, packed_status):
if not self.enabled:
self.status = (self.Status.DISABLED, 'disabled')
@ -746,29 +712,17 @@ class Position(PpmsBase, Drivable):
status = (status[0], 'stopping (%s)' % status[1])
self.status = status
def analyze_move(self, target, mode, speed):
if (target, speed) == self._last_settings:
# we update parameters only on change, as 'speed' is
# not always sent to the hardware
return {}
self._last_settings = target, speed
return dict(target=target, speed=(15 - speed) * 0.8)
def change_move(self, change):
speed = int(round(min(14, max(0, 15 - change.speed / 0.8)), 0))
return change.target, 0, speed
def write_target(self, target):
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)
self._write_params(target, self.speed)
return Done
def write_speed(self, value):
if self.isDriving():
self.move.write(self, 'speed', value)
self._write_params(self.target, value)
return Done
self.speed = value
return None # do not execute MOVE command, as this would trigger an unnecessary move