From 5cbc5be196397142ad9a986ba9c10140f8a6d828 Mon Sep 17 00:00:00 2001 From: vonka_j Date: Fri, 13 Jan 2023 14:47:01 +0000 Subject: [PATCH] Adding Graph client to extract the data from the SEA server --- src/cristallina/SEA_GraphClient.py | 191 +++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 src/cristallina/SEA_GraphClient.py diff --git a/src/cristallina/SEA_GraphClient.py b/src/cristallina/SEA_GraphClient.py new file mode 100644 index 0000000..2348847 --- /dev/null +++ b/src/cristallina/SEA_GraphClient.py @@ -0,0 +1,191 @@ +"""client for SEA GraphServer + +Usage: + +# open a client to host 'samenv', port 8764 +# the port number for the graph server may be retrived by +# the command 'sea list' on the samenv machine + +client = GraphClient('samenv:8764') + +# get one curve + +tlist, vlist, period = client.get_curves(start, end, name) + +or + +# get all important curves +curves = client.get_curves(start, end) + + +# where: + + start, end: interval (unix time, as retrieved from time.time()) + name: the name of a curve (if no name or a list of names + is given, the result is a curves dict) + curves: dict of [tlist, vlist, period] + tlist: time axis (unix time) + vlist: values (y-axis) + period: the expected resolution (a hint for graphic clients, + saying that for a time step t(n) - t(n-1) significantly + bigger than period, an additional point should be added + at t(n) - period) +""" + +import socket +import time + + +FINISH = b'\nTRANSACTIONFINISHED' +START = b'TRANSACTIONSTART' +FMIN = min(FINISH[1:]) +FMAX = max(FINISH[1:]) + + +def expect_reply(sock, expected): + while expected: + got = sock.recv(8192) + if not expected.startswith(got): + raise ValueError('expected %r but got %r' % (expected, got)) + expected = expected[len(got):] + + +def raw_sics_client(hostport, login): + if ':' in hostport: + host, port = hostport.split(':') + hostport = (host, int(port)) + sock = socket.create_connection(hostport, timeout=3) + bbuf = b'' + expect_reply(sock, b'OK\n') + sock.sendall(login.encode('latin-1') + b'\n') + expect_reply(sock, b'Login OK\n') + request = yield None + while True: + sock.sendall(b'fulltransact %s\n' % request.encode('latin-1')) + try: + reply = sock.recv(8192) + if not reply: + sock.close() + return + except socket.timeout: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + raise + before, tag, after = reply.partition(FINISH) + if tag: + result = bbuf + before + elif FMIN <= reply[0] <= FMAX: # the FINISH tag may have been cut + bbuf, tag, after = (bbuf + reply).partition(FINISH) + if not tag: + continue + result = bbuf + else: + bbuf += before + continue + bbuf = after[1:] + before, tag, result = result.rpartition(START) + if tag: + before, nl, result = result.partition(b'\n') + try: + request = yield result.decode('latin-1') + except GeneratorExit: + sock.shutdown(socket.SHUT_RDWR) + sock.close() + return + + +def sics_client(hostport, command=None, login='Spy 007'): + sics = raw_sics_client(hostport, login) + next(sics) + if command is None: + return sics + result = sics.send(command) + sics.close() + return result + + +class GraphClient: + def __init__(self, hostport): + self.sc = sics_client(hostport) + + def close(self): + self.sc.close() + + def get_raw(self, start, end, *args): + """get raw curves (values as text)""" + arglist = ' '.join(args) + try: + reply = self.sc.send(f'graph {start} {end} {arglist}') + except StopIteration: + raise ConnectionError('connection closed') + lines = reply.split('\n') + curve = None + result = {} + t = 0 + for line in lines[1:]: # skip first line + if line.startswith('*'): + spl = line[1:].split() + key = spl[0] + if key in ('0', '1'): + break + tlist = [] + vlist = [] + curve = [tlist, vlist, 1] + if len(spl) >= 3 and spl[1] == 'period': + curve[2] = float(spl[2]) + result[key] = curve + t = 0 + else: + tdif, _, value = line.partition(' ') + try: + t += float(tdif) + except ValueError: + print(lines) + tlist.append(t) + vlist.append(value) + return result + + def get_names(self, start, end=None): + """get names and properties of curves configured to be display on SEA GUI graphics""" + end = start if end is None else end + result = self.get_raw(start, end, 'text', 'vars') + curves = {} + for vlist in result['vars'][1]: # text values + for item in vlist.split(): + item = item.split('|') + curves[item[0]] = item[1:] + [''] * (4 - len(item)) + return curves + + def get_curves(self, start, end, name=None, none_value=None, nmax=None): + """get curves + + start, end: interval (unix time, as retrieved from time.time()) + non positive values are taken relative to the current time + name: a single name or a list of names or None to get all curves (as shown in the SEA GUI) + none_value: replacement when no value is defined + nmax: max. number of points per curve + + when name is a string, returns [tlist, vlist, period] + when name is None or a list of strings (names) returns a dict of [tlist, vlist, period] + + tlist: time axis (unix time) + vlist: values (y-axis) + period: the expected resolution (a hint for graphic clients) + """ + if isinstance(name, str): + names = [name] + elif name is None: + names = self.get_names(start, end) + else: # assume names is a list of strings + names = name + args = ['np', str(nmax)] + names if nmax else names + result = self.get_raw(start, end, *args) + for key, curve in result.items(): + vlist = curve[1] + for i, v in enumerate(vlist): + try: + vlist[i] = float(v) + except ValueError: + vlist[i] = none_value + return result[name] if isinstance(name, str) else result +