Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip
This commit is contained in:
commit
9673f7b242
14
cfg/addons/be-filter-boa.cfg
Normal file
14
cfg/addons/be-filter-boa.cfg
Normal file
@ -0,0 +1,14 @@
|
||||
[NODE]
|
||||
description = CryoTel be-filter BOA
|
||||
id = be-filter-boa.addon.sea.psi.ch
|
||||
|
||||
[sea_addons]
|
||||
class = secop_psi.sea.SeaClient
|
||||
description = addons sea connection for be-filter-boa.addon
|
||||
config = be-filter-boa.addon
|
||||
service = addons
|
||||
|
||||
[befilter]
|
||||
class = secop_psi.sea.SeaReadable
|
||||
iodev = sea_addons
|
||||
sea_object = befilter
|
@ -82,6 +82,16 @@ description = dynamic needle valve position
|
||||
slot = DB8.P1,DB4.G1
|
||||
io = itc1
|
||||
|
||||
[mf]
|
||||
class = secop_psi.ips_mercury.Field
|
||||
description = magnetic field
|
||||
slot = GRPZ
|
||||
io = ips
|
||||
tolerance = 0.001
|
||||
wait_stable_field = 60
|
||||
target.max = 11
|
||||
persistent_limit = 7
|
||||
|
||||
[lev]
|
||||
class = secop_psi.mercury.HeLevel
|
||||
description = LHe level
|
||||
@ -156,14 +166,6 @@ description = coil temperature
|
||||
slot = MB1.T1
|
||||
io = ips
|
||||
|
||||
[mf]
|
||||
class = secop_psi.ips_mercury.Field
|
||||
description = magnetic field
|
||||
slot = GRPZ
|
||||
io = ips
|
||||
tolerance = 0.001
|
||||
target.max = 11
|
||||
|
||||
[om_io]
|
||||
description = dom motor IO
|
||||
class = secop_psi.phytron.PhytronIO
|
||||
|
19
cfg/sea/be-filter-boa.addon.json
Normal file
19
cfg/sea/be-filter-boa.addon.json
Normal file
@ -0,0 +1,19 @@
|
||||
{"befilter": {"base": "/befilter", "params": [
|
||||
{"path": "", "type": "float", "kids": 12},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "befilter send", "visibility": 3},
|
||||
{"path": "status", "type": "text", "visibility": 3},
|
||||
{"path": "cool", "type": "enum", "enum": {"on": 0, "off": 1}, "readonly": false, "cmd": "befilter cool"},
|
||||
{"path": "control", "type": "enum", "enum": {"auto_power": 1, "manual_power": 0, "controlled_T": 2}, "readonly": false, "cmd": "befilter control", "description": "recommended mode: auto_power, use coolpower or holdpower depending on T"},
|
||||
{"path": "set", "type": "float", "readonly": false, "cmd": "befilter set"},
|
||||
{"path": "setpower", "type": "float", "readonly": false, "cmd": "befilter setpower", "visibility": 3},
|
||||
{"path": "coolpower", "type": "float", "readonly": false, "cmd": "befilter coolpower", "visibility": 3},
|
||||
{"path": "holdpower", "type": "float", "readonly": false, "cmd": "befilter holdpower", "visibility": 3},
|
||||
{"path": "cool_threshold", "type": "float", "readonly": false, "cmd": "befilter cool_threshold", "description": "switch to coolpower above this value", "visibility": 3},
|
||||
{"path": "hold_threshold", "type": "float", "readonly": false, "cmd": "befilter hold_threshold", "description": "switch to holdpower below this value", "visibility": 3},
|
||||
{"path": "power", "type": "float"},
|
||||
{"path": "filter", "type": "none", "kids": 5},
|
||||
{"path": "filter/period", "type": "float", "readonly": false, "cmd": "befilter filter/period", "description": "oszillation period / sec"},
|
||||
{"path": "filter/amplitude", "type": "float", "readonly": false, "cmd": "befilter filter/amplitude", "description": "oszillation amplitude / K (+/-)"},
|
||||
{"path": "filter/precision", "type": "float", "readonly": false, "cmd": "befilter filter/precision"},
|
||||
{"path": "filter/raw", "type": "float"},
|
||||
{"path": "filter/intdif", "type": "float"}]}}
|
263
secop/client/interactive.py
Normal file
263
secop/client/interactive.py
Normal file
@ -0,0 +1,263 @@
|
||||
# -*- 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""simple interactive python client"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
from queue import Queue
|
||||
from secop.client import SecopClient
|
||||
from secop.errors import SECoPError
|
||||
|
||||
USAGE = """
|
||||
Usage:
|
||||
|
||||
from secop.client.interactive import Client
|
||||
|
||||
client = Client('localhost:5000') # start client.
|
||||
# this connects and creates objects for all SECoP modules in the main namespace
|
||||
|
||||
<module> # list all parameters
|
||||
<module>.<param> = <value> # change parameter
|
||||
<module>(<target>) # set target and wait until not busy
|
||||
# 'status' and 'value' changes are shown every 1 sec
|
||||
client.mininterval = 0.2 # change minimal update interval to 0.2 sec (default is 1 second)
|
||||
|
||||
<module>.watch(1) # watch changes of all parameters of a module
|
||||
<module>.watch(0) # remove all watching
|
||||
<module>.watch(status=1, value=1) # add 'status' and 'value' to watched parameters
|
||||
<module>.watch(value=0) # remove 'value' from watched parameters
|
||||
"""
|
||||
|
||||
main = sys.modules['__main__']
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, loglevel='info'):
|
||||
func = self.noop
|
||||
for lev in 'debug', 'info', 'warning', 'error':
|
||||
if lev == loglevel:
|
||||
func = self.emit
|
||||
setattr(self, lev, func)
|
||||
|
||||
@staticmethod
|
||||
def emit(fmt, *args, **kwds):
|
||||
print(str(fmt) % args)
|
||||
|
||||
@staticmethod
|
||||
def noop(fmt, *args, **kwds):
|
||||
pass
|
||||
|
||||
|
||||
class PrettyFloat(float):
|
||||
def __repr__(self):
|
||||
result = '%.12g' % self
|
||||
if '.' in result or 'e' in result:
|
||||
return result
|
||||
return result + '.'
|
||||
|
||||
|
||||
class Module:
|
||||
def __init__(self, name, secnode):
|
||||
self._name = name
|
||||
self._secnode = secnode
|
||||
self._parameters = list(secnode.modules[name]['parameters'])
|
||||
self._commands = list(secnode.modules[name]['commands'])
|
||||
self._running = None
|
||||
self._status = None
|
||||
props = secnode.modules[name]['properties']
|
||||
self._title = '# %s (%s)' % (props.get('implementation', ''), props.get('interface_classes', [''])[0])
|
||||
|
||||
def _one_line(self, pname, minwid=0):
|
||||
"""return <module>.<param> = <value> truncated to one line"""
|
||||
try:
|
||||
value = getattr(self, pname)
|
||||
# make floats appear with 7 digits only
|
||||
r = repr(json.loads(json.dumps(value), parse_float=PrettyFloat))
|
||||
except Exception as e:
|
||||
r = repr(e)
|
||||
unit = getattr(type(self), pname).unit
|
||||
if unit:
|
||||
r += ' %s' % unit
|
||||
pname = pname.ljust(minwid)
|
||||
vallen = 113 - len(self._name) - len(pname)
|
||||
if len(r) > vallen:
|
||||
r = r[:vallen - 4] + ' ...'
|
||||
return '%s.%s = %s' % (self._name, pname, r)
|
||||
|
||||
def _isBusy(self):
|
||||
return 300 <= self.status[0] < 400
|
||||
|
||||
def _status_value_update(self, m, p, status, t, e):
|
||||
if self._running:
|
||||
try:
|
||||
self._running.put(True)
|
||||
if self._running and not self._isBusy():
|
||||
self._running.put(False)
|
||||
except TypeError: # may happen when _running is removed during above lines
|
||||
pass
|
||||
|
||||
def _watch_parameter(self, m, pname, *args, forced=False, mininterval=0):
|
||||
"""show parameter update"""
|
||||
pobj = getattr(type(self), pname)
|
||||
if not args:
|
||||
args = self._secnode.cache[self._name, pname]
|
||||
value = args[0]
|
||||
now = time.time()
|
||||
if (value != pobj.prev and now >= pobj.prev_time + mininterval) or forced:
|
||||
self._secnode.log.info('%s', self._one_line(pname))
|
||||
pobj.prev = value
|
||||
pobj.prev_time = now
|
||||
|
||||
def watch(self, *args, **kwds):
|
||||
enabled = {}
|
||||
for arg in args:
|
||||
if arg == 1: # or True
|
||||
enabled.update({k: True for k in self._parameters})
|
||||
elif arg == 0: # or False
|
||||
enabled.update({k: False for k in self._parameters})
|
||||
else:
|
||||
enabled.update(arg)
|
||||
enabled.update(kwds)
|
||||
for pname, enable in enabled.items():
|
||||
self._secnode.unregister_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||
if enable:
|
||||
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||
|
||||
def read(self, pname='value'):
|
||||
value, _, error = self._secnode.readParameter(self._name, pname)
|
||||
if error:
|
||||
raise error
|
||||
return value
|
||||
|
||||
def __call__(self, target=None):
|
||||
if target is None:
|
||||
return self.read()
|
||||
self.target = target # this sets self._running
|
||||
type(self).value.prev = None # show at least one value
|
||||
show_final_value = True
|
||||
try:
|
||||
while self._running.get():
|
||||
self._watch_parameter(self._name, 'value', mininterval=self._secnode.mininterval)
|
||||
self._watch_parameter(self._name, 'status')
|
||||
except KeyboardInterrupt:
|
||||
self._secnode.log.info('-- interrupted --')
|
||||
self._running = None
|
||||
self._watch_parameter(self._name, 'status')
|
||||
self._secnode.readParameter(self._name, 'value')
|
||||
self._watch_parameter(self._name, 'value', forced=show_final_value)
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
wid = max(len(k) for k in self._parameters)
|
||||
return '%s\n%s\nCommands: %s' % (
|
||||
self._title,
|
||||
'\n'.join(self._one_line(k, wid) for k in self._parameters),
|
||||
', '.join(k + '()' for k in self._commands))
|
||||
|
||||
|
||||
class Param:
|
||||
def __init__(self, name, unit=None):
|
||||
self.name = name
|
||||
self.prev = None
|
||||
self.prev_time = 0
|
||||
self.unit = unit
|
||||
|
||||
def __get__(self, obj, owner):
|
||||
if obj is None:
|
||||
return self
|
||||
value, _, error = obj._secnode.cache[obj._name, self.name]
|
||||
if error:
|
||||
raise error
|
||||
return value
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.name == 'target':
|
||||
obj._running = Queue()
|
||||
try:
|
||||
obj._secnode.setParameter(obj._name, self.name, value)
|
||||
except SECoPError as e:
|
||||
obj._secnode.log.error(repr(e))
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self, name, modname, secnode):
|
||||
self.name = name
|
||||
self.modname = modname
|
||||
self.exec = secnode.execCommand
|
||||
|
||||
def call(self, *args, **kwds):
|
||||
if kwds:
|
||||
if args:
|
||||
raise TypeError('mixed arguments forbidden')
|
||||
result, _ = self.exec(self.modname, self.name, kwds)
|
||||
else:
|
||||
result, _ = self.exec(self.modname, self.name, args or None)
|
||||
return result
|
||||
|
||||
def __get__(self, obj, owner=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return self.call
|
||||
|
||||
|
||||
class Client(SecopClient):
|
||||
activate = True
|
||||
secnodes = {}
|
||||
mininterval = 1
|
||||
|
||||
def __init__(self, uri, loglevel='info'):
|
||||
# remove previous client:
|
||||
prev = self.secnodes.pop(uri, None)
|
||||
if prev:
|
||||
prev.log.info('remove previous client to %s', uri)
|
||||
for modname in prev.modules:
|
||||
prevnode = getattr(getattr(main, modname, None), 'secnode', None)
|
||||
if prevnode == prev:
|
||||
prev.log.info('remove previous module %s', modname)
|
||||
delattr(main, modname)
|
||||
prev.disconnect()
|
||||
self.secnodes[uri] = self
|
||||
super().__init__(uri, Logger(loglevel))
|
||||
self.connect()
|
||||
for modname, moddesc in self.modules.items():
|
||||
prev = getattr(main, modname, None)
|
||||
if prev is None:
|
||||
self.log.info('create module %s', modname)
|
||||
else:
|
||||
if getattr(prev, 'secnode', None) is None:
|
||||
self.log.error('skip module %s overwriting a global variable' % modname)
|
||||
continue
|
||||
self.log.info('overwrite module %s', modname)
|
||||
attrs = {}
|
||||
for pname, pinfo in moddesc['parameters'].items():
|
||||
unit = pinfo['datainfo'].get('unit')
|
||||
attrs[pname] = Param(pname, unit)
|
||||
for cname in moddesc['commands']:
|
||||
attrs[cname] = Command(cname, modname, self)
|
||||
mobj = type('M_%s' % modname, (Module,), attrs)(modname, self)
|
||||
if 'status' in mobj._parameters:
|
||||
self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
|
||||
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
|
||||
|
||||
setattr(main, modname, mobj)
|
||||
self.log.info('%s', USAGE)
|
@ -130,10 +130,11 @@ class Stub(DataType):
|
||||
|
||||
this workaround because datatypes need properties with datatypes defined later
|
||||
"""
|
||||
def __init__(self, datatype_name, *args):
|
||||
def __init__(self, datatype_name, *args, **kwds):
|
||||
super().__init__()
|
||||
self.name = datatype_name
|
||||
self.args = args
|
||||
self.kwds = kwds
|
||||
|
||||
def __call__(self, value):
|
||||
"""validate"""
|
||||
@ -151,7 +152,7 @@ class Stub(DataType):
|
||||
for prop in dtcls.propertyDict.values():
|
||||
stub = prop.datatype
|
||||
if isinstance(stub, cls):
|
||||
prop.datatype = globals()[stub.name](*stub.args)
|
||||
prop.datatype = globals()[stub.name](*stub.args, **stub.kwds)
|
||||
|
||||
|
||||
# SECoP types:
|
||||
@ -165,7 +166,7 @@ class FloatRange(DataType):
|
||||
"""
|
||||
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
||||
max = Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max)
|
||||
unit = Property('physical unit', Stub('StringType'), extname='unit', default='')
|
||||
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||
absolute_resolution = Property('absolute resolution', Stub('FloatRange', 0),
|
||||
extname='absolute_resolution', default=0.0)
|
||||
@ -343,7 +344,7 @@ class ScaledInteger(DataType):
|
||||
scale = Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True)
|
||||
min = Property('low limit', FloatRange(), extname='min', mandatory=True)
|
||||
max = Property('high limit', FloatRange(), extname='max', mandatory=True)
|
||||
unit = Property('physical unit', Stub('StringType'), extname='unit', default='')
|
||||
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||
absolute_resolution = Property('absolute resolution', FloatRange(0),
|
||||
extname='absolute_resolution', default=0.0)
|
||||
@ -760,7 +761,7 @@ class ArrayOf(DataType):
|
||||
|
||||
def __call__(self, value):
|
||||
"""validate an external representation to an internal one"""
|
||||
if isinstance(value, (tuple, list)):
|
||||
try:
|
||||
# check number of elements
|
||||
if self.minlen is not None and len(value) < self.minlen:
|
||||
raise BadValueError(
|
||||
@ -771,8 +772,9 @@ class ArrayOf(DataType):
|
||||
'Array too big, holds at most %d elements!' % self.minlen)
|
||||
# apply subtype valiation to all elements and return as list
|
||||
return tuple(self.members(elem) for elem in value)
|
||||
raise BadValueError(
|
||||
'Can not convert %s to ArrayOf DataType!' % repr(value))
|
||||
except TypeError:
|
||||
raise BadValueError('%s can not be converted to ArrayOf DataType!'
|
||||
% type(value).__name__) from None
|
||||
|
||||
def export_value(self, value):
|
||||
"""returns a python object fit for serialisation"""
|
||||
|
@ -106,6 +106,9 @@ class EnumMember:
|
||||
def __repr__(self):
|
||||
return '<%s%s (%d)>' % (self.enum.name + '.' if self.enum.name else '', self.name, self.value)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.value)
|
||||
|
||||
# numeric operations: delegate to int. Do we really need any of those?
|
||||
def __add__(self, other):
|
||||
return self.value.__add__(other.value if isinstance(other, EnumMember) else other)
|
||||
@ -242,7 +245,7 @@ class Enum(dict):
|
||||
name = ''
|
||||
|
||||
def __init__(self, name='', parent=None, **kwds):
|
||||
super(Enum, self).__init__()
|
||||
super().__init__()
|
||||
if isinstance(name, (dict, Enum)) and parent is None:
|
||||
# swap if only parent is given as positional argument
|
||||
name, parent = '', name
|
||||
@ -309,17 +312,17 @@ class Enum(dict):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError as e:
|
||||
raise AttributeError(str(e))
|
||||
raise AttributeError(str(e)) from None
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if self.name and key != 'name':
|
||||
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||
super(Enum, self).__setattr__(key, value)
|
||||
super().__setattr__(key, value)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if self.name:
|
||||
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||
super(Enum, self).__setitem__(key, value)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||
|
@ -186,7 +186,6 @@ class StateMachine:
|
||||
ret = self.state(self)
|
||||
self.init = False
|
||||
if self.stopped:
|
||||
self.log.debug('%r', self.stopped)
|
||||
self.last_error = self.stopped
|
||||
self.cleanup(self)
|
||||
self.stopped = False
|
||||
@ -269,7 +268,6 @@ class StateMachine:
|
||||
self.stopped = Restart
|
||||
with self._lock: # wait for running cycle finished
|
||||
if self.stopped: # cleanup is not yet done
|
||||
self.log.debug('restart')
|
||||
self.last_error = self.stopped
|
||||
self.cleanup(self) # ignore return state on restart
|
||||
self.stopped = False
|
||||
|
@ -87,7 +87,6 @@ class Field(MercuryChannel, Magfield):
|
||||
return self.query('PSU:SIG:SWHT', off_on)
|
||||
|
||||
def write_switch_heater(self, value):
|
||||
super().write_switch_heater(value)
|
||||
return self.change('PSU:SIG:SWHT', value, off_on)
|
||||
|
||||
def read_atob(self):
|
||||
@ -120,16 +119,27 @@ class Field(MercuryChannel, Magfield):
|
||||
return current / self.atob
|
||||
return 0
|
||||
|
||||
def start_ramp_to_field(self, state):
|
||||
self.change('PSU:SIG:FSET', self.persistent_field)
|
||||
def set_and_go(self, value):
|
||||
self.change('PSU:SIG:FSET', value)
|
||||
assert self.write_action('hold') == 'hold'
|
||||
assert self.write_action('run_to_set') == 'run_to_set'
|
||||
|
||||
def start_ramp_to_field(self, state):
|
||||
try:
|
||||
self.set_and_go(self.persistent_field)
|
||||
except (HardwareError, AssertionError):
|
||||
state.switch_undef = self.switch_on_time or state.now
|
||||
return self.wait_for_switch
|
||||
return self.ramp_to_field
|
||||
|
||||
def wait_for_switch(self, state):
|
||||
if self.now - self.switch_undef < self.wait_switch_on:
|
||||
return Retry()
|
||||
self.set_and_go(self.persistent_field)
|
||||
return self.ramp_to_field
|
||||
|
||||
def start_ramp_to_target(self, state):
|
||||
self.change('PSU:SIG:FSET', self.target)
|
||||
assert self.write_action('hold') == 'hold'
|
||||
assert self.write_action('run_to_set') == 'run_to_set'
|
||||
self.set_and_go(self.target)
|
||||
return self.ramp_to_target
|
||||
|
||||
def start_ramp_to_zero(self, state):
|
||||
|
@ -80,17 +80,19 @@ class Magfield(HasLimits, Drivable):
|
||||
'wait time to ensure current is stable', FloatRange(0, unit='s'), readonly=False, default=6)
|
||||
wait_stable_field = Parameter(
|
||||
'wait time to ensure field is stable', FloatRange(0, unit='s'), readonly=False, default=31)
|
||||
persistent_limit = Parameter(
|
||||
'above this limit, lead currents are not driven to 0',
|
||||
FloatRange(0, unit='$'), readonly=False, default=99)
|
||||
|
||||
_state = None
|
||||
__init = True
|
||||
_super_sw_check = False
|
||||
_last_target = None
|
||||
switch_time = None
|
||||
switch_on_time = None
|
||||
switch_off_time = None
|
||||
|
||||
def doPoll(self):
|
||||
if self.__init:
|
||||
self.__init = False
|
||||
self.switch_time = time.time()
|
||||
if self.read_switch_heater() and self.mode == Mode.PERSISTENT:
|
||||
self.read_value() # check for persistent field mismatch
|
||||
# switch off heater from previous live or manual intervention
|
||||
@ -112,6 +114,7 @@ class Magfield(HasLimits, Drivable):
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
self.registerCallbacks(self) # for update_switch_heater
|
||||
self._state = StateMachine(logger=self.log, threaded=False, cleanup=self.cleanup_state)
|
||||
|
||||
def write_target(self, target):
|
||||
@ -180,23 +183,28 @@ class Magfield(HasLimits, Drivable):
|
||||
return Retry()
|
||||
return self.start_switch_on
|
||||
|
||||
def write_switch_heater(self, value):
|
||||
"""implementations must super call this!"""
|
||||
self._super_sw_check = True
|
||||
if value != self.switch_heater:
|
||||
self.switch_time = time.time()
|
||||
return value
|
||||
def update_switch_heater(self, value):
|
||||
"""is called whenever switch heater was changed"""
|
||||
if value != 0:
|
||||
self.switch_off_time = None
|
||||
if self.switch_on_time is None:
|
||||
self.switch_on_time = time.time()
|
||||
else:
|
||||
self.switch_on_time = None
|
||||
if self.switch_off_time is None:
|
||||
self.switch_off_time = time.time()
|
||||
|
||||
def start_switch_on(self, state):
|
||||
"""switch heater on"""
|
||||
if self.switch_heater != 0:
|
||||
self.status = Status.PREPARING, 'wait for heater on'
|
||||
else:
|
||||
if self.switch_heater == 0:
|
||||
self.status = Status.PREPARING, 'turn switch heater on'
|
||||
self._super_sw_check = False
|
||||
self.write_switch_heater(True)
|
||||
if not self._super_sw_check:
|
||||
raise ProgrammingError('missing super call in write_switch_heater')
|
||||
try:
|
||||
self.write_switch_heater(True)
|
||||
except Exception as e:
|
||||
self.log.warning('write_switch_heater %r', e)
|
||||
return Retry()
|
||||
else:
|
||||
self.status = Status.PREPARING, 'wait for heater on'
|
||||
return self.switch_on
|
||||
|
||||
def switch_on(self, state):
|
||||
@ -204,7 +212,13 @@ class Magfield(HasLimits, Drivable):
|
||||
if (self.target == self._last_target and
|
||||
abs(self.target - self.persistent_field) <= self.tolerance): # short cut
|
||||
return self.check_switch_off
|
||||
if state.now - self.switch_time < self.wait_switch_on:
|
||||
self.read_switch_heater()
|
||||
if self.switch_on_time is None:
|
||||
if state.now - self.switch_off_time > 10:
|
||||
self.log.warning('switch turned off manually?')
|
||||
return self.start_switch_on
|
||||
return Retry()
|
||||
if state.now - self.switch_on_time < self.wait_switch_on:
|
||||
return Retry()
|
||||
self._last_target = self.target
|
||||
return self.start_ramp_to_target
|
||||
@ -250,12 +264,11 @@ class Magfield(HasLimits, Drivable):
|
||||
|
||||
def start_switch_off(self, state):
|
||||
"""turn off switch heater"""
|
||||
if self.switch_heater != 0:
|
||||
if self.switch_heater == 1:
|
||||
self.status = Status.FINALIZING, 'turn switch heater off'
|
||||
self.write_switch_heater(False)
|
||||
else:
|
||||
self.status = Status.FINALIZING, 'wait for heater off'
|
||||
self.write_switch_heater(False)
|
||||
# no check for super call needed here (would have been detected in start_switch_on)
|
||||
return self.switch_off
|
||||
|
||||
def switch_off(self, state):
|
||||
@ -265,8 +278,17 @@ class Magfield(HasLimits, Drivable):
|
||||
self._last_target = None
|
||||
return self.start_switch_on
|
||||
self.persistent_field = self.value
|
||||
if state.now - self.switch_time < self.wait_switch_off:
|
||||
self.read_switch_heater()
|
||||
if self.switch_off_time is None:
|
||||
if state.now - self.switch_on_time > 10:
|
||||
self.log.warning('switch turned on manually?')
|
||||
return self.start_switch_off
|
||||
return Retry()
|
||||
if state.now - self.switch_off_time < self.wait_switch_off:
|
||||
return Retry()
|
||||
if abs(self.value) > self.persistent_limit:
|
||||
self.status = Status.IDLE, 'leads current at field, switch off'
|
||||
return self.finish_state
|
||||
return self.start_ramp_to_zero
|
||||
|
||||
def start_ramp_to_zero(self, state):
|
||||
|
@ -77,3 +77,9 @@ def test_Enum():
|
||||
assert e3.c >= e2.a
|
||||
assert e3.b <= e2.b
|
||||
assert Enum({'self': 0, 'other': 1})('self') == 0
|
||||
|
||||
|
||||
def test_Enum_bool():
|
||||
e = Enum('OffOn', off=0, on=1)
|
||||
assert bool(e(0)) is False
|
||||
assert bool(e(1)) is True
|
||||
|
Loading…
x
Reference in New Issue
Block a user