improve sea client

This commit is contained in:
l_samenv 2020-09-08 13:36:11 +02:00
parent 99bdafdd0c
commit 5f9344109d

View File

@ -41,7 +41,7 @@ from secop.modules import Module, Parameter, Command, Override, Drivable, Readab
from secop.datatypes import StringType, FloatRange, ArrayOf, BoolType, IntRange, EnumType from secop.datatypes import StringType, FloatRange, ArrayOf, BoolType, IntRange, EnumType
from secop.lib import mkthread, getGeneralConfig from secop.lib import mkthread, getGeneralConfig
from secop.lib.asynconn import AsynConn, ConnectionClosed from secop.lib.asynconn import AsynConn, ConnectionClosed
from secop.metaclass import Done from secop.metaclass import ModuleMeta, Done
from secop.errors import HardwareError, secop_error, ConfigError from secop.errors import HardwareError, secop_error, ConfigError
from secop.client import ProxyClient from secop.client import ProxyClient
from secop.protocol.dispatcher import make_update from secop.protocol.dispatcher import make_update
@ -54,7 +54,6 @@ description = %(samenv)s over SEA
[seaconn] [seaconn]
class = secop_psi.sea.SeaClient class = secop_psi.sea.SeaClient
description = a SEA connection description = a SEA connection
uri = %(uri)s
""" """
CFG_MODULE = """ CFG_MODULE = """
@ -67,6 +66,7 @@ remote_paths = .
SEA_DIR = expanduser('~/sea') SEA_DIR = expanduser('~/sea')
confdir = getGeneralConfig()['confdir'].split(':', 1)[0]
def get_sea_port(instance): def get_sea_port(instance):
@ -143,14 +143,14 @@ class SeaClient(ProxyClient, Module):
def request(self, command): def request(self, command):
"""send a request and wait for reply""" """send a request and wait for reply"""
with self._write_lock: with self._write_lock:
if not self.io: if not self.io or not self.io.connection:
if not self.asyncio.connection:
self._connect(None)
self.io = AsynConn(self.uri) self.io = AsynConn(self.uri)
assert self.io.readline() == b'OK' assert self.io.readline() == b'OK'
self.io.writeline(b'seauser seaser') self.io.writeline(b'seauser seaser')
assert self.io.readline() == b'Login OK' assert self.io.readline() == b'Login OK'
# do this before changing to user rights (avoid cluttering log) self.io.flush_recv()
#self.io.writeline(b'protocol set json')
#self.io.writeline(b'config rights seauser seaser')
self.io.writeline(('fulltransact %s' % command).encode()) self.io.writeline(('fulltransact %s' % command).encode())
result = None result = None
deadline = time.time() + 10 deadline = time.time() + 10
@ -170,7 +170,6 @@ class SeaClient(ProxyClient, Module):
print('missing TRANSACTIONSTART on: %s' % command) print('missing TRANSACTIONSTART on: %s' % command)
return '' return ''
if not result: if not result:
print('missing return value on: %s' % command)
return '' return ''
return '\n'.join(result) return '\n'.join(result)
if result is None: if result is None:
@ -203,7 +202,7 @@ class SeaClient(ProxyClient, Module):
continue continue
else: else:
continue continue
data = {'error %s' % path: readerror.replace('ERROR: ', '')} data = {'%s.geterror' % path: readerror.replace('ERROR: ', '')}
obj = None obj = None
flag = 'hdbevent' flag = 'hdbevent'
else: else:
@ -217,22 +216,30 @@ class SeaClient(ProxyClient, Module):
started_callback = None started_callback = None
continue continue
if flag != 'hdbevent': if flag != 'hdbevent':
print('SKIP', msg) if obj != 'protocol':
print('SKIP', msg)
continue continue
if data is None: if data is None:
continue continue
now = time.time()
for path, value in data.items(): for path, value in data.items():
readerror = None readerror = None
if path.startswith('error '): if path.endswith('.geterror'):
readerror = HardwareError(value) if value:
readerror = HardwareError(value)
path = path.rsplit('.', 1)[0]
value = None value = None
path = path[6:]
try: try:
module, param = self.path2param[path] module, param = self.path2param[path]
self.updateValue(module, param, value, time.time(), readerror)
except KeyError: except KeyError:
# print('UNUSED', msg) # print('UNUSED', msg)
pass # unused parameters continue # unused parameter
oldv, oldt, oldr = self.cache.get((module, param), [None, None, None])
if value is None:
value = oldv
if value != oldv or str(readerror) != str(oldr) or abs(now - oldt) > 60:
# do not update unchanged values within 0.1 sec
self.updateValue(module, param, value, now, readerror)
def do_communicate(self, command): def do_communicate(self, command):
reply = self.request(command) reply = self.request(command)
@ -243,10 +250,9 @@ class SeaClient(ProxyClient, Module):
reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n')) reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n'))
samenv, reply = json.loads(reply) samenv, reply = json.loads(reply)
samenv = samenv.replace('/', '_') samenv = samenv.replace('/', '_')
confdir = getGeneralConfig()['confdir']
result = [] result = []
with open(join(confdir, 'sea', samenv + '.cfg'), 'w') as cfp: with open(join(confdir, 'sea', samenv + '.cfg'), 'w') as cfp:
cfp.write(CFG_HEADER % dict(samenv=samenv, uri=self.uri)) cfp.write(CFG_HEADER % dict(samenv=samenv))
for filename, obj, descr in reply: for filename, obj, descr in reply:
content = json.dumps([obj, descr]).replace('}, {', '},\n{') content = json.dumps([obj, descr]).replace('}, {', '},\n{')
with open(join(confdir, 'sea', filename + '.json'), 'w') as fp: with open(join(confdir, 'sea', filename + '.json'), 'w') as fp:
@ -290,28 +296,24 @@ class SeaModule(Module):
path2param = None path2param = None
sea_object = None sea_object = None
def __init__(self, name, logger, cfgdict, dispatcher): def __new__(cls, name, logger, cfgdict, dispatcher):
self.buildParams(cfgdict, name)
Module.__init__(self, name, logger, cfgdict, dispatcher)
def buildParams(self, cfgdict, name):
visibility_level = cfgdict.pop('visibility_level', 2) visibility_level = cfgdict.pop('visibility_level', 2)
json_descr = cfgdict.pop('json_descr') json_descr = cfgdict.pop('json_descr')
remote_paths = cfgdict.pop('remote_paths', '') remote_paths = cfgdict.pop('remote_paths', '')
if 'description' not in cfgdict: if 'description' not in cfgdict:
cfgdict['description'] = '%s (remote_paths=%s)' % (json_descr, remote_paths) cfgdict['description'] = '%s (remote_paths=%s)' % (json_descr, remote_paths)
with open(join(getGeneralConfig()['confdir'], 'sea', json_descr + '.json')) as fp: with open(join(confdir, 'sea', json_descr + '.json')) as fp:
obj, descr = json.load(fp) sea_object, descr = json.load(fp)
remote_paths = remote_paths.split() remote_paths = remote_paths.split()
self.sea_object = obj
if remote_paths: if remote_paths:
result = [] result = []
for rpath in remote_paths: for rpath in remote_paths:
include = True include = True
for paramdesc in descr: for paramdesc in descr:
if paramdesc.get('visibility', 1) > visibility_level:
continue
path = paramdesc['path'] path = paramdesc['path']
if paramdesc.get('visibility', 1) > visibility_level:
if not path.endswith('is_running'):
continue
sub = path.split('/', 1) sub = path.split('/', 1)
if rpath == '.': # take all except subpaths with readonly node at top if rpath == '.': # take all except subpaths with readonly node at top
if len(sub) == 1: if len(sub) == 1:
@ -326,8 +328,9 @@ class SeaModule(Module):
main = '' main = ''
else: # take all else: # take all
main = '' main = ''
self.path2param = {} path2param = {}
accessibles = {} parameters = {}
attributes = dict(sea_object=sea_object, path2param=path2param, parameters=parameters)
for paramdesc in descr: for paramdesc in descr:
path = paramdesc['path'] path = paramdesc['path']
readonly = paramdesc.get('readonly', True) readonly = paramdesc.get('readonly', True)
@ -350,43 +353,61 @@ class SeaModule(Module):
# flatten path to parameter name # flatten path to parameter name
for i in reversed(range(len(pathlist))): for i in reversed(range(len(pathlist))):
key = '_'.join(pathlist[i:]) key = '_'.join(pathlist[i:])
if not key in self.accessibles: if not key in cls.accessibles:
break break
if key == 'is_running': if key == 'is_running':
kwds['export'] = False kwds['export'] = False
self.path2param['/'.join(['', obj] + pathlist)] = (name, key) path2param['/'.join(['', sea_object] + pathlist)] = (name, key)
if key in self.accessibles: if key in cls.accessibles:
pobj = Override(**kwds).apply(self.accessibles[key]) if key == 'target':
kwds['readonly'] = False
pobj = Override(**kwds)
datatype = kwds.get('datatype', cls.accessibles[key].datatype)
else: else:
pobj = Parameter(**kwds) pobj = Parameter(**kwds)
accessibles[key] = pobj datatype = pobj.datatype
parameters[key] = pobj
if not hasattr(self, 'read_' + key): if not hasattr(cls, 'read_' + key):
def rfunc(module=self, cmd='hval %s' % path): def rfunc(self, cmd='hval /sics/%s/%s' % (sea_object, path)):
print('READ', cmd) print('READ', cmd)
module._iodev.request(cmd) reply = self._iodev.request(cmd)
print('REPLY', reply)
if reply.startswith('ERROR: '):
raise HardwareError(reply.split(' ', 1)[1])
try:
reply = float(reply)
except ValueError:
pass
# an updateEvent will be handled before above returns # an updateEvent will be handled before above returns
return Done return reply
setattr(self, 'read_' + key, rfunc) attributes['read_' + key] = rfunc
if not (readonly or hasattr(self, 'write_' + key)): if not (readonly or hasattr(cls, 'write_' + key)):
# pylint wrongly complains 'Cell variable pobj defined in loop' # pylint wrongly complains 'Cell variable pobj defined in loop'
# pylint: disable=cell-var-from-loop # pylint: disable=cell-var-from-loop
def wfunc(value, module=self, datatype=pobj.datatype, command=paramdesc['cmd']): def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
# TODO: convert to valid tcl data # TODO: convert to valid tcl data
cmd = "%s %s" % (command, datatype.export_value(value)) cmd = "%s %s" % (command, datatype.export_value(value))
print('WRITE', cmd) print('WRITE', cmd)
module._iodev.request(cmd) self._iodev.request(cmd)
# an updateEvent will be handled before above returns # an updateEvent will be handled before above returns
return Done return Done
setattr(self, 'write_' + key, wfunc) attributes['write_' + key] = wfunc
# create standard parameters like value and status, if not yet there # create standard parameters like value and status, if not yet there
for pname, pobj in self.accessibles.items(): for pname, pobj in cls.accessibles.items():
if pname not in accessibles and isinstance(pobj, Parameter) and pname != 'pollinterval': if pname == 'pollinterval':
accessibles[pname] = Override(poll=False, needscfg=False).apply(pobj) parameters[pname] = Override(export=False)
self.accessibles = accessibles elif pname not in parameters and isinstance(pobj, Parameter):
parameters[pname] = Override(poll=False, needscfg=False)
classname = '%s_%s' % (cls.__name__, sea_object)
newcls = ModuleMeta.__new__(ModuleMeta, classname, (cls,), attributes)
return Module.__new__(newcls)
def __init__(self, name, logger, cfgdict, dispatcher):
Module.__init__(self, name, logger, cfgdict, dispatcher)
def updateEvent(self, module, parameter, value, timestamp, readerror): def updateEvent(self, module, parameter, value, timestamp, readerror):
upd = getattr(self, 'update_' + parameter, None) upd = getattr(self, 'update_' + parameter, None)
@ -450,7 +471,6 @@ class SeaDrivable(SeaModule, Drivable):
def write_target(self, value): def write_target(self, value):
self._iodev.request('run %s %s' % (self.sea_object, value)) self._iodev.request('run %s %s' % (self.sea_object, value))
#self.status = [self.Status.BUSY, 'driving'] #self.status = [self.Status.BUSY, 'driving']
print('TARGET', self.status)
return value return value
def update_status(self, value, timestamp, readerror): def update_status(self, value, timestamp, readerror):