From 958e472f3bae37ab381f9277b4224f3df6774b8e Mon Sep 17 00:00:00 2001 From: l_samenv Date: Wed, 19 Mar 2025 08:14:06 +0100 Subject: [PATCH 01/45] rework of the server side main change: the same server may be used for several instruments - client classes are 'Interactors' dealing with the parameters etc. - methods from history are added to the clients + improvements on the js client side --- base.py | 69 +++++- client/jsFiles/SEAWebClientCommunication.js | 14 +- client/jsFiles/SEAWebClientGraphics.js | 8 +- client/jsFiles/SEAWebClientMain.js | 34 ++- influxgraph.py | 241 +++++++++++--------- secop-webserver | 46 +++- secop.py | 165 ++++++-------- webserver.py | 160 ++++++++----- 8 files changed, 445 insertions(+), 292 deletions(-) diff --git a/base.py b/base.py index d8bf5f6..1fd0f81 100644 --- a/base.py +++ b/base.py @@ -1,6 +1,6 @@ import sys import time -import logging +import uuid ONEYEAR = 366 * 24 * 3600 @@ -35,16 +35,63 @@ class Logger(object): pass -class Instrument: +class HandlerBase: def __init__(self): - self.clients = {} + self.handlers = {k[2:]: getattr(self, k) for k in dir(type(self)) if k.startswith('w_')} - def remove(self, client): - try: - del self.clients[client.id] - except KeyError: - logging.warning('client already removed %s', client.id) - def register(self, client): - self.clients[client.id] = client - return client +class Client(HandlerBase): + def __init__(self, server, streams, instrument_name, device_name): + super().__init__() + self.id = uuid.uuid4().hex[0:15] + self.nodes = {} + self.node_map = {} + if streams: + for uri in streams: + urisplit = uri.rsplit('://') + kind = urisplit[0] if len(urisplit) == 2 else 'secop' + node = server.interactor_classes[kind](uri, self.node_map) + self.nodes[uri] = node + self.server = server + self.instrument_name = instrument_name + self.device_name = device_name # do not know if this is needed + self.updates = {} + + def poll(self): + updates = sum((n.get_updates() for n in self.nodes.values()), start=[]) + result = [dict(type='update', updates=updates)] if updates else [] + graph_updates = self.handlers.get('graphpoll', object)() + if graph_updates: + result.append(graph_updates) + return result + + def w_getblock(self, path): + path = path.split(',')[-1] # TODO: why this? + if path == "main": # TODO: change to "-main-"? + components = [] + for node in self.nodes.values(): + node.add_main_components(components) + return dict(type='draw', path='main', title='modules', components=components) + node = self.node_map[path] + return dict(type='draw', path=path, title=path, components=node.get_components(path)) + + def w_updateblock(self, path): + if path == 'main': # TODO: change to "-main-"? + for node in self.nodes.values(): + node.update_main() + else: + node = self.node_map[path] + node.update_params(path) + return dict(type='accept-block') + + def w_console(self): # TODO: check if still used + return dict(type='accept-console') + + def w_sendcommand(self, command): + for node in self.nodes.values(): + if node.handle_command(command): + break + return dict(type='accept-command') + + def info(self): + return ["na"] diff --git a/client/jsFiles/SEAWebClientCommunication.js b/client/jsFiles/SEAWebClientCommunication.js index 8bd5bf4..9156102 100644 --- a/client/jsFiles/SEAWebClientCommunication.js +++ b/client/jsFiles/SEAWebClientCommunication.js @@ -17,9 +17,9 @@ function buildUpdateConnection() { // Establishes server-sent-event-connection, which is used for all sorts of // updates and exists as long as the client is running. - // Executed at programstart (see also SEAWebClientMain.js). + // Executed at program start (see also SEAWebClientMain.js). - var path = "http://" + hostPort + "/update"; + var path = "http://" + hostPort + "/update?" + window.clientTags; if (debugCommunication) { console.log("%cto server (SSE): " + path, "color:white;background:lightblue"); @@ -29,8 +29,7 @@ function buildUpdateConnection() { var src = new EventSource(path); } catch (e) { console.log(e) - alertify.prompt( - "NETWORK ERROR", + alertify.prompt("NETWORK ERROR", "Failed to establish connection to data-server at the given address!" + "Try to enter HOST and PORT of the data-server manually!", hostPort, function(evt, value) { @@ -50,9 +49,7 @@ function buildUpdateConnection() { src.onerror = function(e) { console.log(e); console.log('EVTSRC error') - alertify - .prompt( - "NETWORK ERROR", + alertify.prompt("NETWORK ERROR", "Failed to establish connection to data-server at the given address!" + "Try to enter HOST and PORT of the data-server manually!", hostPort, function(evt, value) { @@ -406,10 +403,9 @@ function successHandler(s, message) { begin = timeRange[0] - timeRange[1]; select.value = begin; // Server-request for variable-list.*/ - console.log('TIME', window['clientTags'], timeRange) + console.log('TIME', timeRange) reqJSONPOST(0, "http://" + hostPort + "/getvars", "time=" + timeRange[0] + ',' + timeRange[1] - + window['clientTags'] + "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage()) + "&id=" + clientID, successHandler, errorHandler); break; diff --git a/client/jsFiles/SEAWebClientGraphics.js b/client/jsFiles/SEAWebClientGraphics.js index 572a228..c2bcc97 100644 --- a/client/jsFiles/SEAWebClientGraphics.js +++ b/client/jsFiles/SEAWebClientGraphics.js @@ -490,7 +490,7 @@ let graphs = (function (){ if(idx != -1){ //if the clicked block is displayed somewhere, we create a selection window createSelection(idx); // We will create a selection at the gindex } - } + } createGraph(gindex, block); // we create at the current shown selector (gindex), the graph corresponding to the one clicked (block) }) selection.appendChild(bel); @@ -547,7 +547,7 @@ let graphs = (function (){ + "&variables=" + varlist + "&interval=" + resolution + "&id=" + clientID).getJSON().then(function(data){ - //console.log('Graph', block, data) + // console.log('Graph', block, data); let graph = new Graph(gindex, graph_elm, "Time", block.unit, block.tag, type); graph_array[gindex] = graph; @@ -559,7 +559,6 @@ let graphs = (function (){ for(let e of data.graph[key]){ pdata.push({x: e[0]*1000, y: e[1]}); } - addDataset(gindex, key, pdata, dict[key]) // if(pdata.length > 0){ // addDataset(gindex, key, pdata, dict[key]) @@ -919,7 +918,6 @@ let graphs = (function (){ AJAX("http://" + hostPort + "/getvars").postForm( "time=" + msRightTimestampGetVars/1000 - + window['clientTags'] + "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage()) + "&id="+ clientID).then(function(data){ blocks = data.blocks; @@ -998,7 +996,6 @@ let graphs = (function (){ AJAX("http://" + hostPort + "/getvars").postForm( "time=" + msRightTimestamp/1000 + "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage()) - + window['clientTags'] + "&id="+ clientID).then(function(data){ currentMaxTime = msRightTimestamp + 60000; currentMinTime = msLeftTimestamp; @@ -1304,7 +1301,6 @@ let graphs = (function (){ AJAX("http://" + hostPort + "/getvars").postForm( "time=" + currentMaxTime/1000 + "&userconfiguration=" + JSON.stringify(userConfiguration) - + window['clientTags'] + "&id="+ clientID).then(function(data){ blocks = data.blocks; document.getElementById("device").innerHTML = data.device diff --git a/client/jsFiles/SEAWebClientMain.js b/client/jsFiles/SEAWebClientMain.js index 52f5e2a..e3724b7 100644 --- a/client/jsFiles/SEAWebClientMain.js +++ b/client/jsFiles/SEAWebClientMain.js @@ -91,18 +91,21 @@ new Settings() .treat("hideRightPart", "hr", to_bool, false) //used to completely disable the right part .treat("wideGraphs", "wg", to_bool, false) //used to toggle the size of the graphs part .treat("showAsync", "sa", to_bool, false) - .treat("device", "dev", 0, "*") - .treat("server", "srv", 0, "*") + .treat("device", "dev", 0, "") + .treat("server", "srv", 0, "") .treat("instrument", "instrument", 0, "") .treat("timerange", "time", 0, "-1800,0") -if (window['instrument']) { - window['clientTags'] = "&instrument=" + window["instrument"]; +if (window.instrument) { + window.clientTags = "&instrument=" + window.instrument; } else { - window['clientTags'] = "&stream=" + window["server"] + "&device=" + window["device"]; + let args = ''; + if (window.server) { args += "&stream=" + window.server; } + if (window.device) { args += "&device=" + window.device; } + window.clientTags = args; } -console.log('TAGS', window['clientTags']); +console.log('TAGS', window.clientTags); function loadFirstBlocks() { if (debug_main_daniel) { @@ -112,11 +115,19 @@ function loadFirstBlocks() { if (showMain) pushInitCommand("getblock?path=main&", "main") if (showConsole) pushInitCommand("console?", "console") if (nColumns == 1) { // probably mobile phone} - if (showGraphics) pushInitCommand("gettime?time=" + window["timerange"] + "&", "graphics") + if (showGraphics) pushInitCommand("gettime?time=" + window.timerange + "&", "graphics") if (showOverview) pushInitCommand("getblock?path=_overview&", "overview") + var goFS = document.getElementById('header'); + goFS.addEventListener( + 'click', + function () { + document.body.requestFullscreen(); + }, + false, + ); } else { if (showOverview) pushInitCommand("getblock?path=_overview&", "overview") - if (showGraphics) pushInitCommand("gettime?time=" + window["timerange"] + "&", "graphics") + if (showGraphics) pushInitCommand("gettime?time=" + window.timerange + "&", "graphics") // last is shown first } } @@ -169,7 +180,7 @@ window.onload = function() { let crossElement = document.getElementById("close-cross"); - if(window["hideRightPart"]){ + if(window.hideRightPart){ document.body.removeChild(crossElement); }else{ crossElement.onclick = function(){ @@ -193,7 +204,7 @@ window.onload = function() { elements[2].style.display = "none"; // hide parameters } }else{ // else it toggles the graphs window's size and triggers the adjustGrid() - window["wideGraphs"] = !window['wideGraphs']; + window.wideGraphs = ! window.wideGraphs; adjustGrid(); } } @@ -207,9 +218,8 @@ window.onload = function() { // var homeButton = document.getElementById("home-icon"); - // TODO : uncomment this code with the right URL to navigate to when the way to select the instrument will be decided. homeButton.onclick = function () { - window.location = "/select"; + window.location = "/select_experiment"; }; buildUpdateConnection(); // if (location.hash) { diff --git a/influxgraph.py b/influxgraph.py index d57254d..9bbf008 100644 --- a/influxgraph.py +++ b/influxgraph.py @@ -3,20 +3,19 @@ import logging import json import io import uuid -from configparser import ConfigParser +# from configparser import ConfigParser from math import ceil -from sehistory.seinflux import SEHistory +from sehistory.seinflux import fmtime from colors import assign_colors_to_curves from chart_config import ChartConfig -from base import Instrument, get_abs_time -from secop import SecopClient, SecopInstrument +from base import get_abs_time, HandlerBase def split_tags(tags): return {k: v.split(',') for k, v in tags.items()} -class InfluxGraph: +class InfluxGraph(HandlerBase): """Class implementing the logic of the different routes that are called by the client to retrieve graph data with InfluxDB. Global constants : @@ -43,22 +42,37 @@ class InfluxGraph: ACTUAL = 1 LIVE = 2 - def __init__(self, instrument): - self.instrument = instrument - self.db = instrument.db - instrument_name = instrument.title + def __init__(self, server, instrument, device_name, tags): + """create instance for retrieving history + + :param db: a database client (SEInflux instance) + :param instrument: the name of anm instrument or None + :param streams: a stream or comma separated list of streams + :param devices: a device name ar a comma separated list of devices + :param device_name: (comma separated) device name for labelling + typically only one of the 3 last parameters are needed + if more are specified, all of them must be fulfilled + """ + super().__init__() # put methods w_... to handlers + self.handlers['graphpoll'] = self.graphpoll + self.server = server + self.db = server.db # self.influx_data_getter = influx_data_getter self.chart_configs = [ChartConfig("./config/generic.ini")] - try: - self.chart_configs.append(ChartConfig(f"./config/{instrument_name}.ini")) - except KeyError: - pass + self.instrument = instrument + self.device_name = device_name + if instrument: # TODO: should it not be better to have inifiles per device? + try: + self.chart_configs.append(ChartConfig(f"./config/{instrument}.ini")) + except KeyError: + pass self.livemode = self.HISTORICAL self.last_values = {} # dict of last known point (