diff --git a/secop/lib/__init__.py b/secop/lib/__init__.py index d6cd24e..d777640 100644 --- a/secop/lib/__init__.py +++ b/secop/lib/__init__.py @@ -71,6 +71,9 @@ class GeneralConfig: if configfile is None: configfile = environ.get('FRAPPY_CONFIG_FILE', path.join(cfg['confdir'], 'generalConfig.cfg')) + configfile = path.expanduser(configfile) + if not path.exists(configfile): + raise FileNotFoundError(configfile) if configfile and path.exists(configfile): parser = ConfigParser() parser.optionxform = str diff --git a/secop_psi/sea.py b/secop_psi/sea.py index a6cbe03..d15aace 100644 --- a/secop_psi/sea.py +++ b/secop_psi/sea.py @@ -40,8 +40,9 @@ from os.path import expanduser, join, exists from secop.client import ProxyClient from secop.datatypes import ArrayOf, BoolType, \ EnumType, FloatRange, IntRange, StringType -from secop.errors import ConfigError, HardwareError, secop_error, NoSuchModuleError -from secop.lib import getGeneralConfig, mkthread, formatExtendedStack +from secop.errors import ConfigError, HardwareError, secop_error, NoSuchModuleError, \ + CommunicationFailedError +from secop.lib import generalConfig, mkthread, formatExtendedStack from secop.lib.asynconn import AsynConn, ConnectionClosed from secop.modules import Attached, Command, Done, Drivable, \ Module, Parameter, Property, Readable, Writable @@ -73,7 +74,7 @@ SERVICE_NAMES = { } SEA_DIR = expanduser('~/sea') -for confdir in getGeneralConfig()['confdir'].split(os.pathsep): +for confdir in generalConfig.confdir.split(os.pathsep): seaconfdir = join(confdir, 'sea') if exists(seaconfdir): break @@ -108,6 +109,7 @@ class SeaClient(ProxyClient, Module): service = Property("main/stick/addons", StringType(), default='') visibility = 'expert' default_json_file = {} + _connect_thread = None def __init__(self, name, log, opts, srv): instance = srv.node_cfg['name'].rsplit('_', 1)[0] @@ -123,8 +125,8 @@ class SeaClient(ProxyClient, Module): config = opts.get('config') if config: self.default_json_file[name] = config.split()[0] + '.json' - self.io = None - self.asyncio = None + self.syncio = None + self.asynio = None ProxyClient.__init__(self) Module.__init__(self, name, log, opts, srv) @@ -133,42 +135,58 @@ class SeaClient(ProxyClient, Module): self.path2param.update(module.path2param) self.register_callback(module.name, module.updateEvent) - def startModule(self, started_callback): - mkthread(self._connect, started_callback) + def startModule(self, start_events): + super().startModule(start_events) + self._connect_thread = mkthread(self._connect, start_events.get_trigger()) def _connect(self, started_callback): if '//' not in self.uri: self.uri = 'tcp://' + self.uri - self.asyncio = AsynConn(self.uri) - assert self.asyncio.readline() == b'OK' - self.asyncio.writeline(b'Spy 007') - assert self.asyncio.readline() == b'Login OK' + 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) + for _ in range(2): + self.asynio.writeline(b'Spy 007') + reply = self.asynio.readline() + if reply == b'Login OK': + break + else: + raise CommunicationFailedError('reply %r should be "Login OK"' % reply) self.request('frappy_config %s %s' % (self.service, self.config)) # frappy_async_client switches to the json protocol (better for updates) - self.asyncio.writeline(b'frappy_async_client') - self.asyncio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) + self.asynio.writeline(b'frappy_async_client') + self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) + self._connect_thread = None mkthread(self._rxthread, started_callback) def request(self, command): """send a request and wait for reply""" with self._write_lock: - if not self.io or not self.io.connection: - if not self.asyncio or not self.asyncio.connection: + if not self.syncio or not self.syncio.connection: + if not self.asynio or not self.asynio.connection: + try: + self._connect_thread.join() + except AttributeError: + pass 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' + 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.io.flush_recv() + self.syncio.flush_recv() # print('> %s' % command) - self.io.writeline(('fulltransact %s' % command).encode()) + self.syncio.writeline(('fulltransact %s' % command).encode()) result = None deadline = time.time() + 10 while time.time() < deadline: try: - reply = self.io.readline() + reply = self.syncio.readline() if reply is None: continue except ConnectionClosed: @@ -197,7 +215,7 @@ class SeaClient(ProxyClient, Module): def _rxthread(self, started_callback): while not self.shutdown: try: - reply = self.asyncio.readline() + reply = self.asynio.readline() if reply is None: continue except ConnectionClosed: @@ -252,7 +270,7 @@ class SeaClient(ProxyClient, Module): if path == '/device/changetime': result = self.request('check_config %s %s' % (self.service, self.config)) if result == '1': - self.asyncio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) + self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) else: self.DISPATCHER.shutdown() elif path.startswith('/device/frappy_%s' % self.service) and value == '': @@ -293,7 +311,7 @@ class SeaClient(ProxyClient, Module): class SeaConfigCreator(SeaClient): def startModule(self, started_callback): """save objects (and sub-objects) description and exit""" - self._connect(lambda: None) + self._connect(None) reply = self.request('describe_all') reply = ''.join('' if line.startswith('WARNING') else line for line in reply.split('\n')) description, reply = json.loads(reply) @@ -369,7 +387,6 @@ def get_datatype(paramdesc): class SeaModule(Module): io = Attached() - # pollerClass=None path2param = None sea_object = None hdbpath = None # hdbpath for main writable @@ -457,6 +474,7 @@ class SeaModule(Module): if issubclass(cls, SeaWritable): paramdesc = params[0] 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'])) paramdesc['key'] = 'target' @@ -476,10 +494,12 @@ 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, poll=False, readonly=readonly) + needscfg=False, readonly=readonly) if kwds['datatype'] is None: kwds.update(visibility=3, default='', datatype=StringType()) pathlist = path.split('/') if path else [] @@ -531,6 +551,7 @@ class SeaModule(Module): # an updateEvent will be handled before above returns return reply + rfunc.poll = False attributes['read_' + key] = rfunc if not readonly: @@ -555,13 +576,11 @@ class SeaModule(Module): attributes[pname] = pobj pobj.__set_name__(cls, pname) elif pname not in attributes and isinstance(pobj, Parameter): - pobj.poll = False pobj.needscfg = False attributes[pname] = pobj pobj.__set_name__(cls, pname) classname = '%s_%s' % (cls.__name__, sea_object) - attributes['pollerClass'] = None newcls = type(classname, (cls,), attributes) return Module.__new__(newcls) @@ -592,6 +611,9 @@ class SeaModule(Module): self.io.register_obj(self, self.sea_object) super().initModule() + def doPoll(self): + pass + class SeaReadable(SeaModule, Readable):