mercury, ips, sea, triton, convergence

after gerrit

Change-Id: Iff14047ecc476589aef10c96fae9970133b8bd14
This commit is contained in:
2023-05-09 14:57:34 +02:00
parent 750b5a7794
commit 8039351395
12 changed files with 426 additions and 357 deletions

View File

@ -40,9 +40,8 @@ from os.path import expanduser, join, exists
from frappy.client import ProxyClient
from frappy.datatypes import ArrayOf, BoolType, \
EnumType, FloatRange, IntRange, StringType
from frappy.errors import ConfigError, HardwareError, secop_error, NoSuchModuleError, \
CommunicationFailedError
from frappy.lib import generalConfig, mkthread, formatExtendedStack
from frappy.errors import ConfigError, HardwareError, secop_error, CommunicationFailedError
from frappy.lib import generalConfig, mkthread
from frappy.lib.asynconn import AsynConn, ConnectionClosed
from frappy.modules import Attached, Command, Done, Drivable, \
Module, Parameter, Property, Readable, Writable
@ -62,7 +61,7 @@ Mod(%(seaconn)r,
"""
CFG_MODULE = """Mod(%(module)r,
'frappy_psi.sea.%(modcls)s',
'frappy_psi.sea.%(modcls)s', '',
io = %(seaconn)r,
sea_object = %(module)r,
)
@ -75,18 +74,18 @@ SERVICE_NAMES = {
}
SEA_DIR = expanduser('~/sea')
for confdir in generalConfig.confdir.split(os.pathsep):
seaconfdir = join(confdir, 'sea')
if exists(seaconfdir):
break
else:
seaconfdir = os.environ.get('FRAPPY_SEA_DIR')
seaconfdir = os.environ.get('FRAPPY_SEA_DIR')
if not exists(seaconfdir):
for confdir in generalConfig.confdir.split(os.pathsep):
seaconfdir = join(confdir, 'sea')
if exists(seaconfdir):
break
def get_sea_port(instance):
for filename in ('sea_%s.tcl' % instance, 'sea.tcl'):
try:
with open(join(SEA_DIR, filename)) as f:
with open(join(SEA_DIR, filename), encoding='utf-8') as f:
for line in f:
linesplit = line.split()
if len(linesplit) == 3:
@ -115,18 +114,21 @@ class SeaClient(ProxyClient, Module):
_instance = None
def __init__(self, name, log, opts, srv):
instance = srv.node_cfg['name'].rsplit('_', 1)[0]
nodename = srv.node_cfg.get('name') or srv.node_cfg.get('equipment_id')
instance = nodename.rsplit('_', 1)[0]
if 'uri' not in opts:
self._instance = instance
port = get_sea_port(instance)
if port is None:
raise ConfigError('missing sea port for %s' % instance)
opts['uri'] = 'tcp://localhost:%s' % port
opts['uri'] = {'value': 'tcp://localhost:%s' % port}
self.objects = set()
self.shutdown = False
self.path2param = {}
self._write_lock = threading.Lock()
config = opts.get('config')
if isinstance(config, dict):
config = config['value']
if config:
self.default_json_file[name] = config.split()[0] + '.json'
self.syncio = None
@ -147,14 +149,17 @@ class SeaClient(ProxyClient, Module):
def _connect(self, started_callback):
if self._instance:
if not self._service_manager:
from servicemanager import SeaManager
self._service_manager = SeaManager()
self._service_manager.do_start(self._instance)
if self._service_manager is None:
try:
from servicemanager import SeaManager # pylint: disable=import-outside-toplevel
self._service_manager = SeaManager()
except ImportError:
self._service_manager = False
if self._service_manager:
self._service_manager.do_start(self._instance)
if '//' not in self.uri:
self.uri = 'tcp://' + self.uri
self.asynio = AsynConn(self.uri)
# print('CONNECT', self.uri, self.asynio)
# print(formatExtendedStack())
reply = self.asynio.readline()
if reply != b'OK':
raise CommunicationFailedError('reply %r should be "OK"' % reply)
@ -184,13 +189,11 @@ class SeaClient(ProxyClient, Module):
pass
self._connect(None)
self.syncio = AsynConn(self.uri)
# print('SYNCIO', self.uri)
assert self.syncio.readline() == b'OK'
self.syncio.writeline(b'seauser seaser')
assert self.syncio.readline() == b'Login OK'
print('connected to %s' % self.uri)
self.log.info('connected to %s', self.uri)
self.syncio.flush_recv()
# print('> %s' % command)
ft = 'fulltransAct' if quiet else 'fulltransact'
self.syncio.writeline(('%s %s' % (ft, command)).encode())
result = None
@ -203,19 +206,18 @@ class SeaClient(ProxyClient, Module):
except ConnectionClosed:
break
reply = reply.decode()
# print('< %s' % reply)
if reply.startswith('TRANSACTIONSTART'):
result = []
continue
if reply == 'TRANSACTIONFINISHED':
if result is None:
print('missing TRANSACTIONSTART on: %s' % command)
self.log.info('missing TRANSACTIONSTART on: %s', command)
return ''
if not result:
return ''
return '\n'.join(result)
if result is None:
print('swallow: %s' % reply)
self.log.info('swallow: %s', reply)
continue
if not result:
result = [reply.split('=', 1)[-1]]
@ -234,7 +236,7 @@ class SeaClient(ProxyClient, Module):
try:
msg = json.loads(reply)
except Exception as e:
print(repr(e), reply)
self.log.warn('bad reply %r %r', e, reply)
continue
if isinstance(msg, str):
if msg.startswith('_E '):
@ -259,12 +261,12 @@ class SeaClient(ProxyClient, Module):
continue
if flag != 'hdbevent':
if obj not in ('frappy_async_client', 'get_all_param'):
print('SKIP', msg)
self.log.debug('skip %r', msg)
continue
if not data:
continue
if not isinstance(data, dict):
print('what means %r' % msg)
self.log.debug('what means %r', msg)
continue
now = time.time()
for path, value in data.items():
@ -318,7 +320,7 @@ class SeaClient(ProxyClient, Module):
class SeaConfigCreator(SeaClient):
def startModule(self, started_callback):
def startModule(self, start_events):
"""save objects (and sub-objects) description and exit"""
self._connect(None)
reply = self.request('describe_all')
@ -343,20 +345,20 @@ class SeaConfigCreator(SeaClient):
service = SERVICE_NAMES[ext]
seaconn = 'sea_' + service
cfgfile = join(seaconfdir, stripped + '_cfg.py')
with open(cfgfile, 'w') as fp:
fp.write(CFG_HEADER % dict(config=filename, seaconn=seaconn, service=service,
nodedescr=description.get(filename, filename)))
with open(cfgfile, 'w', encoding='utf-8') as fp:
fp.write(CFG_HEADER % {'config': filename, 'seaconn': seaconn, 'service': service,
'nodedescr': description.get(filename, filename)})
for obj in descr:
fp.write(CFG_MODULE % dict(modcls=modcls[obj], module=obj, seaconn=seaconn))
fp.write(CFG_MODULE % {'modcls': modcls[obj], 'module': obj, 'seaconn': seaconn})
content = json.dumps(descr).replace('}, {', '},\n{').replace('[{', '[\n{').replace('}]}, ', '}]},\n\n')
result.append('%s\n' % cfgfile)
with open(join(seaconfdir, filename + '.json'), 'w') as fp:
with open(join(seaconfdir, filename + '.json'), 'w', encoding='utf-8') as fp:
fp.write(content + '\n')
result.append('%s: %s' % (filename, ','.join(n for n in descr)))
raise SystemExit('; '.join(result))
@Command(StringType(), result=StringType())
def query(self, cmd):
def query(self, cmd, quiet=False):
"""a request checking for errors and accepting 0 or 1 line as result"""
errors = []
reply = None
@ -400,12 +402,18 @@ class SeaModule(Module):
sea_object = None
hdbpath = None # hdbpath for main writable
# pylint: disable=too-many-statements,arguments-differ,too-many-branches
def __new__(cls, name, logger, cfgdict, srv):
if hasattr(srv, 'extra_sea_modules'):
extra_modules = srv.extra_sea_modules
else:
extra_modules = {}
srv.extra_sea_modules = extra_modules
for k, v in cfgdict.items():
try:
cfgdict[k] = v['value']
except (KeyError, TypeError):
pass
json_file = cfgdict.pop('json_file', None) or SeaClient.default_json_file[cfgdict['io']]
visibility_level = cfgdict.pop('visibility_level', 2)
@ -416,25 +424,22 @@ class SeaModule(Module):
paramdesc['key'] = 'value'
if issubclass(cls, SeaWritable):
if paramdesc.get('readonly', True):
raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path']))
raise ConfigError(f"{sea_object}/{paramdesc['path']} is not writable")
params.insert(0, paramdesc.copy()) # copy value
paramdesc['key'] = 'target'
paramdesc['readonly'] = False
extra_module_set = ()
if 'description' not in cfgdict:
cfgdict['description'] = '%s@%s' % (single_module, json_file)
cfgdict['description'] = f'{single_module}@{json_file}'
else:
sea_object = cfgdict.pop('sea_object')
rel_paths = cfgdict.pop('rel_paths', '.')
if 'description' not in cfgdict:
cfgdict['description'] = '%s@%s%s' % (
name, json_file, '' if rel_paths == '.' else ' (rel_paths=%s)' % rel_paths)
name, json_file, '' if rel_paths == '.' else f' (rel_paths={rel_paths})')
# with open(join(seaconfdir, json_file + '.json')) as fp:
# sea_object, descr = json.load(fp)
with open(join(seaconfdir, json_file)) as fp:
with open(join(seaconfdir, json_file), encoding='utf-8') as fp:
content = json.load(fp)
# print(json_file, content.keys())
descr = content[sea_object]
if rel_paths == '*' or not rel_paths:
# take all
@ -478,7 +483,6 @@ class SeaModule(Module):
result.pop(0)
else:
logger.error('%s: no value found', name)
# logger.info('PARAMS %s %r', name, result)
base = descr['base']
params = descr['params']
if issubclass(cls, SeaWritable):
@ -486,14 +490,14 @@ class SeaModule(Module):
assert paramdesc['key'] == 'value'
params.append(paramdesc.copy()) # copy value?
if paramdesc.get('readonly', True):
raise ConfigError('%s/%s is not writable' % (sea_object, paramdesc['path']))
raise ConfigError(f"{sea_object}/{paramdesc['path']} is not writable")
paramdesc['key'] = 'target'
paramdesc['readonly'] = False
extra_module_set = cfgdict.pop('extra_modules', ())
if extra_module_set:
extra_module_set = set(extra_module_set.replace(',', ' ').split())
path2param = {}
attributes = dict(sea_object=sea_object, path2param=path2param)
attributes = {'sea_object': sea_object, 'path2param': path2param}
# some guesses about visibility (may be overriden in *_cfg.py):
if sea_object in ('table', 'cc'):
@ -504,12 +508,10 @@ class SeaModule(Module):
path = paramdesc['path']
readonly = paramdesc.get('readonly', True)
dt = get_datatype(paramdesc)
#print('----', sea_object)
#print(dt, paramdesc)
kwds = dict(description=paramdesc.get('description', path),
datatype=dt,
visibility=paramdesc.get('visibility', 1),
needscfg=False, readonly=readonly)
kwds = {'description': paramdesc.get('description', path),
'datatype': dt,
'visibility': paramdesc.get('visibility', 1),
'needscfg': False, 'readonly': readonly}
if kwds['datatype'] is None:
kwds.update(visibility=3, default='', datatype=StringType())
pathlist = path.split('/') if path else []
@ -555,10 +557,8 @@ class SeaModule(Module):
continue # skip this parameter
path2param.setdefault(hdbpath, []).append((name, key))
attributes[key] = pobj
# if hasattr(cls, 'read_' + key):
# print('override %s.read_%s' % (cls.__name__, key))
def rfunc(self, cmd='hval %s/%s' % (base, path)):
def rfunc(self, cmd=f'hval {base}/{path}'):
reply = self.io.query(cmd, True)
try:
reply = float(reply)
@ -571,15 +571,13 @@ class SeaModule(Module):
attributes['read_' + key] = rfunc
if not readonly:
# if hasattr(cls, 'write_' + key):
# print('override %s.write_%s' % (cls.__name__, key))
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
value = datatype.export_value(value)
if isinstance(value, bool):
value = int(value)
# TODO: check if more has to be done for valid tcl data (strings?)
cmd = "%s %s" % (command, value)
cmd = f'{command} {value}'
self.io.query(cmd)
return Done
@ -596,7 +594,7 @@ class SeaModule(Module):
attributes[pname] = pobj
pobj.__set_name__(cls, pname)
classname = '%s_%s' % (cls.__name__, name)
classname = f'{cls.__name__}_{name}'
newcls = type(classname, (cls,), attributes)
result = Module.__new__(newcls)
return result
@ -609,11 +607,9 @@ class SeaModule(Module):
try:
pobj = self.parameters[parameter]
except KeyError:
print(self.name, parameter)
self.log.error('do not know %s:%s', self.name, parameter)
raise
pobj.timestamp = timestamp
#if not pobj.readonly and pobj.value != value:
# print('UPDATE', module, parameter, value)
# should be done here: deal with clock differences
if not readerror:
try:
@ -668,7 +664,7 @@ class SeaDrivable(SeaModule, Drivable):
# return self.target
def write_target(self, value):
self.io.query('run %s %s' % (self.sea_object, value))
self.io.query(f'run {self.sea_object} {value}')
# self.status = [self.Status.BUSY, 'driving']
return value
@ -701,4 +697,4 @@ class SeaDrivable(SeaModule, Drivable):
- on stdsct drivables this will call the halt script
- on EaseDriv this will set the stopped state
"""
self.io.query('%s is_running 0' % self.sea_object)
self.io.query(f'{self.sea_object} is_running 0')