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.lib import mkthread, getGeneralConfig
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.client import ProxyClient
from secop.protocol.dispatcher import make_update
@ -54,7 +54,6 @@ description = %(samenv)s over SEA
[seaconn]
class = secop_psi.sea.SeaClient
description = a SEA connection
uri = %(uri)s
"""
CFG_MODULE = """
@ -67,6 +66,7 @@ remote_paths = .
SEA_DIR = expanduser('~/sea')
confdir = getGeneralConfig()['confdir'].split(':', 1)[0]
def get_sea_port(instance):
@ -143,14 +143,14 @@ class SeaClient(ProxyClient, Module):
def request(self, command):
"""send a request and wait for reply"""
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)
assert self.io.readline() == b'OK'
self.io.writeline(b'seauser seaser')
assert self.io.readline() == b'Login OK'
# do this before changing to user rights (avoid cluttering log)
#self.io.writeline(b'protocol set json')
#self.io.writeline(b'config rights seauser seaser')
self.io.flush_recv()
self.io.writeline(('fulltransact %s' % command).encode())
result = None
deadline = time.time() + 10
@ -170,7 +170,6 @@ class SeaClient(ProxyClient, Module):
print('missing TRANSACTIONSTART on: %s' % command)
return ''
if not result:
print('missing return value on: %s' % command)
return ''
return '\n'.join(result)
if result is None:
@ -203,7 +202,7 @@ class SeaClient(ProxyClient, Module):
continue
else:
continue
data = {'error %s' % path: readerror.replace('ERROR: ', '')}
data = {'%s.geterror' % path: readerror.replace('ERROR: ', '')}
obj = None
flag = 'hdbevent'
else:
@ -217,22 +216,30 @@ class SeaClient(ProxyClient, Module):
started_callback = None
continue
if flag != 'hdbevent':
if obj != 'protocol':
print('SKIP', msg)
continue
if data is None:
continue
now = time.time()
for path, value in data.items():
readerror = None
if path.startswith('error '):
if path.endswith('.geterror'):
if value:
readerror = HardwareError(value)
path = path.rsplit('.', 1)[0]
value = None
path = path[6:]
try:
module, param = self.path2param[path]
self.updateValue(module, param, value, time.time(), readerror)
except KeyError:
# 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):
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'))
samenv, reply = json.loads(reply)
samenv = samenv.replace('/', '_')
confdir = getGeneralConfig()['confdir']
result = []
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:
content = json.dumps([obj, descr]).replace('}, {', '},\n{')
with open(join(confdir, 'sea', filename + '.json'), 'w') as fp:
@ -290,28 +296,24 @@ class SeaModule(Module):
path2param = None
sea_object = None
def __init__(self, name, logger, cfgdict, dispatcher):
self.buildParams(cfgdict, name)
Module.__init__(self, name, logger, cfgdict, dispatcher)
def buildParams(self, cfgdict, name):
def __new__(cls, name, logger, cfgdict, dispatcher):
visibility_level = cfgdict.pop('visibility_level', 2)
json_descr = cfgdict.pop('json_descr')
remote_paths = cfgdict.pop('remote_paths', '')
if 'description' not in cfgdict:
cfgdict['description'] = '%s (remote_paths=%s)' % (json_descr, remote_paths)
with open(join(getGeneralConfig()['confdir'], 'sea', json_descr + '.json')) as fp:
obj, descr = json.load(fp)
with open(join(confdir, 'sea', json_descr + '.json')) as fp:
sea_object, descr = json.load(fp)
remote_paths = remote_paths.split()
self.sea_object = obj
if remote_paths:
result = []
for rpath in remote_paths:
include = True
for paramdesc in descr:
if paramdesc.get('visibility', 1) > visibility_level:
continue
path = paramdesc['path']
if paramdesc.get('visibility', 1) > visibility_level:
if not path.endswith('is_running'):
continue
sub = path.split('/', 1)
if rpath == '.': # take all except subpaths with readonly node at top
if len(sub) == 1:
@ -326,8 +328,9 @@ class SeaModule(Module):
main = ''
else: # take all
main = ''
self.path2param = {}
accessibles = {}
path2param = {}
parameters = {}
attributes = dict(sea_object=sea_object, path2param=path2param, parameters=parameters)
for paramdesc in descr:
path = paramdesc['path']
readonly = paramdesc.get('readonly', True)
@ -350,43 +353,61 @@ class SeaModule(Module):
# flatten path to parameter name
for i in reversed(range(len(pathlist))):
key = '_'.join(pathlist[i:])
if not key in self.accessibles:
if not key in cls.accessibles:
break
if key == 'is_running':
kwds['export'] = False
self.path2param['/'.join(['', obj] + pathlist)] = (name, key)
if key in self.accessibles:
pobj = Override(**kwds).apply(self.accessibles[key])
path2param['/'.join(['', sea_object] + pathlist)] = (name, key)
if key in cls.accessibles:
if key == 'target':
kwds['readonly'] = False
pobj = Override(**kwds)
datatype = kwds.get('datatype', cls.accessibles[key].datatype)
else:
pobj = Parameter(**kwds)
accessibles[key] = pobj
if not hasattr(self, 'read_' + key):
def rfunc(module=self, cmd='hval %s' % path):
datatype = pobj.datatype
parameters[key] = pobj
if not hasattr(cls, 'read_' + key):
def rfunc(self, cmd='hval /sics/%s/%s' % (sea_object, path)):
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
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: 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
cmd = "%s %s" % (command, datatype.export_value(value))
print('WRITE', cmd)
module._iodev.request(cmd)
self._iodev.request(cmd)
# an updateEvent will be handled before above returns
return Done
setattr(self, 'write_' + key, wfunc)
attributes['write_' + key] = wfunc
# create standard parameters like value and status, if not yet there
for pname, pobj in self.accessibles.items():
if pname not in accessibles and isinstance(pobj, Parameter) and pname != 'pollinterval':
accessibles[pname] = Override(poll=False, needscfg=False).apply(pobj)
self.accessibles = accessibles
for pname, pobj in cls.accessibles.items():
if pname == 'pollinterval':
parameters[pname] = Override(export=False)
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):
upd = getattr(self, 'update_' + parameter, None)
@ -450,7 +471,6 @@ class SeaDrivable(SeaModule, Drivable):
def write_target(self, value):
self._iodev.request('run %s %s' % (self.sea_object, value))
#self.status = [self.Status.BUSY, 'driving']
print('TARGET', self.status)
return value
def update_status(self, value, timestamp, readerror):