From 746df2eb94f65d069a2e665b723cb9fec749e636 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 17 Feb 2023 07:25:37 +0100 Subject: [PATCH] improve online help of frappy-cli - help text shown exactly once (even with no or more arguments) - automatically generated client object names + stay in interactive mode even when not all clients succeded Change-Id: Iefcac66df92f47363e43bc9b97bb2082f153e5df Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30583 Tested-by: Jenkins Automated Tests Reviewed-by: Alexander Zaft Reviewed-by: Enrico Faulhaber Reviewed-by: Markus Zolliker --- bin/frappy-cli | 43 ++++++++++++++++++++-- frappy/client/interactive.py | 71 +++++++++++++++++++++++------------- 2 files changed, 84 insertions(+), 30 deletions(-) diff --git a/bin/frappy-cli b/bin/frappy-cli index fc1f8e2..c929ff2 100755 --- a/bin/frappy-cli +++ b/bin/frappy-cli @@ -18,7 +18,6 @@ # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Module authors: -# Alexander Lenz # Markus Zolliker # # ***************************************************************************** @@ -34,7 +33,43 @@ sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..'))) from frappy.client.interactive import Client, watch -for node in sys.argv[1:]: - Client(node) +_USAGE = """ +Usage: +%s +# for all SECoP modules objects are created in the main namespace -code.interact(banner='', local=sys.modules['__main__'].__dict__) + # list all parameters +. = # change parameter +() # set target and wait until not busy + # 'status' and 'value' changes are shown every 1 sec +%s.mininterval = 0.2 # change minimal update interval to 0.2 sec (default is 1 second) + +watch(T) # watch changes of T.status and T.value (stop with ctrl-C) +watch(T='status target') # watch status and target parameters +watch(io, T=True) # watch io and all parameters of T +""" + +_CLIENT_USAGE = """ +c = Client('localhost:5000') +""" + +Client.show_usage = False + +if len(sys.argv) < 2: + _usage_args = ("\ncli = Client('localhost:5000')\n", 'cli') + success = True +else: + _usage_args = ('', '_c0') + success = False + +for _idx, _node in enumerate(sys.argv[1:]): + _client_name = '_c%d' % _idx + try: + setattr(sys.modules['__main__'], _client_name, Client(_node, name=_client_name)) + success = True + except Exception as e: + print(repr(e)) + +if success: + print(_USAGE % _usage_args) + code.interact(banner='', local=sys.modules['__main__'].__dict__) diff --git a/frappy/client/interactive.py b/frappy/client/interactive.py index 0d45a7c..92d257c 100644 --- a/frappy/client/interactive.py +++ b/frappy/client/interactive.py @@ -19,17 +19,8 @@ # Markus Zolliker # # ***************************************************************************** -"""simple interactive python client""" +"""simple interactive python client -import sys -import time -import re -from queue import Queue -from frappy.client import SecopClient -from frappy.errors import SECoPError -from frappy.datatypes import get_datatype - -USAGE = """ Usage: from frappy.client.interactive import Client @@ -48,12 +39,22 @@ watch(T='status target') # watch status and target parameters watch(io, T=True) # watch io and all parameters of T """ +import sys +import time +import re +from queue import Queue +from frappy.client import SecopClient +from frappy.errors import SECoPError +from frappy.datatypes import get_datatype + main = sys.modules['__main__'] LOG_LEVELS = {'debug', 'comlog', 'info', 'warning', 'error', 'off'} class Logger: + show_time = False + def __init__(self, loglevel='info'): func = self.noop for lev in 'debug', 'info', 'warning', 'error': @@ -63,12 +64,15 @@ class Logger: self._minute = 0 def emit(self, fmt, *args, **kwds): - now = time.time() - minute = now // 60 - if minute != self._minute: - self._minute = minute - print(time.strftime('--- %H:%M:%S ---', time.localtime(now))) - print('%6.3f' % (now % 60.0), str(fmt) % args) + if self.show_time: + now = time.time() + minute = now // 60 + if minute != self._minute: + self._minute = minute + print(time.strftime('--- %H:%M:%S ---', time.localtime(now))) + print('%6.3f' % (now % 60.0), str(fmt) % args) + else: + print(str(fmt) % args) @staticmethod def noop(fmt, *args, **kwds): @@ -200,7 +204,6 @@ class Module: return self.read() self.target = target # this sets self._running type(self).value.prev = None # show at least one value - show_final_value = True try: while self._running.get(): self._watch_parameter(self._name, 'value', mininterval=self._secnode.mininterval) @@ -210,7 +213,7 @@ class Module: self._running = None self._watch_parameter(self._name, 'status') self._secnode.readParameter(self._name, 'value') - self._watch_parameter(self._name, 'value', forced=show_final_value) + self._watch_parameter(self._name, 'value', forced=True) return self.value def __repr__(self): @@ -319,29 +322,36 @@ class Client(SecopClient): secnodes = {} mininterval = 1 - def __init__(self, uri, loglevel='info'): + def __init__(self, uri, loglevel='info', name=''): # remove previous client: prev = self.secnodes.pop(uri, None) + log = Logger(loglevel) + removed_modules = [] if prev: - prev.log.info('remove previous client to %s', uri) + log.info('remove previous client to %s', uri) for modname in prev.modules: prevnode = getattr(getattr(main, modname, None), '_secnode', None) if prevnode == prev: - prev.log.info('remove previous module %s', modname) + removed_modules.append(modname) delattr(main, modname) prev.disconnect() self.secnodes[uri] = self - super().__init__(uri, Logger(loglevel)) + if name: + log.info('\n>>> %s = Client(%r)', name, uri) + super().__init__(uri, log) self.connect() + created_modules = [] + skipped_modules = [] for modname, moddesc in self.modules.items(): prev = getattr(main, modname, None) if prev is None: - self.log.info('create module %s', modname) + created_modules.append(modname) else: if getattr(prev, '_secnode', None) is None: - self.log.error('skip module %s overwriting a global variable' % modname) + skipped_modules.append(modname) continue - self.log.info('overwrite module %s', modname) + removed_modules.append(modname) + created_modules.append(modname) attrs = {} for pname, pinfo in moddesc['parameters'].items(): attrs[pname] = Param(pname, pinfo['datainfo']) @@ -352,8 +362,14 @@ class Client(SecopClient): self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update) self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update) setattr(main, modname, mobj) + if removed_modules: + self.log.info('removed modules: %s', ' '.join(removed_modules)) + if skipped_modules: + self.log.info('skipped modules overwriting globals: %s', ' '.join(skipped_modules)) + if created_modules: + self.log.info('created modules: %s', ' '.join(created_modules)) self.register_callback(None, self.unhandledMessage) - self.log.info('%s', USAGE) + log.show_time = True def unhandledMessage(self, action, ident, data): """handle logging messages""" @@ -365,3 +381,6 @@ class Client(SecopClient): return self.log.info('module %s not found', modname) self.log.info('unhandled: %s %s %r', action, ident, data) + + def __repr__(self): + return 'Client(%r)' % self.uri