ppms driver now uses command handlers
this is a simplicifcation for the ppms driver - the derivation of a changecmd from a querycmd is moved to CmdHandler.__init__ - the special treatment of handlers when writing configured parameters has moved to CmdHandler.write - introduced Drivable.isDriving, changed Module.isBusy Change-Id: I8862ecda9c8cc998bb018bd960f31c9488146707 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22033 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
cb4874331b
commit
fcad78a682
@ -136,22 +136,26 @@ class CmdParser:
|
|||||||
return [c(v) for c, v in zip(self.casts, match.groups())]
|
return [c(v) for c, v in zip(self.casts, match.groups())]
|
||||||
|
|
||||||
|
|
||||||
class ChangeWrapper:
|
class Change:
|
||||||
"""Wrapper around a module
|
"""contains new values for the call to change_<group>
|
||||||
|
|
||||||
A ChangeWrapper instance is used as the 'new' argument for the change_<group> message.
|
A Change instance is used as the 'new' argument for the change_<group> method.
|
||||||
new.<parameter> is either the new, changed value or the old value from the module.
|
new.<parameter> is either the new, changed value or the old value from the module.
|
||||||
In addition '<parameter>' indicates, whether <parameter> is to be changed.
|
In addition '<parameter>' in new indicates, whether <parameter> is to be changed.
|
||||||
setting new.<parameter> does not yet set the value on the module.
|
new.<parameter> can not be changed
|
||||||
"""
|
"""
|
||||||
def __init__(self, module, valuedict):
|
def __init__(self, module, parameters, valuedict):
|
||||||
self._module = module
|
self.__dict__.update(valuedict, _module=module, _parameters=parameters)
|
||||||
for pname, value in valuedict.items():
|
|
||||||
setattr(self, pname, value)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, pname):
|
||||||
"""get current values from _module for unchanged parameters"""
|
"""get current values from _module for unchanged parameters"""
|
||||||
return getattr(self._module, key)
|
if not pname in self._parameters:
|
||||||
|
raise AttributeError("parameter '%s' is not within the handlers group"
|
||||||
|
% pname)
|
||||||
|
return getattr(self._module, pname)
|
||||||
|
|
||||||
|
def __setattr__(self, pname, value):
|
||||||
|
raise AttributeError("can't set attribute ")
|
||||||
|
|
||||||
def __contains__(self, pname):
|
def __contains__(self, pname):
|
||||||
"""check whether a specific parameter is to be changed"""
|
"""check whether a specific parameter is to be changed"""
|
||||||
@ -237,35 +241,49 @@ class CmdHandlerBase:
|
|||||||
raise
|
raise
|
||||||
return Done # parameters should be updated already
|
return Done # parameters should be updated already
|
||||||
|
|
||||||
def get_write_func(self, pname):
|
def get_write_func(self, pname, wfunc):
|
||||||
"""returns the write function passed to the metaclass
|
"""returns the write function passed to the metaclass
|
||||||
|
|
||||||
return None if not used.
|
may be overriden to return None, if not used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wfunc(module, value, cmd=self, pname=pname):
|
if wfunc:
|
||||||
cmd.write(module, {pname: value})
|
def new_wfunc(module, value, cmd=self, pname=pname, wfunc=wfunc):
|
||||||
return Done
|
value = wfunc(module, value)
|
||||||
|
if value is None or value is Done:
|
||||||
|
return value
|
||||||
|
cmd.write(module, {pname: value})
|
||||||
|
return Done
|
||||||
|
else:
|
||||||
|
def new_wfunc(module, value, cmd=self, pname=pname):
|
||||||
|
cmd.write(module, {pname: value})
|
||||||
|
return Done
|
||||||
|
|
||||||
return wfunc
|
return new_wfunc
|
||||||
|
|
||||||
def write(self, module, valuedict, force_read=False):
|
def write(self, module, valuedict, force_read=False):
|
||||||
"""write values to the module
|
"""write values to the module
|
||||||
|
|
||||||
When called from write_<param>, valuedict contains only one item:
|
When called from write_<param>, valuedict contains only one item,
|
||||||
the parameter to be changed.
|
the single parameter to be changed.
|
||||||
When called from initialization, valuedict may have more items.
|
If called directly, valuedict may have more items.
|
||||||
"""
|
"""
|
||||||
analyze = getattr(module, 'analyze_' + self.group)
|
analyze = getattr(module, 'analyze_' + self.group)
|
||||||
|
if module.writeDict: # collect other parameters to be written
|
||||||
|
valuedict = dict(valuedict)
|
||||||
|
for p in self.parameters:
|
||||||
|
if p in self.writeDict:
|
||||||
|
valuedict[p] = self.writeDict.pop(p)
|
||||||
|
elif p not in valuedict:
|
||||||
|
force_read = True
|
||||||
if self.READ_BEFORE_WRITE or force_read:
|
if self.READ_BEFORE_WRITE or force_read:
|
||||||
# do a read of the current hw values
|
# do a read of the current hw values
|
||||||
values = self.send_command(module)
|
values = self.send_command(module)
|
||||||
# convert them to parameters
|
# convert them to parameters
|
||||||
analyze(*values)
|
analyze(*values)
|
||||||
if not self.READ_BEFORE_WRITE:
|
if not self.READ_BEFORE_WRITE:
|
||||||
values = ()
|
values = () # values are not expected for change_<group>
|
||||||
# create wrapper object 'new' with changed parameter 'pname'
|
new = Change(module, self.parameters, valuedict)
|
||||||
new = ChangeWrapper(module, valuedict)
|
|
||||||
# call change_* for calculation new hw values
|
# call change_* for calculation new hw values
|
||||||
values = getattr(module, 'change_' + self.group)(new, *values)
|
values = getattr(module, 'change_' + self.group)(new, *values)
|
||||||
if values is None: # this indicates that nothing has to be written
|
if values is None: # this indicates that nothing has to be written
|
||||||
@ -273,6 +291,13 @@ class CmdHandlerBase:
|
|||||||
# send the change command and a query command
|
# send the change command and a query command
|
||||||
analyze(*self.send_change(module, *values))
|
analyze(*self.send_change(module, *values))
|
||||||
|
|
||||||
|
def change(self, module, *values):
|
||||||
|
"""write and read back values
|
||||||
|
|
||||||
|
might be called from a write method, if change_<group> is not implemented
|
||||||
|
"""
|
||||||
|
getattr(module, 'analyze_' + self.group)(*self.send_change(module, *values))
|
||||||
|
|
||||||
|
|
||||||
class CmdHandler(CmdHandlerBase):
|
class CmdHandler(CmdHandlerBase):
|
||||||
"""more evolved command handler
|
"""more evolved command handler
|
||||||
@ -290,16 +315,19 @@ class CmdHandler(CmdHandlerBase):
|
|||||||
# the given separator
|
# the given separator
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, group, querycmd, replyfmt):
|
def __init__(self, group, querycmd, replyfmt, changecmd=None):
|
||||||
"""initialize the command handler
|
"""initialize the command handler
|
||||||
|
|
||||||
group: the handler group (used for analyze_<group> and change_<group>)
|
group: the handler group (used for analyze_<group> and change_<group>)
|
||||||
querycmd: the command for a query, may contain named formats for cmdargs
|
querycmd: the command for a query, may contain named formats for cmdargs
|
||||||
replyfmt: the format for reading the reply with some scanf like behaviour
|
replyfmt: the format for reading the reply with some scanf like behaviour
|
||||||
|
changecmd: the first part of the change command (without values), may be
|
||||||
|
omitted if no write happens
|
||||||
"""
|
"""
|
||||||
super().__init__(group)
|
super().__init__(group)
|
||||||
self.querycmd = querycmd
|
self.querycmd = querycmd
|
||||||
self.replyfmt = CmdParser(replyfmt)
|
self.replyfmt = CmdParser(replyfmt)
|
||||||
|
self.changecmd = changecmd
|
||||||
|
|
||||||
def parse_reply(self, reply):
|
def parse_reply(self, reply):
|
||||||
"""return values from a raw reply"""
|
"""return values from a raw reply"""
|
||||||
@ -310,11 +338,8 @@ class CmdHandler(CmdHandlerBase):
|
|||||||
return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
|
return self.querycmd % {k: getattr(module, k, None) for k in self.CMDARGS}
|
||||||
|
|
||||||
def make_change(self, module, *values):
|
def make_change(self, module, *values):
|
||||||
"""make a change command from a query command"""
|
"""make a change command"""
|
||||||
changecmd = self.querycmd.replace('?', ' ')
|
changecmd = self.changecmd % {k: getattr(module, k, None) for k in self.CMDARGS}
|
||||||
if not self.querycmd.endswith('?'):
|
|
||||||
changecmd += ','
|
|
||||||
changecmd %= {k: getattr(module, k, None) for k in self.CMDARGS}
|
|
||||||
return changecmd + self.replyfmt.format(*values)
|
return changecmd + self.replyfmt.format(*values)
|
||||||
|
|
||||||
def send_change(self, module, *values):
|
def send_change(self, module, *values):
|
||||||
|
@ -112,12 +112,12 @@ class ModuleMeta(PropertyMeta):
|
|||||||
# skip commands for now
|
# skip commands for now
|
||||||
continue
|
continue
|
||||||
rfunc = attrs.get('read_' + pname, None)
|
rfunc = attrs.get('read_' + pname, None)
|
||||||
handler = pobj.handler.get_read_func(newtype, pname) if pobj.handler else None
|
rfunc_handler = pobj.handler.get_read_func(newtype, pname) if pobj.handler else None
|
||||||
if handler:
|
if rfunc_handler:
|
||||||
if rfunc:
|
if rfunc:
|
||||||
raise ProgrammingError("parameter '%s' can not have a handler "
|
raise ProgrammingError("parameter '%s' can not have a handler "
|
||||||
"and read_%s" % (pname, pname))
|
"and read_%s" % (pname, pname))
|
||||||
rfunc = handler
|
rfunc = rfunc_handler
|
||||||
else:
|
else:
|
||||||
for base in bases:
|
for base in bases:
|
||||||
if rfunc is not None:
|
if rfunc is not None:
|
||||||
@ -151,17 +151,13 @@ class ModuleMeta(PropertyMeta):
|
|||||||
|
|
||||||
if not pobj.readonly:
|
if not pobj.readonly:
|
||||||
wfunc = attrs.get('write_' + pname, None)
|
wfunc = attrs.get('write_' + pname, None)
|
||||||
handler = pobj.handler.get_write_func(pname) if pobj.handler else None
|
# if a handler and write_<param> is present, wfunc will be called
|
||||||
if handler:
|
# by the handler first
|
||||||
if wfunc:
|
wfunc = pobj.handler.get_write_func(pname, wfunc) if pobj.handler else wfunc
|
||||||
raise ProgrammingError("parameter '%s' can not have a handler "
|
for base in bases:
|
||||||
"and write_%s" % (pname, pname))
|
if wfunc is not None:
|
||||||
wfunc = handler
|
break
|
||||||
else:
|
wfunc = getattr(base, 'write_' + pname, None)
|
||||||
for base in bases:
|
|
||||||
if wfunc is not None:
|
|
||||||
break
|
|
||||||
wfunc = getattr(base, 'write_' + pname, None)
|
|
||||||
|
|
||||||
if wfunc is None or getattr(wfunc, '__wrapped__', False) is False:
|
if wfunc is None or getattr(wfunc, '__wrapped__', False) is False:
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
|||||||
pobj = self.parameters[pname]
|
pobj = self.parameters[pname]
|
||||||
self.DISPATCHER.announce_update_error(self, pname, pobj, exception)
|
self.DISPATCHER.announce_update_error(self, pname, pobj, exception)
|
||||||
|
|
||||||
def isBusy(self):
|
def isBusy(self, status=None):
|
||||||
'''helper function for treating substates of BUSY correctly'''
|
'''helper function for treating substates of BUSY correctly'''
|
||||||
# defined even for non drivable (used for dynamic polling)
|
# defined even for non drivable (used for dynamic polling)
|
||||||
return False
|
return False
|
||||||
@ -295,21 +295,11 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
|||||||
with proper error handling
|
with proper error handling
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
pobj = self.parameters[pname]
|
if pname in self.writeDict:
|
||||||
if pobj.handler:
|
self.log.debug('write parameter %s', pname)
|
||||||
pnames = pobj.handler.parameters
|
getattr(self, 'write_'+ pname)(self.writeDict.pop(pname))
|
||||||
valuedict = {n: self.writeDict.pop(n) for n in pnames if n in self.writeDict}
|
|
||||||
if valuedict:
|
|
||||||
self.log.debug('write parameters %r', valuedict)
|
|
||||||
pobj.handler.write(self, valuedict, force_read=True)
|
|
||||||
return
|
|
||||||
pobj.handler.read(self)
|
|
||||||
else:
|
else:
|
||||||
if pname in self.writeDict:
|
getattr(self, 'read_'+ pname)()
|
||||||
self.log.debug('write parameter %s', pname)
|
|
||||||
getattr(self, 'write_'+ pname)(self.writeDict.pop(pname))
|
|
||||||
else:
|
|
||||||
getattr(self, 'read_'+ pname)()
|
|
||||||
except SilentError as e:
|
except SilentError as e:
|
||||||
pass
|
pass
|
||||||
except SECoPError as e:
|
except SECoPError as e:
|
||||||
@ -428,9 +418,13 @@ class Drivable(Writable):
|
|||||||
'status' : Override(datatype=TupleOf(EnumType(Status), StringType())),
|
'status' : Override(datatype=TupleOf(EnumType(Status), StringType())),
|
||||||
}
|
}
|
||||||
|
|
||||||
def isBusy(self):
|
def isBusy(self, status=None):
|
||||||
'''helper function for treating substates of BUSY correctly'''
|
'''helper function for treating substates of BUSY correctly'''
|
||||||
return 300 <= self.status[0] < 400
|
return 300 <= (status or self.status)[0] < 400
|
||||||
|
|
||||||
|
def isDriving(self, status=None):
|
||||||
|
'''helper function (finalize is busy, not driving)'''
|
||||||
|
return 300 <= (status or self.status)[0] < 380
|
||||||
|
|
||||||
# improved polling: may poll faster if module is BUSY
|
# improved polling: may poll faster if module is BUSY
|
||||||
def pollParams(self, nr=0):
|
def pollParams(self, nr=0):
|
||||||
|
@ -36,6 +36,13 @@ class CmdHandler(secop.commandhandler.CmdHandler):
|
|||||||
CMDARGS = ['channel']
|
CMDARGS = ['channel']
|
||||||
CMDSEPARATOR = ';'
|
CMDSEPARATOR = ';'
|
||||||
|
|
||||||
|
def __init__(self, name, querycmd, replyfmt):
|
||||||
|
changecmd = querycmd.replace('?', ' ')
|
||||||
|
if not querycmd.endswith('?'):
|
||||||
|
changecmd += ','
|
||||||
|
super().__init__(name, querycmd, replyfmt, changecmd)
|
||||||
|
|
||||||
|
|
||||||
rdgrng = CmdHandler('rdgrng', 'RDGRNG?%(channel)d', '%d,%d,%d,%d,%d')
|
rdgrng = CmdHandler('rdgrng', 'RDGRNG?%(channel)d', '%d,%d,%d,%d,%d')
|
||||||
inset = CmdHandler('inset', 'INSET?%(channel)d', '%d,%d,%d,%d,%d')
|
inset = CmdHandler('inset', 'INSET?%(channel)d', '%d,%d,%d,%d,%d')
|
||||||
filterhdl = CmdHandler('filt', 'FILTER?%(channel)d', '%d,%d,%d')
|
filterhdl = CmdHandler('filt', 'FILTER?%(channel)d', '%d,%d,%d')
|
||||||
|
@ -39,7 +39,6 @@ settings and target would do a useless cycle of ramping up leads, heating switch
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import json
|
|
||||||
|
|
||||||
from secop.modules import Module, Readable, Drivable, Parameter, Override,\
|
from secop.modules import Module, Readable, Drivable, Parameter, Override,\
|
||||||
Communicator, Property, Attached
|
Communicator, Property, Attached
|
||||||
@ -48,6 +47,9 @@ from secop.datatypes import EnumType, FloatRange, IntRange, StringType,\
|
|||||||
from secop.lib.enum import Enum
|
from secop.lib.enum import Enum
|
||||||
from secop.errors import HardwareError
|
from secop.errors import HardwareError
|
||||||
from secop.poller import Poller
|
from secop.poller import Poller
|
||||||
|
import secop.commandhandler
|
||||||
|
from secop.stringio import HasIodev
|
||||||
|
from secop.metaclass import Done
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import secop_psi.ppmswindows as ppmshw
|
import secop_psi.ppmswindows as ppmshw
|
||||||
@ -55,9 +57,15 @@ except ImportError:
|
|||||||
import secop_psi.ppmssim as ppmshw
|
import secop_psi.ppmssim as ppmshw
|
||||||
|
|
||||||
|
|
||||||
def isDriving(status):
|
class CmdHandler(secop.commandhandler.CmdHandler):
|
||||||
"""moving towards target"""
|
CMDARGS = ['no']
|
||||||
return 300 <= status[0] < 390
|
CMDSEPARATOR = None # no command chaining
|
||||||
|
READ_BEFORE_WRITE = False
|
||||||
|
|
||||||
|
def __init__(self, name, querycmd, replyfmt):
|
||||||
|
changecmd = querycmd.split('?')[0] + ' '
|
||||||
|
super().__init__(name, querycmd, replyfmt, changecmd)
|
||||||
|
|
||||||
|
|
||||||
class Main(Communicator):
|
class Main(Communicator):
|
||||||
"""general ppms dummy module"""
|
"""general ppms dummy module"""
|
||||||
@ -122,82 +130,41 @@ class Main(Communicator):
|
|||||||
return data # return data as string
|
return data # return data as string
|
||||||
|
|
||||||
|
|
||||||
class PpmsMixin(Module):
|
class PpmsMixin(HasIodev, Module):
|
||||||
properties = {
|
properties = {
|
||||||
'iodev': Attached('_main'),
|
'iodev': Attached(),
|
||||||
}
|
|
||||||
parameters = {
|
|
||||||
'settings':
|
|
||||||
Parameter('internal', export=False, poll=True, readonly=False,
|
|
||||||
default="", datatype=StringType()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pollerClass = Poller
|
pollerClass = Poller
|
||||||
enabled = True # default, if no parameter enable is defined
|
enabled = True # default, if no parameter enable is defined
|
||||||
STATUS_MAP = {} # a mapping converting ppms status codes into SECoP status values
|
# STATUS_MAP = {} # a mapping converting ppms status codes into SECoP status values
|
||||||
_settingnames = [] # names of the parameters in the settings command
|
_last_target_change = 0 # used by several modules
|
||||||
_last_target_change = 0
|
_last_settings = None # used by several modules
|
||||||
slow_pollfactor = 1
|
slow_pollfactor = 1
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
self._main.register(self)
|
self._iodev.register(self)
|
||||||
|
|
||||||
def startModule(self, started_callback):
|
def startModule(self, started_callback):
|
||||||
# no polls except on main module
|
# no polls except on main module
|
||||||
started_callback()
|
started_callback()
|
||||||
|
|
||||||
def send_cmd(self, writecmd, argdict):
|
|
||||||
self._main.do_communicate(writecmd + ' ' +
|
|
||||||
','.join('%.7g' % argdict[key] for key in self._settingnames))
|
|
||||||
|
|
||||||
def get_reply(self, settings, query):
|
|
||||||
"""return a dict with the values get from the reply
|
|
||||||
|
|
||||||
if the reply has not changed, an empty dict is returned
|
|
||||||
"""
|
|
||||||
reply = self._main.do_communicate(query)
|
|
||||||
if getattr(self, settings) == reply:
|
|
||||||
return {}
|
|
||||||
setattr(self, settings, reply)
|
|
||||||
return dict(zip(self._settingnames, json.loads('[%s]' % reply)))
|
|
||||||
|
|
||||||
def apply_reply(self, reply, pname):
|
|
||||||
"""apply reply dict to the parameters
|
|
||||||
|
|
||||||
except for reply[pname], which is returned
|
|
||||||
"""
|
|
||||||
returnvalue = getattr(self, pname)
|
|
||||||
for key, value in reply.items():
|
|
||||||
if key == pname:
|
|
||||||
returnvalue = value
|
|
||||||
else:
|
|
||||||
setattr(self, key, value)
|
|
||||||
return returnvalue
|
|
||||||
|
|
||||||
def make_argdict(self, pname, value):
|
|
||||||
"""make a dict from the parameters self._settingnames
|
|
||||||
|
|
||||||
but result[pname] replaced by value
|
|
||||||
"""
|
|
||||||
return {key: value if key == pname else getattr(self, key) for key in self._settingnames}
|
|
||||||
|
|
||||||
def read_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def read_value(self):
|
def read_value(self):
|
||||||
"""not very useful, as values are updated fast enough
|
"""effective polling is done by the main module"""
|
||||||
|
if not self.enabled:
|
||||||
note: this will update all values, and the value of this module twice
|
return Done
|
||||||
"""
|
if self.parameters['value'].timestamp == 0:
|
||||||
self._main.read_data()
|
# make sure that the value is read at least after init
|
||||||
|
self._iodev.read_data()
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
"""not very useful, as status is updated fast enough
|
"""effective polling is done by the main module"""
|
||||||
|
if not self.enabled:
|
||||||
note: this will update the status of all modules, and this module twice
|
return Done
|
||||||
"""
|
if self.parameters['value'].timestamp == 0:
|
||||||
self._main.read_data()
|
# make sure that the value is read at least after init
|
||||||
|
self._iodev.read_data()
|
||||||
return self.status
|
return self.status
|
||||||
|
|
||||||
def update_value_status(self, value, packed_status):
|
def update_value_status(self, value, packed_status):
|
||||||
@ -214,12 +181,11 @@ class PpmsMixin(Module):
|
|||||||
self.value = value
|
self.value = value
|
||||||
self.status = [self.Status.IDLE, '']
|
self.status = [self.Status.IDLE, '']
|
||||||
|
|
||||||
|
|
||||||
class Channel(PpmsMixin, Readable):
|
class Channel(PpmsMixin, Readable):
|
||||||
parameters = {
|
parameters = {
|
||||||
'value':
|
'value':
|
||||||
Override('main value of channels', poll=False, default=0),
|
Override('main value of channels', poll=True),
|
||||||
'status':
|
|
||||||
Override(poll=False),
|
|
||||||
'enabled':
|
'enabled':
|
||||||
Parameter('is this channel used?', readonly=False, poll=False,
|
Parameter('is this channel used?', readonly=False, poll=False,
|
||||||
datatype=BoolType(), default=False),
|
datatype=BoolType(), default=False),
|
||||||
@ -243,6 +209,7 @@ class Channel(PpmsMixin, Readable):
|
|||||||
def get_settings(self, pname):
|
def get_settings(self, pname):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class UserChannel(Channel):
|
class UserChannel(Channel):
|
||||||
parameters = {
|
parameters = {
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
@ -256,169 +223,86 @@ class UserChannel(Channel):
|
|||||||
|
|
||||||
|
|
||||||
class DriverChannel(Channel):
|
class DriverChannel(Channel):
|
||||||
|
drvout = CmdHandler('drvout', 'DRVOUT? %(no)d', '%d,%g,%g')
|
||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
'current':
|
'current':
|
||||||
Parameter('driver current', readonly=False, poll=False,
|
Parameter('driver current', readonly=False, handler=drvout,
|
||||||
datatype=FloatRange(0., 5000., unit='uA'), default=0),
|
datatype=FloatRange(0., 5000., unit='uA')),
|
||||||
'powerlimit':
|
'powerlimit':
|
||||||
Parameter('power limit', readonly=False, poll=False,
|
Parameter('power limit', readonly=False, handler=drvout,
|
||||||
datatype=FloatRange(0., 1000., unit='uW'), default=0),
|
datatype=FloatRange(0., 1000., unit='uW')),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
|
|
||||||
_settingnames = ['no', 'current', 'powerlimit']
|
def analyze_drvout(self, no, current, powerlimit):
|
||||||
|
if self.no != no:
|
||||||
|
raise HardwareError('DRVOUT command: channel number in reply does not match')
|
||||||
|
self.current = current
|
||||||
|
self.powerlimit = powerlimit
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def change_drvout(self, new):
|
||||||
"""read settings
|
return new.current, new.powerlimit
|
||||||
|
|
||||||
return the value for <pname> and update all other parameters
|
|
||||||
"""
|
|
||||||
reply = self.get_reply('settings', 'DRVOUT? %d' % self.no)
|
|
||||||
if reply:
|
|
||||||
if self.no != reply.pop('no'):
|
|
||||||
raise HardwareError('DRVOUT command: channel number in reply does not match')
|
|
||||||
return self.apply_reply(reply, pname)
|
|
||||||
|
|
||||||
def put_settings(self, value, pname):
|
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
|
||||||
|
|
||||||
and request updated settings
|
|
||||||
"""
|
|
||||||
self.send_cmd('DRVOUT', self.make_argdict(pname, value))
|
|
||||||
return self.get_settings(pname)
|
|
||||||
|
|
||||||
def read_current(self):
|
|
||||||
return self.get_settings('current')
|
|
||||||
|
|
||||||
def read_powerlimit(self):
|
|
||||||
return self.get_settings('powerlimit')
|
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def write_current(self, value):
|
|
||||||
return self.put_settings(value, 'current')
|
|
||||||
|
|
||||||
def write_powerlimit(self, value):
|
|
||||||
return self.put_settings(value, 'powerlimit')
|
|
||||||
|
|
||||||
|
|
||||||
class BridgeChannel(Channel):
|
class BridgeChannel(Channel):
|
||||||
|
bridge = CmdHandler('bridge', 'BRIDGE? %(no)d', '%d,%g,%g,%d,%d,%g')
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
ReadingMode = Enum('ReadingMode', standard=0, fast=1, highres=2)
|
ReadingMode = Enum('ReadingMode', standard=0, fast=1, highres=2)
|
||||||
parameters = {
|
parameters = {
|
||||||
|
'enabled':
|
||||||
|
Override(handler=bridge),
|
||||||
'excitation':
|
'excitation':
|
||||||
Parameter('excitation current', readonly=False, poll=False,
|
Parameter('excitation current', readonly=False, handler=bridge,
|
||||||
datatype=FloatRange(0.01, 5000., unit='uA'), default=0.01),
|
datatype=FloatRange(0.01, 5000., unit='uA')),
|
||||||
'powerlimit':
|
'powerlimit':
|
||||||
Parameter('power limit', readonly=False, poll=False,
|
Parameter('power limit', readonly=False, handler=bridge,
|
||||||
datatype=FloatRange(0.001, 1000., unit='uW'), default=0.001),
|
datatype=FloatRange(0.001, 1000., unit='uW')),
|
||||||
'dcflag':
|
'dcflag':
|
||||||
Parameter('True when excitation is DC (else AC)', readonly=False, poll=False,
|
Parameter('True when excitation is DC (else AC)', readonly=False, handler=bridge,
|
||||||
datatype=BoolType(), default=False),
|
datatype=BoolType()),
|
||||||
'readingmode':
|
'readingmode':
|
||||||
Parameter('reading mode', readonly=False, poll=False,
|
Parameter('reading mode', readonly=False, handler=bridge,
|
||||||
datatype=EnumType(ReadingMode), default=ReadingMode.standard),
|
datatype=EnumType(ReadingMode)),
|
||||||
'voltagelimit':
|
'voltagelimit':
|
||||||
Parameter('voltage limit', readonly=False, poll=False,
|
Parameter('voltage limit', readonly=False, handler=bridge,
|
||||||
datatype=FloatRange(0.0001, 100., unit='mV'), default=0.0001),
|
datatype=FloatRange(0.0001, 100., unit='mV')),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
|
|
||||||
_settingnames = ['no', 'excitation', 'powerlimit', 'dcflag', 'readingmode', 'voltagelimit']
|
_settingnames = ['no', 'excitation', 'powerlimit', 'dcflag', 'readingmode', 'voltagelimit']
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def analyze_bridge(self, no, excitation, powerlimit, dcflag, readingmode, voltagelimit):
|
||||||
"""read settings
|
if self.no != no:
|
||||||
|
raise HardwareError('DRVOUT command: channel number in reply does not match')
|
||||||
|
self.enabled = excitation != 0 and powerlimit != 0 and voltagelimit != 0
|
||||||
|
self.excitation = excitation or self.excitation
|
||||||
|
self.powerlimit = powerlimit or self.powerlimit
|
||||||
|
self.dcflag = dcflag
|
||||||
|
self.readingmode = readingmode
|
||||||
|
self.voltagelimit = voltagelimit or self.voltagelimit
|
||||||
|
|
||||||
return the value for <pname> and update all other parameters
|
def change_bridge(self, new):
|
||||||
"""
|
if new.enabled:
|
||||||
reply = self.get_reply('settings', 'BRIDGE? %d' % self.no)
|
return self.no, new.excitation, new.powerlimit, new.dcflag, new.readingmode, new.voltagelimit
|
||||||
if reply:
|
return self.no, 0, 0, new.dcflag, new.readingmode, 0
|
||||||
if self.no != reply['no']:
|
|
||||||
raise HardwareError('BRIDGE command: channel number in reply does not match')
|
|
||||||
reply['enabled'] = 1
|
|
||||||
if reply['excitation'] == 0:
|
|
||||||
reply['excitation'] = self.excitation
|
|
||||||
reply['enabled'] = 0
|
|
||||||
if reply['powerlimit'] == 0:
|
|
||||||
reply['powerlimit'] = self.powerlimit
|
|
||||||
reply['enabled'] = 0
|
|
||||||
if reply['voltagelimit'] == 0:
|
|
||||||
reply['voltagelimit'] = self.voltagelimit
|
|
||||||
reply['enabled'] = 0
|
|
||||||
del reply['no']
|
|
||||||
returnvalue = self.apply_reply(reply, pname)
|
|
||||||
return returnvalue
|
|
||||||
|
|
||||||
def put_settings(self, value, pname):
|
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
|
||||||
|
|
||||||
and request updated settings
|
|
||||||
"""
|
|
||||||
argdict = self.make_argdict(pname, value)
|
|
||||||
enabled = value if pname == 'enabled' else self.enabled
|
|
||||||
if not enabled:
|
|
||||||
argdict['excitation'] = 0
|
|
||||||
argdict['powerlimit'] = 0
|
|
||||||
argdict['voltagelimit'] = 0
|
|
||||||
self.send_cmd('BRIDGE', argdict)
|
|
||||||
returnvalue = self.get_settings(pname)
|
|
||||||
return returnvalue
|
|
||||||
|
|
||||||
def read_enabled(self):
|
|
||||||
return self.get_settings('enabled')
|
|
||||||
|
|
||||||
def read_excitation(self):
|
|
||||||
return self.get_settings('excitation')
|
|
||||||
|
|
||||||
def read_powerlimit(self):
|
|
||||||
return self.get_settings('powerlimit')
|
|
||||||
|
|
||||||
def read_dcflag(self):
|
|
||||||
return self.get_settings('dcflag')
|
|
||||||
|
|
||||||
def read_readingmode(self):
|
|
||||||
return self.get_settings('readingmode')
|
|
||||||
|
|
||||||
def read_voltagelimit(self):
|
|
||||||
return self.get_settings('voltagelimit')
|
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def write_enabled(self, value):
|
|
||||||
return self.put_settings(value, 'enabled')
|
|
||||||
|
|
||||||
def write_excitation(self, value):
|
|
||||||
return self.put_settings(value, 'excitation')
|
|
||||||
|
|
||||||
def write_powerlimit(self, value):
|
|
||||||
return self.put_settings(value, 'powerlimit')
|
|
||||||
|
|
||||||
def write_dcflag(self, value):
|
|
||||||
return self.put_settings(value, 'dcflag')
|
|
||||||
|
|
||||||
def write_readingmode(self, value):
|
|
||||||
return self.put_settings(value, 'readingmode')
|
|
||||||
|
|
||||||
def write_voltagelimit(self, value):
|
|
||||||
return self.put_settings(value, 'voltagelimit')
|
|
||||||
|
|
||||||
|
|
||||||
class Level(PpmsMixin, Readable):
|
class Level(PpmsMixin, Readable):
|
||||||
"""helium level"""
|
"""helium level"""
|
||||||
|
|
||||||
|
level = CmdHandler('level', 'LEVEL?', '%g,%d')
|
||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
'value': Override(datatype=FloatRange(unit='%'), poll=False, default=0),
|
'value': Override(datatype=FloatRange(unit='%'), handler=level),
|
||||||
'status': Override(poll=False),
|
'status': Override(handler=level),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
|
|
||||||
channel = 'level'
|
channel = 'level'
|
||||||
_settingnames = ['value', 'status']
|
|
||||||
|
|
||||||
def update_value_status(self, value, packed_status):
|
def update_value_status(self, value, packed_status):
|
||||||
"""must be a no-op
|
"""must be a no-op
|
||||||
@ -427,24 +311,12 @@ class Level(PpmsMixin, Readable):
|
|||||||
value and status is polled via settings
|
value and status is polled via settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def analyze_level(self, level, status):
|
||||||
"""read settings
|
if status:
|
||||||
|
self.status = [self.Status.IDLE, '']
|
||||||
return the value for <pname> and update all other parameters
|
else:
|
||||||
"""
|
self.status = [self.Status.ERROR, 'old reading']
|
||||||
reply = self.get_reply('settings', 'LEVEL?')
|
self.value = level
|
||||||
if reply:
|
|
||||||
if reply['status']:
|
|
||||||
reply['status'] = [self.Status.IDLE, '']
|
|
||||||
else:
|
|
||||||
reply['status'] = [self.Status.ERROR, 'old reading']
|
|
||||||
return self.apply_reply(reply, pname)
|
|
||||||
|
|
||||||
def read_value(self):
|
|
||||||
return self.get_settings('value')
|
|
||||||
|
|
||||||
def read_status(self):
|
|
||||||
return self.get_settings('status')
|
|
||||||
|
|
||||||
|
|
||||||
class Chamber(PpmsMixin, Drivable):
|
class Chamber(PpmsMixin, Drivable):
|
||||||
@ -453,6 +325,7 @@ class Chamber(PpmsMixin, Drivable):
|
|||||||
value is an Enum, which is redundant with the status text
|
value is an Enum, which is redundant with the status text
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
chamber = CmdHandler('chamber', 'CHAMBER?', '%d')
|
||||||
Status = Drivable.Status
|
Status = Drivable.Status
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
Operation = Enum(
|
Operation = Enum(
|
||||||
@ -481,13 +354,11 @@ class Chamber(PpmsMixin, Drivable):
|
|||||||
)
|
)
|
||||||
parameters = {
|
parameters = {
|
||||||
'value':
|
'value':
|
||||||
Override(description='chamber state', poll=False,
|
Override(description='chamber state', handler=chamber,
|
||||||
datatype=EnumType(StatusCode), default='unknown'),
|
datatype=EnumType(StatusCode)),
|
||||||
'status':
|
|
||||||
Override(poll=False),
|
|
||||||
'target':
|
'target':
|
||||||
Override(description='chamber command', poll=True,
|
Override(description='chamber command', handler=chamber,
|
||||||
datatype=EnumType(Operation), default=Operation.noop),
|
datatype=EnumType(Operation)),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
@ -513,34 +384,23 @@ class Chamber(PpmsMixin, Drivable):
|
|||||||
self.value = (packed_status >> 8) & 0xf
|
self.value = (packed_status >> 8) & 0xf
|
||||||
self.status = self.STATUS_MAP[self.value]
|
self.status = self.STATUS_MAP[self.value]
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def analyze_chamber(self, target):
|
||||||
"""read settings
|
self.target = target
|
||||||
|
|
||||||
return the value for <pname> and update all other parameters
|
def change_chamber(self, new):
|
||||||
"""
|
|
||||||
reply = self.get_reply('settings', 'CHAMBER?')
|
|
||||||
return self.apply_reply(reply, pname)
|
|
||||||
|
|
||||||
def put_settings(self, value, pname):
|
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
"""write settings, combining <pname>=<value> and current attributes
|
||||||
|
|
||||||
and request updated settings
|
and request updated settings
|
||||||
"""
|
"""
|
||||||
self.send_cmd('CHAMBER', self.make_argdict(pname, value))
|
if new.target == self.Operation.noop:
|
||||||
return self.get_settings(pname)
|
return None
|
||||||
|
return (new.target,)
|
||||||
def read_target(self):
|
|
||||||
return self.get_settings('target')
|
|
||||||
|
|
||||||
def write_target(self, value):
|
|
||||||
if value == self.Operation.noop:
|
|
||||||
return value
|
|
||||||
return self.put_settings(value, 'target')
|
|
||||||
|
|
||||||
|
|
||||||
class Temp(PpmsMixin, Drivable):
|
class Temp(PpmsMixin, Drivable):
|
||||||
"""temperature"""
|
"""temperature"""
|
||||||
|
|
||||||
|
temp = CmdHandler('temp', 'TEMP?', '%g,%g,%d')
|
||||||
Status = Enum(Drivable.Status,
|
Status = Enum(Drivable.Status,
|
||||||
RAMPING = 370,
|
RAMPING = 370,
|
||||||
STABILIZING = 380,
|
STABILIZING = 380,
|
||||||
@ -549,17 +409,17 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
ApproachMode = Enum('ApproachMode', fast_settle=0, no_overshoot=1)
|
ApproachMode = Enum('ApproachMode', fast_settle=0, no_overshoot=1)
|
||||||
parameters = {
|
parameters = {
|
||||||
'value':
|
'value':
|
||||||
Override(datatype=FloatRange(unit='K'), poll=False, default=0),
|
Override(datatype=FloatRange(unit='K'), poll=True),
|
||||||
'status':
|
'status':
|
||||||
Override(poll=False, datatype=StatusType(Status)),
|
Override(datatype=StatusType(Status), poll=True),
|
||||||
'target':
|
'target':
|
||||||
Override(datatype=FloatRange(1.7, 402.0, unit='K'), default=295, poll=False),
|
Override(datatype=FloatRange(1.7, 402.0, unit='K'), handler=temp),
|
||||||
'ramp':
|
'ramp':
|
||||||
Parameter('ramping speed', readonly=False, poll=False,
|
Parameter('ramping speed', readonly=False, handler=temp,
|
||||||
datatype=FloatRange(0, 20, unit='K/min'), default=0.1),
|
datatype=FloatRange(0, 20, unit='K/min')),
|
||||||
'approachmode':
|
'approachmode':
|
||||||
Parameter('how to approach target!', readonly=False, poll=False,
|
Parameter('how to approach target!', readonly=False, handler=temp,
|
||||||
datatype=EnumType(ApproachMode), default=0),
|
datatype=EnumType(ApproachMode)),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
'timeout':
|
'timeout':
|
||||||
@ -621,7 +481,7 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
self.status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
|
self.status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
|
||||||
return
|
return
|
||||||
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 isDriving(status):
|
if self.isDriving(status):
|
||||||
if now > self._last_change + 15 or status != self._status_before_change:
|
if now > self._last_change + 15 or 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)
|
||||||
@ -632,71 +492,50 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
status = [self.Status.WARN, 'temperature status (%r) does not change to BUSY' % status]
|
status = [self.Status.WARN, 'temperature status (%r) does not change to BUSY' % status]
|
||||||
if self._expected_target:
|
if self._expected_target:
|
||||||
# handle timeout
|
# handle timeout
|
||||||
if isDriving(status):
|
if self.isDriving(status):
|
||||||
if now > self._expected_target + self.timeout:
|
if now > self._expected_target + self.timeout:
|
||||||
self.status = [self.Status.WARN, 'timeout while %s' % status[1]]
|
status = [self.Status.WARN, 'timeout while %s' % status[1]]
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self._expected_target = 0
|
self._expected_target = 0
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def analyze_temp(self, target, ramp, approachmode):
|
||||||
"""read settings
|
if (target, ramp, approachmode) != self._last_settings:
|
||||||
|
# we update parameters only on change, as 'approachmode'
|
||||||
|
# is not always sent to the hardware
|
||||||
|
self._last_settings = target, ramp, approachmode
|
||||||
|
self.target = target
|
||||||
|
self.ramp =ramp
|
||||||
|
self.approachmode = approachmode
|
||||||
|
|
||||||
return the value for <pname> and update all other parameters
|
def change_temp(self, new):
|
||||||
"""
|
self.calc_expected(new.target, self.ramp)
|
||||||
return self.apply_reply(self.get_reply('settings', 'TEMP?'), pname)
|
return new.target, new.ramp, new.approachmode
|
||||||
|
|
||||||
def put_settings(self, value, pname):
|
def write_target(self, target):
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
self._stopped = False
|
||||||
|
if abs(self.target - self.value) < 2e-5 and target == self.target:
|
||||||
|
return None
|
||||||
|
self._status_before_change = self.status
|
||||||
|
self.status = [self.Status.BUSY, 'changed_target']
|
||||||
|
self._last_change = time.time()
|
||||||
|
return target
|
||||||
|
|
||||||
and request updated settings
|
def write_approachmode(self, value):
|
||||||
"""
|
if self.isDriving():
|
||||||
self.send_cmd('TEMP', self.make_argdict(pname, value))
|
return value
|
||||||
return self.get_settings(pname)
|
return None # change_temp will not be called, as this would trigger an unnecessary T change
|
||||||
|
|
||||||
def read_target(self):
|
def write_ramp(self, value):
|
||||||
return self.get_settings('target')
|
if self.isDriving():
|
||||||
|
return value
|
||||||
def read_ramp(self):
|
return None # change_temp will not be called, as this would trigger an unnecessary T change
|
||||||
return self.get_settings('ramp')
|
|
||||||
|
|
||||||
def read_approachmode(self):
|
|
||||||
return self.get_settings('approachmode')
|
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def calc_expected(self, target, ramp):
|
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() + abs(target - self.value) * 60.0 / max(0.1, ramp)
|
||||||
|
|
||||||
def write_target(self, target):
|
|
||||||
self._stopped = False
|
|
||||||
if abs(self.value - target) < 2e-5 and target == self.target:
|
|
||||||
return target # no action needed
|
|
||||||
self._status_before_change = self.status
|
|
||||||
self.status = [self.Status.BUSY, 'changed_target']
|
|
||||||
self._last_change = time.time()
|
|
||||||
newtarget = self.put_settings(target, 'target')
|
|
||||||
self.calc_expected(target, self.ramp)
|
|
||||||
return newtarget
|
|
||||||
|
|
||||||
def write_ramp(self, value):
|
|
||||||
if not isDriving(self.status):
|
|
||||||
# do not yet write settings, as this may change the status to busy
|
|
||||||
return value
|
|
||||||
if time.time() < self._expected_target: # recalc expected target
|
|
||||||
self.calc_expected(self.target, value)
|
|
||||||
return self.put_settings(value, 'ramp')
|
|
||||||
|
|
||||||
def write_approachmode(self, value):
|
|
||||||
if not isDriving(self.status):
|
|
||||||
# do not yet write settings, as this may change the status to busy
|
|
||||||
return value
|
|
||||||
return self.put_settings(value, 'approachmode')
|
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
if not isDriving(self.status):
|
if self.isDriving():
|
||||||
return
|
return
|
||||||
if self.status[0] == self.Status.STABILIZING:
|
if self.status[0] == self.Status.STABILIZING:
|
||||||
# we are already near target
|
# we are already near target
|
||||||
@ -712,6 +551,7 @@ class Temp(PpmsMixin, Drivable):
|
|||||||
class Field(PpmsMixin, Drivable):
|
class Field(PpmsMixin, Drivable):
|
||||||
"""magnetic field"""
|
"""magnetic field"""
|
||||||
|
|
||||||
|
field = CmdHandler('field', 'FIELD?', '%g,%g,%d,%d')
|
||||||
Status = Enum(Drivable.Status,
|
Status = Enum(Drivable.Status,
|
||||||
PREPARED = 150,
|
PREPARED = 150,
|
||||||
PREPARING = 340,
|
PREPARING = 340,
|
||||||
@ -724,20 +564,20 @@ class Field(PpmsMixin, Drivable):
|
|||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
'value':
|
'value':
|
||||||
Override(datatype=FloatRange(unit='T'), poll=False, default=0),
|
Override(datatype=FloatRange(unit='T'), poll=True),
|
||||||
'status':
|
'status':
|
||||||
Override(poll=False, datatype=StatusType(Status)),
|
Override(datatype=StatusType(Status), poll=True),
|
||||||
'target':
|
'target':
|
||||||
Override(datatype=FloatRange(-15,15,unit='T'), poll=False),
|
Override(datatype=FloatRange(-15,15,unit='T'), handler=field),
|
||||||
'ramp':
|
'ramp':
|
||||||
Parameter('ramping speed', readonly=False, poll=False,
|
Parameter('ramping speed', readonly=False, handler=field,
|
||||||
datatype=FloatRange(0.064, 1.19, unit='T/min'), default=0.064),
|
datatype=FloatRange(0.064, 1.19, unit='T/min')),
|
||||||
'approachmode':
|
'approachmode':
|
||||||
Parameter('how to approach target', readonly=False, poll=False,
|
Parameter('how to approach target', readonly=False, handler=field,
|
||||||
datatype=EnumType(ApproachMode), default=0),
|
datatype=EnumType(ApproachMode)),
|
||||||
'persistentmode':
|
'persistentmode':
|
||||||
Parameter('what to do after changing field', readonly=False, poll=False,
|
Parameter('what to do after changing field', readonly=False, handler=field,
|
||||||
datatype=EnumType(PersistentMode), default=0),
|
datatype=EnumType(PersistentMode)),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
@ -756,7 +596,6 @@ class Field(PpmsMixin, Drivable):
|
|||||||
}
|
}
|
||||||
|
|
||||||
channel = 'field'
|
channel = 'field'
|
||||||
_settingnames = ['target', 'ramp', 'approachmode', 'persistentmode']
|
|
||||||
_stopped = False
|
_stopped = False
|
||||||
_last_target = 0
|
_last_target = 0
|
||||||
_last_change= 0 # means no target change is pending
|
_last_change= 0 # means no target change is pending
|
||||||
@ -784,7 +623,7 @@ class Field(PpmsMixin, Drivable):
|
|||||||
status = [self.Status.PREPARING, 'ramping leads']
|
status = [self.Status.PREPARING, 'ramping leads']
|
||||||
else:
|
else:
|
||||||
status = [self.Status.WARN, 'timeout when ramping leads']
|
status = [self.Status.WARN, 'timeout when ramping leads']
|
||||||
elif isDriving(status):
|
elif self.isDriving(status):
|
||||||
if now > self._last_change + 5 or status != self._status_before_change:
|
if now > self._last_change + 5 or 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)
|
||||||
@ -793,82 +632,34 @@ class Field(PpmsMixin, Drivable):
|
|||||||
status = [self.Status.BUSY, 'changed target while %s' % status[1]]
|
status = [self.Status.BUSY, 'changed target while %s' % status[1]]
|
||||||
else:
|
else:
|
||||||
status = [self.Status.WARN, 'field status (%r) does not change to BUSY' % status]
|
status = [self.Status.WARN, 'field status (%r) does not change to BUSY' % status]
|
||||||
|
|
||||||
|
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
def _start(self):
|
def analyze_field(self, target, ramp, approachmode, persistentmode):
|
||||||
"""common code for change target and change persistentmode"""
|
if (target, ramp, approachmode, persistentmode) != self._last_settings:
|
||||||
self._last_change = time.time()
|
# we update parameters only on change, as 'ramp' and 'approachmode' are
|
||||||
self._status_before_change = list(self.status)
|
# not always sent to the hardware
|
||||||
|
self._last_settings = target, ramp, approachmode, persistentmode
|
||||||
|
self.target = target * 1e-4
|
||||||
|
self.ramp = ramp * 6e-3
|
||||||
|
self.approachmode = approachmode
|
||||||
|
self.persistentmode = persistentmode
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def change_field(self, new):
|
||||||
"""read settings
|
if 'target' in new or 'persistentmode' in new:
|
||||||
|
# changed target or persistentmode
|
||||||
return the value for <pname> and update all other parameters
|
if 'target' in new:
|
||||||
"""
|
self._last_target = self.target # save for stop command
|
||||||
reply = self.get_reply('settings', 'FIELD?')
|
self._stopped = False
|
||||||
if reply:
|
self._last_change = time.time()
|
||||||
reply['target'] *= 1e-4
|
self._status_before_change = list(self.status)
|
||||||
reply['ramp'] *= 6e-3
|
else:
|
||||||
return self.apply_reply(reply, pname)
|
# changed ramp or approachmode
|
||||||
|
if not self.isDriving():
|
||||||
def put_settings(self, value, pname):
|
return None # nothing to be written, as this would trigger a ramp up of leads current
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
return new.target * 1e+4, new.ramp / 6e-3, new.approachmode, new.persistentmode
|
||||||
|
|
||||||
and request updated settings
|
|
||||||
"""
|
|
||||||
argdict = self.make_argdict(pname, value)
|
|
||||||
argdict['target'] *= 1e+4
|
|
||||||
argdict['ramp'] /= 6e-3
|
|
||||||
self.send_cmd('FIELD', argdict)
|
|
||||||
return self.get_settings(pname)
|
|
||||||
|
|
||||||
def read_target(self):
|
|
||||||
return self.get_settings('target')
|
|
||||||
|
|
||||||
def read_ramp(self):
|
|
||||||
return self.get_settings('ramp')
|
|
||||||
|
|
||||||
def read_approachmode(self):
|
|
||||||
return self.get_settings('approachmode')
|
|
||||||
|
|
||||||
def read_persistentmode(self):
|
|
||||||
return self.get_settings('persistentmode')
|
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def write_target(self, target):
|
|
||||||
self._last_target = self.target # save for stop command
|
|
||||||
self._stopped = False
|
|
||||||
if abs(self.value - target) < 2e-5 and target == self.target:
|
|
||||||
return target # no action needed
|
|
||||||
self._start()
|
|
||||||
result = self.put_settings(target, 'target')
|
|
||||||
self._main.read_data() # update status
|
|
||||||
return result
|
|
||||||
|
|
||||||
def write_ramp(self, value):
|
|
||||||
if not isDriving(self.status):
|
|
||||||
# do not yet write settings, as this will trigger a ramp up of leads current
|
|
||||||
return value
|
|
||||||
return self.put_settings(value, 'ramp')
|
|
||||||
|
|
||||||
def write_approachmode(self, value):
|
|
||||||
if not isDriving(self.status):
|
|
||||||
# do not yet write settings, as this will trigger a ramp up of leads current
|
|
||||||
return value
|
|
||||||
return self.put_settings(value, 'approachmode')
|
|
||||||
|
|
||||||
def write_persistentmode(self, value):
|
|
||||||
if self.persistentmode == value:
|
|
||||||
return value # no action needed
|
|
||||||
self._start()
|
|
||||||
return self.put_settings(value, 'persistentmode')
|
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
if not isDriving(self.status):
|
if not self.isDriving():
|
||||||
return
|
return
|
||||||
self.status = [self.Status.IDLE, '_stopped']
|
self.status = [self.Status.IDLE, '_stopped']
|
||||||
self._stopped = True
|
self._stopped = True
|
||||||
@ -884,20 +675,19 @@ class Field(PpmsMixin, Drivable):
|
|||||||
class Position(PpmsMixin, Drivable):
|
class Position(PpmsMixin, Drivable):
|
||||||
"""rotator position"""
|
"""rotator position"""
|
||||||
|
|
||||||
|
move = CmdHandler('move', 'MOVE?', '%g,%g,%g')
|
||||||
Status = Drivable.Status
|
Status = Drivable.Status
|
||||||
parameters = {
|
parameters = {
|
||||||
'value':
|
'value':
|
||||||
Override(datatype=FloatRange(unit='deg'), poll=False, default=0),
|
Override(datatype=FloatRange(unit='deg'), poll=True),
|
||||||
'status':
|
|
||||||
Override(poll=False),
|
|
||||||
'target':
|
'target':
|
||||||
Override(datatype=FloatRange(-720., 720., unit='deg'), default=0., poll=False),
|
Override(datatype=FloatRange(-720., 720., unit='deg'), handler=move),
|
||||||
'enabled':
|
'enabled':
|
||||||
Parameter('is this channel used?', readonly=False, poll=False,
|
Parameter('is this channel used?', readonly=False, poll=False,
|
||||||
datatype=BoolType(), default=True),
|
datatype=BoolType(), default=True),
|
||||||
'speed':
|
'speed':
|
||||||
Parameter('motor speed', readonly=False, poll=False,
|
Parameter('motor speed', readonly=False, handler=move,
|
||||||
datatype=FloatRange(0.8, 12, unit='deg/sec'), default=12.0),
|
datatype=FloatRange(0.8, 12, unit='deg/sec')),
|
||||||
'pollinterval':
|
'pollinterval':
|
||||||
Override(visibility=3),
|
Override(visibility=3),
|
||||||
}
|
}
|
||||||
@ -915,7 +705,6 @@ class Position(PpmsMixin, Drivable):
|
|||||||
_stopped = False
|
_stopped = False
|
||||||
_last_target = 0
|
_last_target = 0
|
||||||
_last_change = 0 # means no target change is pending
|
_last_change = 0 # means no target change is pending
|
||||||
mode = 0 # always use normal mode
|
|
||||||
|
|
||||||
def update_value_status(self, value, packed_status):
|
def update_value_status(self, value, packed_status):
|
||||||
"""update value and status"""
|
"""update value and status"""
|
||||||
@ -935,7 +724,7 @@ class Position(PpmsMixin, Drivable):
|
|||||||
status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
|
status = [self.Status.IDLE, 'stopped(%s)' % status[1]]
|
||||||
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 isDriving(status):
|
if self.isDriving():
|
||||||
if now > self._last_change + 15 or status != self._status_before_change:
|
if now > self._last_change + 15 or 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)
|
||||||
@ -946,50 +735,32 @@ class Position(PpmsMixin, Drivable):
|
|||||||
status = [self.Status.WARN, 'temperature status (%r) does not change to BUSY' % status]
|
status = [self.Status.WARN, 'temperature status (%r) does not change to BUSY' % status]
|
||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
def get_settings(self, pname):
|
def analyze_move(self, target, mode, speed):
|
||||||
"""read settings
|
if (target, speed) != self._last_settings:
|
||||||
|
# we update parameters only on change, as 'speed' is
|
||||||
|
# not always sent to the hardware
|
||||||
|
self._last_settings = target, speed
|
||||||
|
self.target = target
|
||||||
|
self.speed = (15 - speed) * 0.8
|
||||||
|
|
||||||
return the value for <pname> and update all other parameters
|
def change_move(self, new):
|
||||||
"""
|
speed = int(round(min(14, max(0, 15 - new.speed / 0.8)), 0))
|
||||||
reply = self.get_reply('settings', 'MOVE?')
|
return new.target, 0, speed
|
||||||
if reply:
|
|
||||||
reply['speed'] = (15 - reply['speed']) * 0.8
|
|
||||||
reply.pop('mode', None)
|
|
||||||
return self.apply_reply(reply, pname)
|
|
||||||
|
|
||||||
def put_settings(self, value, pname):
|
def write_target(self, target):
|
||||||
"""write settings, combining <pname>=<value> and current attributes
|
|
||||||
|
|
||||||
and request updated settings
|
|
||||||
"""
|
|
||||||
argdict = self.make_argdict(pname, value)
|
|
||||||
argdict['speed'] = int(round(min(14, max(0, 15 - argdict['speed'] / 0.8)), 0))
|
|
||||||
self.send_cmd('MOVE', argdict)
|
|
||||||
return self.get_settings(pname)
|
|
||||||
|
|
||||||
def read_target(self):
|
|
||||||
return self.get_settings('target')
|
|
||||||
|
|
||||||
def read_speed(self):
|
|
||||||
return self.get_settings('speed')
|
|
||||||
|
|
||||||
def write_settings(self):
|
|
||||||
return self.get_settings('settings')
|
|
||||||
|
|
||||||
def write_target(self, value):
|
|
||||||
self._last_target = self.target # save for stop command
|
self._last_target = self.target # save for stop command
|
||||||
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
|
||||||
return self.put_settings(value, 'target')
|
return target
|
||||||
|
|
||||||
def write_speed(self, value):
|
def write_speed(self, value):
|
||||||
if not isDriving(self.status):
|
if self.isDriving():
|
||||||
return value
|
return value
|
||||||
return self.put_settings(value, 'speed')
|
return None # change_move not called: as this would trigger an unnecessary move
|
||||||
|
|
||||||
def do_stop(self):
|
def do_stop(self):
|
||||||
if not isDriving(self.status):
|
if not self.isDriving():
|
||||||
return
|
return
|
||||||
self.status = [self.Status.BUSY, '_stopped']
|
self.status = [self.Status.BUSY, '_stopped']
|
||||||
self._stopped = True
|
self._stopped = True
|
||||||
|
@ -96,6 +96,12 @@ def test_CmdHandler():
|
|||||||
CMDARGS = ['channel', 'loop']
|
CMDARGS = ['channel', 'loop']
|
||||||
CMDSEPARATOR ='|'
|
CMDSEPARATOR ='|'
|
||||||
|
|
||||||
|
def __init__(self, name, querycmd, replyfmt):
|
||||||
|
changecmd = querycmd.replace('?', ' ')
|
||||||
|
if not querycmd.endswith('?'):
|
||||||
|
changecmd += ','
|
||||||
|
super().__init__(name, querycmd, replyfmt, changecmd)
|
||||||
|
|
||||||
group1 = Hdl('group1', 'SIMPLE?', '%g')
|
group1 = Hdl('group1', 'SIMPLE?', '%g')
|
||||||
group2 = Hdl('group2', 'CMD?%(channel)d', '%g,%s,%d')
|
group2 = Hdl('group2', 'CMD?%(channel)d', '%g,%s,%d')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user