from math import cos from datetime import datetime import plotly.graph_objects as go import pandas as pd from dash import html, dcc import plotly.express as px # TODO DOCUMENT MORE def find_Device(name: str, devices): return [dev for dev in devices if dev.name in name] def find_Device_strict(name: str, devices): return [dev for dev in devices if dev.name == name] def now(timestamp): #return datetime.fromtimestamp(int(timestamp.split("/")[-1])).replace(microsecond=0) return datetime.now().replace(microsecond=0) #TODO CHANGE ME WHEN GETTING NEW FILES # Main class for devices used for node graphs and plots class Device: def __init__(self, name): self.name = name self.data = {} # Todo: Maybe we can remove this altogether self.graphs = [] self.built = False # Onetime token to build the graphs self.hidden = True # Careful! True => Hidden, False => Visible self.index = -1 # This ensures the index is smallest (As it normally is non-negative) self.position = [0,0] # Position 0,0 is NOT drawn self.power = [None, None] self.Buspower = [] self.connection = [] # This is used for invisible devices to draw the graph # building data from the json timestamped files to a list with the timestamp as index and all the data as dict def BuildData(self, data): values = {} for key in data[0].keys(): values.update({key: []}) for timestamp in range(0, len(data)): for key in data[timestamp].keys(): values[key].append(data[timestamp][key]) self.data = values # This builds the Graphs on app-start and gives back the divs containing said plots def BuildDiv(self, index): if self.power == [None,None]: self.UpdatePower() if not self.built: self.BuildGraphs() self.built = True self.index = index return html.Div(id='graphs' + self.name, children=[ html.Div(id={"type": "graphs", "index": self.index}, children=self.graphs)]) #, style={'display': 'inlineBlock'} def BuildChecklist(self, index): return html.Div(className="round", children=[ dcc.Checklist([{'label': [self.name.replace('_', ' ').title(), html.Br()], 'value': self.name.replace('_', ' ')}], persistence=False, id={'type': "topChecklist", 'index': self.index}, style={"paddingBottom": "0px", 'display': 'inlineBlock'}), dcc.Checklist(options=[{"label": [graph.children.id["role"].split(self.name, 1)[1], html.Br()], "value": graph.children.id} for graph in self.graphs], value=[], persistence=False, id={'type': "checklist", 'index': self.index}, style={"postition": "relative", "paddingLeft": "20px", "paddingTop": "0px"})]) # If no graphs have been built yet builds graphs and gives back the div '{"index":1,"type":"topChecklist"}' def UpdateDiv(self): return self.BuildDiv(self.index) # Builds all the plots def BuildGraphs(self): graphs = [] values = {} time = [] # Get Data from each saved time for i, timestamp in enumerate(self.data.keys()): #time.append(datetime.fromtimestamp(int(timestamp.split("/")[-1])).replace(microsecond=0)) # Todo set me back in! time.append(datetime.now().replace(microsecond=0)) # Todo Better for demo purposes for key in self.data[timestamp].keys(): if not key in values.keys(): values.update({key: []}) values[key].append(self.data[timestamp][key]) for key in values.keys(): global fig if "Ac" in key and not "Actual" in key and not 'Power' in key: for key2 in values[key][0][0]: fig = go.Figure() fig.layout.title =self.name + " Ac " + key2 stackgroup1 = "one" stackgroup2 = "two" stackgroup3 = "three" if key2 == "Current": stackgroup2 = "one" stackgroup3 = "one" fig.add_trace(go.Scatter(x=time, y=[values[key][i][0][key2] for i in range(len(time))], fill='tonexty',name="Phase 1", stackgroup=stackgroup1)) fig.add_trace(go.Scatter(x=time, y=[values[key][i][1][key2] for i in range(len(time))], fill='tonexty', name="Phase 2", stackgroup=stackgroup2)) fig.add_trace(go.Scatter(x=time, y=[values[key][i][2][key2] for i in range(len(time))], fill='tonexty', name="Phase 3", stackgroup=stackgroup3)) fig.update_xaxes(fixedrange=True) fig.update_yaxes(fixedrange=True, rangemode="tozero") fig.update_layout(uirevision=False) graphs.append(html.Div(hidden=self.hidden, children=dcc.Graph(id={"type":"graph", "role": self.name + " " + "Ac " + key2.replace('/', ' '), "index":self.index*100+len(graphs)}, figure=fig, config={'displayModeBar': False}, style={"width": "90%", "float": "right"}))) continue elif "Dc" in key and not "Actual" in key and not "Power" in key: for key2 in values[key][0]: df = pd.DataFrame({ "Zeit": time, "Wert": [values[key][i][key2] for i in range(len(time))], }) fig = px.area(df, x="Zeit", y="Wert", title=self.name + " " + key2.replace('/', ' ').title(), markers=True) fig.update_xaxes(fixedrange=True) fig.update_yaxes(fixedrange=True, rangemode="tozero") fig.update_layout(uirevision=False) # finally add figure to graphs graphs.append(html.Div(hidden=self.hidden,children=dcc.Graph(id={"type":"graph", "role": self.name + " " + 'Dc ' + key2.replace('/', ' '), "index":self.index*100+len(graphs)}, figure=fig, # id=self.name + 'Dc ' + key2, config={'displayModeBar': False}, style={"width": "90%", "float": "right", "display": "inline-block"}))) continue elif "Alarm" in key: fig = go.Figure() fig.layout.title = self.name + "-Alarms" for key2 in values[key][0]: fig.add_trace(go.Scatter(x=time, y=[1 if key2 in values[key][i] else 0 for i in range(len(time))], fill='tonexty', name=key2, stackgroup="one")) fig.update_xaxes(fixedrange=True) fig.update_yaxes(fixedrange=True, rangemode="tozero") fig.update_layout(uirevision=False) graphs.append( html.Div(hidden=self.hidden, children=dcc.Graph(id={"type":"graph", "role": self.name + " " + "Alarms", "index":self.index*100+len(graphs)}, figure=fig, config={'displayModeBar': False}, style={"width": "90%", "float": "right"}))) continue elif any(["Name" in key, "Type" in key, "MainState" in key]): # Skip we have built this already, or it isn't useful continue # check if the data is values or flags/strings if type(values[key][0]) is float or type(values[key][0]) is int: df = pd.DataFrame({ "Zeit": time, "Wert": values[key], }) fig = px.area(df, x="Zeit", y="Wert", title=self.name + " " + key.replace('/', ' ').title(), markers=True) fig.update_xaxes(fixedrange=True) fig.update_yaxes(fixedrange=True, rangemode="tozero") fig.update_layout(uirevision=False) #finally add figure to graphs graphs.append(html.Div(hidden=self.hidden, children=dcc.Graph(id={"type":"graph", "role": self.name + " " + key, "index":self.index*100+len(graphs)}, figure=fig, config = {'displayModeBar': False}, style={"width":"90%", "float":"right", "display":"inline-block"}))) self.graphs = graphs # This checks what kind of device this is and sets power calculation def UpdatePower(self): if not self.data: #or self.data[list(self.data.keys())[0]]['Name'] == "ampt" return if "Frequency" in list(self.data[list(self.data.keys())[0]].keys()): # 3-phase AC for timestamp in self.data.keys(): self.data[timestamp].update({"Ac Power": self.data[timestamp]["Ac"][0]["Voltage"] * self.data[timestamp]["Ac"][0]["Current"] * cos(self.data[timestamp]["Ac"][0]["Phi"]) + self.data[timestamp]["Ac"][1]["Voltage"] * self.data[timestamp]["Ac"][1]["Current"] * cos(self.data[timestamp]["Ac"][1]["Phi"]) + self.data[timestamp]["Ac"][2]["Voltage"] * self.data[timestamp]["Ac"][2]["Current"] * cos(self.data[timestamp]["Ac"][2]["Phi"])}) self.power[0] = self.data[list(self.data.keys())[-1]]["Ac Power"] if "Dc" in list(self.data[list(self.data.keys())[0]].keys()) or "Dc48" in list(self.data[list(self.data.keys())[0]].keys()): # DC Dc = "Dc48" if "Dc48" in list(self.data[list(self.data.keys())[0]].keys()) else "Dc" for timestamp in self.data.keys(): self.data[timestamp].update({"Dc Power": self.data[timestamp][Dc]["Voltage"] * self.data[timestamp][Dc]["Current"]}) self.power[1] = self.data[list(self.data.keys())[-1]]["Dc Power"]