Merge branch 'wip' of gitlab.psi.ch-samenv:samenv/frappy into wip
This commit is contained in:
commit
c39aef10aa
@ -13,4 +13,5 @@ Mod('stickrot',
|
|||||||
'stick rotation, typically not used as omega',
|
'stick rotation, typically not used as omega',
|
||||||
io='stick_io',
|
io='stick_io',
|
||||||
encoder_mode='CHECK',
|
encoder_mode='CHECK',
|
||||||
|
backlash=-1,
|
||||||
)
|
)
|
||||||
|
@ -43,6 +43,7 @@ Mod('mf',
|
|||||||
'frappy_psi.sea.SeaDrivable', '',
|
'frappy_psi.sea.SeaDrivable', '',
|
||||||
io='sea_main',
|
io='sea_main',
|
||||||
sea_object='mf',
|
sea_object='mf',
|
||||||
|
rel_paths=['.', 'gen', 'ips'],
|
||||||
)
|
)
|
||||||
|
|
||||||
Mod('lev',
|
Mod('lev',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{"hcp": {"base": "/hcp", "params": [
|
{"hcp": {"base": "/hcp", "params": [
|
||||||
{"path": "", "type": "float", "kids": 10},
|
{"path": "", "type": "float", "readonly": false, "cmd": "hcp set", "kids": 10},
|
||||||
{"path": "send", "type": "text", "readonly": false, "cmd": "hcp send", "visibility": 3},
|
{"path": "send", "type": "text", "readonly": false, "cmd": "hcp send", "visibility": 3},
|
||||||
{"path": "status", "type": "text", "visibility": 3},
|
{"path": "status", "type": "text", "visibility": 3},
|
||||||
{"path": "set", "type": "float", "readonly": false, "cmd": "hcp set"},
|
{"path": "set", "type": "float", "readonly": false, "cmd": "hcp set"},
|
||||||
|
@ -18,7 +18,7 @@ Mod('ts',
|
|||||||
)
|
)
|
||||||
|
|
||||||
Mod('hcp',
|
Mod('hcp',
|
||||||
'frappy_psi.sea.SeaReadable', '',
|
'frappy_psi.sea.SeaWritable', '',
|
||||||
io='sea_stick',
|
io='sea_stick',
|
||||||
sea_object='hcp',
|
sea_object='hcp',
|
||||||
)
|
)
|
||||||
|
@ -200,7 +200,10 @@ implemented the readable stuff. We need to define some properties of the ``targe
|
|||||||
parameter and add a property ``loop`` indicating, which control loop and
|
parameter and add a property ``loop`` indicating, which control loop and
|
||||||
heater output we use.
|
heater output we use.
|
||||||
|
|
||||||
In addition, we have to implement the methods ``write_target`` and ``read_target``:
|
In addition, we have to implement the method ``write_target``. Remark: we do not
|
||||||
|
implement ``read_target`` here, because the lakeshore does not offer to read back the
|
||||||
|
real target. The SETP command is returning the working setpoint, which may be distinct
|
||||||
|
from target during a ramp.
|
||||||
|
|
||||||
.. code:: python
|
.. code:: python
|
||||||
|
|
||||||
@ -216,11 +219,9 @@ In addition, we have to implement the methods ``write_target`` and ``read_target
|
|||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
# we always use a request / reply scheme
|
# we always use a request / reply scheme
|
||||||
reply = self.communicate(f'SETP {self.loop},{target};SETP?{self.loop}')
|
self.communicate(f'SETP {self.loop},{target};*OPC?')
|
||||||
return float(reply)
|
return target
|
||||||
|
|
||||||
def read_target(self):
|
|
||||||
return float(self.communicate(f'SETP?{self.loop}'))
|
|
||||||
|
|
||||||
In order to test this, we will need to change the entry module ``T`` in the
|
In order to test this, we will need to change the entry module ``T`` in the
|
||||||
configuration file:
|
configuration file:
|
||||||
@ -272,13 +273,12 @@ There are two things still missing:
|
|||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
# reactivate heater in case it was switched off
|
# reactivate heater in case it was switched off
|
||||||
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
|
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
|
||||||
reply = self.communicate(f'SETP {self.loop},{target};SETP? {self.loop}')
|
self.communicate(f'SETP {self.loop},{target};*OPC?')
|
||||||
self._driving = True
|
self._driving = True
|
||||||
# Setting the status attribute triggers an update message for the SECoP status
|
# Setting the status attribute triggers an update message for the SECoP status
|
||||||
# parameter. This has to be done before returning from this method!
|
# parameter. This has to be done before returning from this method!
|
||||||
self.status = BUSY, 'target changed'
|
self.status = BUSY, 'target changed'
|
||||||
return float(reply)
|
return target
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
@ -403,4 +403,10 @@ Appendix 2: Extract from the LakeShore Manual
|
|||||||
Command (340) RANGE? *term*
|
Command (340) RANGE? *term*
|
||||||
Command (336/350) RANGE?<loop> *term*
|
Command (336/350) RANGE?<loop> *term*
|
||||||
Reply <range> *term*
|
Reply <range> *term*
|
||||||
|
**Operation Complete Query**
|
||||||
|
----------------------------------------------
|
||||||
|
Command *OPC?
|
||||||
|
Reply 1
|
||||||
|
Description in Frappy, we append this command to request in order
|
||||||
|
to generate a reply
|
||||||
====================== =======================
|
====================== =======================
|
||||||
|
@ -261,7 +261,6 @@ class SecopClient(ProxyClient):
|
|||||||
"""a general SECoP client"""
|
"""a general SECoP client"""
|
||||||
reconnect_timeout = 10
|
reconnect_timeout = 10
|
||||||
_running = False
|
_running = False
|
||||||
_shutdown = False
|
|
||||||
_rxthread = None
|
_rxthread = None
|
||||||
_txthread = None
|
_txthread = None
|
||||||
_connthread = None
|
_connthread = None
|
||||||
@ -283,6 +282,7 @@ class SecopClient(ProxyClient):
|
|||||||
self.uri = uri
|
self.uri = uri
|
||||||
self.nodename = uri
|
self.nodename = uri
|
||||||
self._lock = RLock()
|
self._lock = RLock()
|
||||||
|
self._shutdown = Event()
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
try:
|
try:
|
||||||
@ -303,7 +303,7 @@ class SecopClient(ProxyClient):
|
|||||||
else:
|
else:
|
||||||
self._set_state(False, 'connecting')
|
self._set_state(False, 'connecting')
|
||||||
deadline = time.time() + try_period
|
deadline = time.time() + try_period
|
||||||
while not self._shutdown:
|
while not self._shutdown.is_set():
|
||||||
try:
|
try:
|
||||||
self.io = AsynConn(self.uri) # timeout 1 sec
|
self.io = AsynConn(self.uri) # timeout 1 sec
|
||||||
self.io.writeline(IDENTREQUEST.encode('utf-8'))
|
self.io.writeline(IDENTREQUEST.encode('utf-8'))
|
||||||
@ -339,8 +339,8 @@ class SecopClient(ProxyClient):
|
|||||||
# stay online for now, if activated
|
# stay online for now, if activated
|
||||||
self._set_state(self.online and self.activate)
|
self._set_state(self.online and self.activate)
|
||||||
raise
|
raise
|
||||||
time.sleep(1)
|
self._shutdown.wait(1)
|
||||||
if not self._shutdown:
|
if not self._shutdown.is_set():
|
||||||
self.log.info('%s ready', self.nodename)
|
self.log.info('%s ready', self.nodename)
|
||||||
|
|
||||||
def __txthread(self):
|
def __txthread(self):
|
||||||
@ -436,7 +436,7 @@ class SecopClient(ProxyClient):
|
|||||||
self.log.error('rxthread ended with %r', e)
|
self.log.error('rxthread ended with %r', e)
|
||||||
self._rxthread = None
|
self._rxthread = None
|
||||||
self.disconnect(False)
|
self.disconnect(False)
|
||||||
if self._shutdown:
|
if self._shutdown.is_set():
|
||||||
return
|
return
|
||||||
if self.activate:
|
if self.activate:
|
||||||
self.log.info('try to reconnect to %s', self.uri)
|
self.log.info('try to reconnect to %s', self.uri)
|
||||||
@ -454,7 +454,7 @@ class SecopClient(ProxyClient):
|
|||||||
self._connthread = mkthread(self._reconnect, connected_callback)
|
self._connthread = mkthread(self._reconnect, connected_callback)
|
||||||
|
|
||||||
def _reconnect(self, connected_callback=None):
|
def _reconnect(self, connected_callback=None):
|
||||||
while not self._shutdown:
|
while not self._shutdown.is_set():
|
||||||
try:
|
try:
|
||||||
self.connect()
|
self.connect()
|
||||||
if connected_callback:
|
if connected_callback:
|
||||||
@ -474,15 +474,15 @@ class SecopClient(ProxyClient):
|
|||||||
self.log.info('continue trying to reconnect')
|
self.log.info('continue trying to reconnect')
|
||||||
# self.log.warning(formatExtendedTraceback())
|
# self.log.warning(formatExtendedTraceback())
|
||||||
self._set_state(False)
|
self._set_state(False)
|
||||||
time.sleep(self.reconnect_timeout)
|
self._shutdown.wait(self.reconnect_timeout)
|
||||||
else:
|
else:
|
||||||
time.sleep(1)
|
self._shutdown.wait(1)
|
||||||
self._connthread = None
|
self._connthread = None
|
||||||
|
|
||||||
def disconnect(self, shutdown=True):
|
def disconnect(self, shutdown=True):
|
||||||
self._running = False
|
self._running = False
|
||||||
if shutdown:
|
if shutdown:
|
||||||
self._shutdown = True
|
self._shutdown.set()
|
||||||
self._set_state(False, 'shutdown')
|
self._set_state(False, 'shutdown')
|
||||||
if self._connthread:
|
if self._connthread:
|
||||||
if self._connthread == current_thread():
|
if self._connthread == current_thread():
|
||||||
|
@ -191,8 +191,8 @@ class HasUnit:
|
|||||||
class FloatRange(HasUnit, DataType):
|
class FloatRange(HasUnit, DataType):
|
||||||
"""(restricted) float type
|
"""(restricted) float type
|
||||||
|
|
||||||
:param minval: (property **min**)
|
:param min: (property **min**)
|
||||||
:param maxval: (property **max**)
|
:param max: (property **max**)
|
||||||
:param kwds: any of the properties below
|
:param kwds: any of the properties below
|
||||||
"""
|
"""
|
||||||
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
||||||
@ -203,11 +203,11 @@ class FloatRange(HasUnit, DataType):
|
|||||||
relative_resolution = Property('relative resolution', Stub('FloatRange', 0),
|
relative_resolution = Property('relative resolution', Stub('FloatRange', 0),
|
||||||
extname='relative_resolution', default=1.2e-7)
|
extname='relative_resolution', default=1.2e-7)
|
||||||
|
|
||||||
def __init__(self, minval=None, maxval=None, **kwds):
|
def __init__(self, min=None, max=None, **kwds): # pylint: disable=redefined-builtin
|
||||||
super().__init__()
|
super().__init__()
|
||||||
kwds['min'] = minval if minval is not None else -sys.float_info.max
|
self.set_properties(min=min if min is not None else -sys.float_info.max,
|
||||||
kwds['max'] = maxval if maxval is not None else sys.float_info.max
|
max=max if max is not None else sys.float_info.max,
|
||||||
self.set_properties(**kwds)
|
**kwds)
|
||||||
|
|
||||||
def checkProperties(self):
|
def checkProperties(self):
|
||||||
self.default = 0 if self.min <= 0 <= self.max else self.min
|
self.default = 0 if self.min <= 0 <= self.max else self.min
|
||||||
@ -247,9 +247,9 @@ class FloatRange(HasUnit, DataType):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
hints = self.get_info()
|
hints = self.get_info()
|
||||||
if 'min' in hints:
|
if 'min' in hints:
|
||||||
hints['minval'] = hints.pop('min')
|
hints['min'] = hints.pop('min')
|
||||||
if 'max' in hints:
|
if 'max' in hints:
|
||||||
hints['maxval'] = hints.pop('max')
|
hints['max'] = hints.pop('max')
|
||||||
return 'FloatRange(%s)' % (', '.join('%s=%r' % (k, v) for k, v in hints.items()))
|
return 'FloatRange(%s)' % (', '.join('%s=%r' % (k, v) for k, v in hints.items()))
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
@ -281,18 +281,18 @@ class FloatRange(HasUnit, DataType):
|
|||||||
class IntRange(DataType):
|
class IntRange(DataType):
|
||||||
"""restricted int type
|
"""restricted int type
|
||||||
|
|
||||||
:param minval: (property **min**)
|
:param min: (property **min**)
|
||||||
:param maxval: (property **max**)
|
:param max: (property **max**)
|
||||||
"""
|
"""
|
||||||
min = Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True)
|
min = Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True)
|
||||||
max = Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True)
|
max = Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True)
|
||||||
# a unit on an int is now allowed in SECoP, but do we need them in Frappy?
|
# a unit on an int is now allowed in SECoP, but do we need them in Frappy?
|
||||||
# unit = Property('physical unit', StringType(), extname='unit', default='')
|
# unit = Property('physical unit', StringType(), extname='unit', default='')
|
||||||
|
|
||||||
def __init__(self, minval=None, maxval=None):
|
def __init__(self, min=None, max=None): # pylint: disable=redefined-builtin
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.set_properties(min=DEFAULT_MIN_INT if minval is None else minval,
|
self.set_properties(min=DEFAULT_MIN_INT if min is None else min,
|
||||||
max=DEFAULT_MAX_INT if maxval is None else maxval)
|
max=DEFAULT_MAX_INT if max is None else max)
|
||||||
|
|
||||||
def checkProperties(self):
|
def checkProperties(self):
|
||||||
self.default = 0 if self.min <= 0 <= self.max else self.min
|
self.default = 0 if self.min <= 0 <= self.max else self.min
|
||||||
@ -364,8 +364,8 @@ class IntRange(DataType):
|
|||||||
class ScaledInteger(HasUnit, DataType):
|
class ScaledInteger(HasUnit, DataType):
|
||||||
"""scaled integer (= fixed resolution float) type
|
"""scaled integer (= fixed resolution float) type
|
||||||
|
|
||||||
:param minval: (property **min**)
|
:param min: (property **min**)
|
||||||
:param maxval: (property **max**)
|
:param max: (property **max**)
|
||||||
:param kwds: any of the properties below
|
:param kwds: any of the properties below
|
||||||
|
|
||||||
note: limits are for the scaled float value
|
note: limits are for the scaled float value
|
||||||
@ -380,7 +380,8 @@ class ScaledInteger(HasUnit, DataType):
|
|||||||
relative_resolution = Property('relative resolution', FloatRange(0),
|
relative_resolution = Property('relative resolution', FloatRange(0),
|
||||||
extname='relative_resolution', default=1.2e-7)
|
extname='relative_resolution', default=1.2e-7)
|
||||||
|
|
||||||
def __init__(self, scale, minval=None, maxval=None, absolute_resolution=None, **kwds):
|
# pylint: disable=redefined-builtin
|
||||||
|
def __init__(self, scale, min=None, max=None, absolute_resolution=None, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
try:
|
try:
|
||||||
scale = float(scale)
|
scale = float(scale)
|
||||||
@ -390,8 +391,8 @@ class ScaledInteger(HasUnit, DataType):
|
|||||||
absolute_resolution = scale
|
absolute_resolution = scale
|
||||||
self.set_properties(
|
self.set_properties(
|
||||||
scale=scale,
|
scale=scale,
|
||||||
min=DEFAULT_MIN_INT * scale if minval is None else float(minval),
|
min=DEFAULT_MIN_INT * scale if min is None else float(min),
|
||||||
max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval),
|
max=DEFAULT_MAX_INT * scale if max is None else float(max),
|
||||||
absolute_resolution=absolute_resolution,
|
absolute_resolution=absolute_resolution,
|
||||||
**kwds)
|
**kwds)
|
||||||
|
|
||||||
@ -1327,11 +1328,11 @@ DATATYPES = {
|
|||||||
'bool': lambda **kwds:
|
'bool': lambda **kwds:
|
||||||
BoolType(),
|
BoolType(),
|
||||||
'int': lambda min, max, **kwds:
|
'int': lambda min, max, **kwds:
|
||||||
IntRange(minval=min, maxval=max),
|
IntRange(min=min, max=max),
|
||||||
'scaled': lambda scale, min, max, **kwds:
|
'scaled': lambda scale, min, max, **kwds:
|
||||||
ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **floatargs(kwds)),
|
ScaledInteger(scale=scale, min=min*scale, max=max*scale, **floatargs(kwds)),
|
||||||
'double': lambda min=None, max=None, **kwds:
|
'double': lambda min=None, max=None, **kwds:
|
||||||
FloatRange(minval=min, maxval=max, **floatargs(kwds)),
|
FloatRange(min=min, max=max, **floatargs(kwds)),
|
||||||
'blob': lambda maxbytes, minbytes=0, **kwds:
|
'blob': lambda maxbytes, minbytes=0, **kwds:
|
||||||
BLOBType(minbytes=minbytes, maxbytes=maxbytes),
|
BLOBType(minbytes=minbytes, maxbytes=maxbytes),
|
||||||
'string': lambda minchars=0, maxchars=None, isUTF8=False, **kwds:
|
'string': lambda minchars=0, maxchars=None, isUTF8=False, **kwds:
|
||||||
|
@ -63,6 +63,7 @@ class HasIO(Module):
|
|||||||
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
|
io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable
|
||||||
io.callingModule = []
|
io.callingModule = []
|
||||||
srv.modules[ioname] = io
|
srv.modules[ioname] = io
|
||||||
|
srv.dispatcher.register_module(io, ioname)
|
||||||
self.ioDict[self.uri] = ioname
|
self.ioDict[self.uri] = ioname
|
||||||
self.io = ioname
|
self.io = ioname
|
||||||
|
|
||||||
|
@ -30,10 +30,10 @@ Remarks:
|
|||||||
import sys
|
import sys
|
||||||
from logging import DEBUG, INFO, addLevelName
|
from logging import DEBUG, INFO, addLevelName
|
||||||
import mlzlog
|
import mlzlog
|
||||||
from frappy.errors import NoSuchModuleError
|
|
||||||
from frappy.server import Server
|
from frappy.server import Server
|
||||||
from frappy.config import load_config, Mod as ConfigMod
|
from frappy.config import load_config, Mod as ConfigMod
|
||||||
from frappy.lib import generalConfig
|
from frappy.lib import generalConfig
|
||||||
|
from frappy.protocol import dispatcher
|
||||||
|
|
||||||
|
|
||||||
USAGE = """create config on the fly:
|
USAGE = """create config on the fly:
|
||||||
@ -77,27 +77,25 @@ class MainLogger:
|
|||||||
self.log.handlers[0].setLevel(LOG_LEVELS['comlog'])
|
self.log.handlers[0].setLevel(LOG_LEVELS['comlog'])
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher:
|
class Dispatcher(dispatcher.Dispatcher):
|
||||||
def __init__(self, name, log, opts, srv):
|
def __init__(self, name, log, options, srv):
|
||||||
self.log = log
|
super().__init__(name, log, options, srv)
|
||||||
self._modules = {}
|
self.log = srv.log # overwrite child logger
|
||||||
self.equipment_id = opts.pop('equipment_id', name)
|
|
||||||
|
|
||||||
def announce_update(self, modulename, pname, pobj):
|
def announce_update(self, modulename, pname, pobj):
|
||||||
if pobj.readerror:
|
if pobj.readerror:
|
||||||
value = repr(pobj.readerror)
|
value = repr(pobj.readerror)
|
||||||
else:
|
else:
|
||||||
value = pobj.value
|
value = pobj.value
|
||||||
self.log.info('%s:%s %r', modulename, pname, value)
|
logobj = self._modules.get(modulename, self)
|
||||||
|
# self.log.info('%s:%s %r', modulename, pname, value)
|
||||||
|
logobj.log.info('%s %r', pname, value)
|
||||||
|
|
||||||
def register_module(self, moduleobj, modulename, export=True):
|
def register_module(self, moduleobj, modulename, export=True):
|
||||||
|
self.log.info('registering %s', modulename)
|
||||||
|
super().register_module(moduleobj, modulename, export)
|
||||||
setattr(main, modulename, moduleobj)
|
setattr(main, modulename, moduleobj)
|
||||||
self._modules[modulename] = moduleobj
|
self.get_module(modulename)
|
||||||
|
|
||||||
def get_module(self, modulename):
|
|
||||||
if modulename in self._modules:
|
|
||||||
return self._modules[modulename]
|
|
||||||
raise NoSuchModuleError(f'Module {modulename!r} does not exist on this SEC-Node!')
|
|
||||||
|
|
||||||
|
|
||||||
logger = MainLogger()
|
logger = MainLogger()
|
||||||
|
@ -54,7 +54,7 @@ class SimBase:
|
|||||||
|
|
||||||
attrs['write_' + k] = writer
|
attrs['write_' + k] = writer
|
||||||
|
|
||||||
return object.__new__(type(f'SimBase_{devname}', (cls,), attrs))
|
return super().__new__(type(f'SimBase_{devname}', (cls,), attrs))
|
||||||
|
|
||||||
def initModule(self):
|
def initModule(self):
|
||||||
super().initModule()
|
super().initModule()
|
||||||
|
@ -69,24 +69,21 @@ class TemperatureSensor(HasIO, Readable):
|
|||||||
class TemperatureLoop(TemperatureSensor, Drivable):
|
class TemperatureLoop(TemperatureSensor, Drivable):
|
||||||
# lakeshore loop number to be used for this module
|
# lakeshore loop number to be used for this module
|
||||||
loop = Property('lakeshore loop', IntRange(1, 2), default=1)
|
loop = Property('lakeshore loop', IntRange(1, 2), default=1)
|
||||||
target = Parameter(datatype=FloatRange(min=0, max=1500, unit='K'))
|
target = Parameter(datatype=FloatRange(unit='K', min=0, max=1500))
|
||||||
heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336
|
heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336
|
||||||
tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly = False)
|
tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly=False)
|
||||||
_driving = False
|
_driving = False
|
||||||
|
|
||||||
def write_target(self, target):
|
def write_target(self, target):
|
||||||
# reactivate heater in case it was switched off
|
# reactivate heater in case it was switched off
|
||||||
# the command has to be changed in case of model 340 to f'RANGE {self.heater_range};RANGE?'
|
# the command has to be changed in case of model 340 to f'RANGE {self.heater_range};RANGE?'
|
||||||
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
|
self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}')
|
||||||
reply = self.communicate(f'SETP {self.loop},{target};SETP? {self.loop}')
|
self.communicate(f'SETP {self.loop},{target};*OPC?')
|
||||||
self._driving = True
|
self._driving = True
|
||||||
# Setting the status attribute triggers an update message for the SECoP status
|
# Setting the status attribute triggers an update message for the SECoP status
|
||||||
# parameter. This has to be done before returning from this method!
|
# parameter. This has to be done before returning from this method!
|
||||||
self.status = BUSY, 'target changed'
|
self.status = BUSY, 'target changed'
|
||||||
return float(reply)
|
return target
|
||||||
|
|
||||||
def read_target(self):
|
|
||||||
return float(self.communicate(f'SETP?{self.loop}'))
|
|
||||||
|
|
||||||
def read_status(self):
|
def read_status(self):
|
||||||
code = int(self.communicate(f'RDGST?{self.channel}'))
|
code = int(self.communicate(f'RDGST?{self.channel}'))
|
||||||
|
@ -23,12 +23,36 @@
|
|||||||
"""modules to access parameters"""
|
"""modules to access parameters"""
|
||||||
|
|
||||||
from frappy.core import Drivable, IDLE, Attached, StringType, Property, \
|
from frappy.core import Drivable, IDLE, Attached, StringType, Property, \
|
||||||
Parameter, FloatRange
|
Parameter, FloatRange, Readable
|
||||||
from frappy.errors import ConfigError
|
from frappy.errors import ConfigError
|
||||||
from frappy_psi.convergence import HasConvergence
|
from frappy_psi.convergence import HasConvergence
|
||||||
from frappy_psi.mixins import HasRamp
|
from frappy_psi.mixins import HasRamp
|
||||||
|
|
||||||
|
|
||||||
|
class Par(Readable):
|
||||||
|
value = Parameter(datatype=FloatRange(unit='$'))
|
||||||
|
read = Attached(description='<module>.<parameter> for read')
|
||||||
|
unit = Property('main unit', StringType())
|
||||||
|
|
||||||
|
def setProperty(self, key, value):
|
||||||
|
if key == 'read':
|
||||||
|
value, param = value.split('.')
|
||||||
|
setattr(self, f'{key}_param', param)
|
||||||
|
super().setProperty(key, value)
|
||||||
|
|
||||||
|
def checkProperties(self):
|
||||||
|
self.applyMainUnit(self.unit)
|
||||||
|
if self.read == self.name :
|
||||||
|
raise ConfigError('illegal recursive read/write module')
|
||||||
|
super().checkProperties()
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
return getattr(self.read, f'{self.read_param}')
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
return IDLE, ''
|
||||||
|
|
||||||
|
|
||||||
class Driv(Drivable):
|
class Driv(Drivable):
|
||||||
value = Parameter(datatype=FloatRange(unit='$'))
|
value = Parameter(datatype=FloatRange(unit='$'))
|
||||||
target = Parameter(datatype=FloatRange(unit='$'))
|
target = Parameter(datatype=FloatRange(unit='$'))
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
# 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
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
@ -185,8 +184,9 @@ class SeaClient(ProxyClient, Module):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise CommunicationFailedError('reply %r should be "Login OK"' % reply)
|
raise CommunicationFailedError('reply %r should be "Login OK"' % reply)
|
||||||
self.request('frappy_config %s %s' % (self.service, self.config))
|
result = self.request('frappy_config %s %s' % (self.service, self.config))
|
||||||
|
if result not in {'0', '1'}:
|
||||||
|
raise CommunicationFailedError(f'reply from frappy_config: {result}')
|
||||||
# frappy_async_client switches to the json protocol (better for updates)
|
# frappy_async_client switches to the json protocol (better for updates)
|
||||||
self.asynio.writeline(b'frappy_async_client')
|
self.asynio.writeline(b'frappy_async_client')
|
||||||
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
|
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
|
||||||
@ -248,7 +248,16 @@ class SeaClient(ProxyClient, Module):
|
|||||||
raise TimeoutError('no response within 10s')
|
raise TimeoutError('no response within 10s')
|
||||||
|
|
||||||
def _rxthread(self, started_callback):
|
def _rxthread(self, started_callback):
|
||||||
|
recheck = None
|
||||||
while not self.shutdown:
|
while not self.shutdown:
|
||||||
|
if recheck and time.time() > recheck:
|
||||||
|
# try to collect device changes within 1 sec
|
||||||
|
recheck = None
|
||||||
|
result = self.request('check_config %s %s' % (self.service, self.config))
|
||||||
|
if result == '1':
|
||||||
|
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
|
||||||
|
else:
|
||||||
|
self.DISPATCHER.shutdown()
|
||||||
try:
|
try:
|
||||||
reply = self.asynio.readline()
|
reply = self.asynio.readline()
|
||||||
if reply is None:
|
if reply is None:
|
||||||
@ -307,11 +316,7 @@ class SeaClient(ProxyClient, Module):
|
|||||||
if mplist is None:
|
if mplist is None:
|
||||||
if path.startswith('/device'):
|
if path.startswith('/device'):
|
||||||
if path == '/device/changetime':
|
if path == '/device/changetime':
|
||||||
result = self.request('check_config %s %s' % (self.service, self.config))
|
recheck = time.time() + 1
|
||||||
if result == '1':
|
|
||||||
self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode())
|
|
||||||
else:
|
|
||||||
self.DISPATCHER.shutdown()
|
|
||||||
elif path.startswith('/device/frappy_%s' % self.service) and value == '':
|
elif path.startswith('/device/frappy_%s' % self.service) and value == '':
|
||||||
self.DISPATCHER.shutdown()
|
self.DISPATCHER.shutdown()
|
||||||
else:
|
else:
|
||||||
|
@ -160,9 +160,9 @@ def test_ScaledInteger():
|
|||||||
with pytest.raises(ProgrammingError):
|
with pytest.raises(ProgrammingError):
|
||||||
ScaledInteger('xc', 'Yx')
|
ScaledInteger('xc', 'Yx')
|
||||||
with pytest.raises(ProgrammingError):
|
with pytest.raises(ProgrammingError):
|
||||||
ScaledInteger(scale=0, minval=1, maxval=2)
|
ScaledInteger(scale=0, min=1, max=2)
|
||||||
with pytest.raises(ProgrammingError):
|
with pytest.raises(ProgrammingError):
|
||||||
ScaledInteger(scale=-10, minval=1, maxval=2)
|
ScaledInteger(scale=-10, min=1, max=2)
|
||||||
# check that unit can be changed
|
# check that unit can be changed
|
||||||
dt.setProperty('unit', 'A')
|
dt.setProperty('unit', 'A')
|
||||||
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300,
|
assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300,
|
||||||
@ -563,7 +563,7 @@ def test_get_datatype():
|
|||||||
assert isinstance(get_datatype(
|
assert isinstance(get_datatype(
|
||||||
{'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger)
|
{'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger)
|
||||||
|
|
||||||
dt = ScaledInteger(scale=0.03, minval=0, maxval=9.9)
|
dt = ScaledInteger(scale=0.03, min=0, max=9.9)
|
||||||
assert dt.export_datatype() == {'type': 'scaled', 'max':330, 'min':0, 'scale':0.03}
|
assert dt.export_datatype() == {'type': 'scaled', 'max':330, 'min':0, 'scale':0.03}
|
||||||
assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype()
|
assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user