2023-02-16 12:57:06 +00:00
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import string
|
|
|
|
from math import cos
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
import dash
|
|
|
|
import numpy as np
|
|
|
|
from dash import Dash, html, dcc, Output, Input, State, MATCH, ctx, ClientsideFunction, ALL
|
|
|
|
from dash.exceptions import PreventUpdate
|
|
|
|
from python.gui.cytoscape import style, buildEdges, buildNodes
|
|
|
|
from python.gui.flatten import flattenDevice
|
|
|
|
from python.gui.templates import Device, find_Device, now, find_Device_strict
|
|
|
|
import dash_cytoscape as cyto
|
|
|
|
|
|
|
|
# TODO Repair the Cytoscape Node Click Callbacks toggling visibility of the associated graphs
|
|
|
|
# TODO CSS BUG: the graphs don't float to the top. (check only DcDc current for example)
|
|
|
|
# TODO Startup Bug: ~~This should resolve itself once we have realtime data~~
|
|
|
|
# TODO Beautify and Test (E.G Units)
|
|
|
|
|
|
|
|
# External css and javascript for the dash app
|
|
|
|
external_stylesheets = [
|
|
|
|
'./assets/header.css',
|
|
|
|
]
|
|
|
|
external_scripts = [
|
|
|
|
'./assets/inserted.js',
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# Main Dash App Webserver
|
|
|
|
app = Dash(__name__, external_stylesheets=external_stylesheets, external_scripts=external_scripts)
|
|
|
|
|
|
|
|
|
|
|
|
# First: Build Topology
|
|
|
|
|
|
|
|
# Grabs device names and connections from topology file
|
|
|
|
# NOTE: Device changes are only possible on restart TODO grab topology from s3
|
|
|
|
|
|
|
|
deviceNames = []
|
|
|
|
connectionsL = []
|
|
|
|
connectionsT = []
|
|
|
|
connectionsB = []
|
|
|
|
connectionsR = []
|
|
|
|
devices = []
|
|
|
|
topology = json.load(open(f"./assets/data/topology1671435263"))
|
|
|
|
|
|
|
|
def psum(l : list) -> float:
|
|
|
|
sum = 0
|
|
|
|
for k in l:
|
|
|
|
if k:
|
|
|
|
sum += k
|
|
|
|
return float(sum)
|
|
|
|
|
|
|
|
#This builds the cytoscape nodes (deviceNames, and Edges (connections)
|
|
|
|
for i, bus in enumerate(topology["Topology"]):
|
|
|
|
# if not bus["Name"] in deviceNames:
|
|
|
|
for key in bus.keys():
|
|
|
|
nodeName = bus[key].split(':',1)[0]
|
|
|
|
if not nodeName or nodeName == "None" or nodeName == "Losses":
|
|
|
|
continue
|
|
|
|
if not nodeName in deviceNames:
|
|
|
|
deviceNames.append(nodeName)
|
|
|
|
dev = Device(nodeName)
|
|
|
|
new = True
|
|
|
|
elif key == "Name":
|
|
|
|
new = False
|
|
|
|
dev = find_Device_strict(nodeName, devices)[0]
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
invisible = False
|
|
|
|
pos = [0,0]
|
|
|
|
buspower = []
|
|
|
|
|
|
|
|
if len(bus[key].split(':'))>2 and bus[key].split(':')[2] == "hide":
|
|
|
|
invisible = True
|
|
|
|
|
|
|
|
if invisible:
|
|
|
|
dev.connection = [bus["Name"], nodeName]
|
|
|
|
|
|
|
|
|
|
|
|
match key:
|
|
|
|
case "Left":
|
|
|
|
pos = [i, 1]
|
|
|
|
if invisible:
|
|
|
|
if topology["Topology"][i - 1]["Name"] != None:
|
|
|
|
nodeName = topology["Topology"][i - 1]["Name"]
|
|
|
|
if not (bus["Name"], nodeName) in connectionsL:
|
|
|
|
connectionsL.append((bus["Name"], nodeName))
|
|
|
|
connectionsR.append((nodeName, bus["Name"]))
|
|
|
|
case "Right":
|
|
|
|
pos = [i + 2, 1]
|
|
|
|
if invisible:
|
|
|
|
if len(topology["Topology"]) > i and topology["Topology"][i + 1]["Name"] != None != "None":
|
|
|
|
nodeName = topology["Topology"][i + 1]["Name"]
|
|
|
|
if not (bus["Name"], nodeName) in connectionsR:
|
|
|
|
connectionsR.append((bus["Name"], nodeName))
|
|
|
|
connectionsL.append((nodeName, bus["Name"]))
|
|
|
|
case "Name":
|
|
|
|
pos = [i + 1, 1]
|
|
|
|
if new:
|
|
|
|
buspower = [bus[key] for key in bus.keys() if not key == "Name"]
|
|
|
|
if bus["Name"] == nodeName == "Dc":
|
|
|
|
connectionsR.append(("Inverter", bus["Name"]))
|
|
|
|
connectionsL.append((nodeName, "Inverter"))
|
|
|
|
if bus["Name"] == nodeName == "DcDc":
|
|
|
|
connectionsL.append((nodeName, "Dc"))
|
|
|
|
|
|
|
|
case "Top":
|
|
|
|
pos = [i + 1, 0]
|
|
|
|
if not (bus["Name"], nodeName) in connectionsT:
|
|
|
|
connectionsT.append((nodeName, bus["Name"]))
|
|
|
|
case "Bottom":
|
|
|
|
pos = [i + 1, 2]
|
|
|
|
if not (bus["Name"], nodeName) in connectionsB:
|
|
|
|
connectionsB.append((bus["Name"], nodeName))
|
|
|
|
|
|
|
|
if buspower != []:
|
|
|
|
dev.Buspower = buspower
|
|
|
|
|
|
|
|
if invisible:
|
|
|
|
pos = [0, 0]
|
|
|
|
dev.position = pos
|
|
|
|
|
|
|
|
if new:
|
|
|
|
devices.append(dev)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# NOTE: Device changes are only possible on restart
|
|
|
|
#deviceNames = ['Grid','PvOnAcIn','ac_in','ac_last','ac_out','Inverter','dc_last','PvOnDc','dc_bus','DcDc','battery']
|
|
|
|
# devices = [Device(name) for name in deviceNames]
|
|
|
|
|
|
|
|
# data for the cytoscape graph found in ./cytoscape.py
|
|
|
|
nodes = buildNodes(devices)
|
|
|
|
edges = buildEdges(connectionsT + connectionsB + connectionsL + connectionsR)
|
|
|
|
elements = nodes + edges
|
|
|
|
|
|
|
|
# Second: Grab initial GraphData
|
|
|
|
|
|
|
|
# Building initial device Data TODO implement Local/S3 History
|
|
|
|
path = "./assets/data3/"
|
|
|
|
allFileNames = os.listdir(path)
|
|
|
|
fileNames = allFileNames[0:180]
|
|
|
|
|
|
|
|
# fileNames = [f"./assets/data/{i}" for i in range(1671435263, 1671435443, 2)] #range(1669730732, 1669731092, 2)]
|
|
|
|
|
|
|
|
# timeData is a dict of dicts with the keys being the timestamps
|
|
|
|
timeData = {}
|
|
|
|
for i, file in enumerate(fileNames):
|
|
|
|
file = json.load(open(path + file))
|
|
|
|
timeData.update({fileNames[i]:{}})
|
|
|
|
for device in file["Devices"]:
|
|
|
|
timeData[fileNames[i]].update(flattenDevice(device))
|
|
|
|
|
|
|
|
# Building initial device graphs (this considerably reduces input lag, but increases start time of the program)
|
|
|
|
for time in timeData.keys():
|
|
|
|
for key in timeData[time].keys():
|
|
|
|
device = find_Device_strict(key.split("/")[0], devices)[0]
|
|
|
|
if not device:
|
|
|
|
continue #TODO THROW ERROR
|
|
|
|
if not time in (device.data.keys()):
|
|
|
|
device.data.update({time: {}})
|
|
|
|
if not len(key.split("/", 1)) < 2:
|
|
|
|
device.data[time].update({key.split("/", 1)[1]: timeData[time][key]})
|
|
|
|
|
|
|
|
debug = []
|
|
|
|
|
|
|
|
#-----------------------------------------------------------------------------------------------------------------------
|
|
|
|
# Callbacks
|
|
|
|
|
|
|
|
# This shifts the website viewpoint to the graphs of the node that has been clicked on
|
|
|
|
# And toggles visibility of graphs associated with the node-device
|
|
|
|
# using Javascript found in clientside.js
|
|
|
|
app.clientside_callback(
|
|
|
|
ClientsideFunction(
|
|
|
|
namespace='clientside',
|
|
|
|
function_name='clickNode'
|
|
|
|
),
|
|
|
|
Output('placeholder', 'data'),
|
|
|
|
Input('view', 'data')
|
|
|
|
)
|
|
|
|
|
|
|
|
# Clock callback, every second updates the time TODO MAKE ME CLIENTSIDE?
|
|
|
|
# This also resets tapNodeData and tapEdgeData for correct handling of clicks on nodes/edges
|
|
|
|
@app.callback([Output('title', 'children'),
|
|
|
|
Output('cytoscape-elements', 'tapNodeData'),
|
|
|
|
Output('cytoscape-elements', 'tapEdgeData')],
|
|
|
|
Input('interval-component3', 'n_intervals'))
|
|
|
|
def update_time_live(n):
|
|
|
|
return '''
|
|
|
|
Echtzeit Daten-Monitoring
|
|
|
|
''' + str(np.datetime64('now')).replace('T', ' '), {}, {}
|
|
|
|
|
|
|
|
|
|
|
|
# Updates the powergraph connections every 2 seconds, also tracks the highest power measured in 'max-power' dcc.Store
|
|
|
|
@app.callback(Output('cytoscape-elements', 'elements'),
|
|
|
|
Output('max-power', 'data'),
|
|
|
|
Input('interval-component-2', 'n_intervals'),
|
|
|
|
State('max-power', 'data'))
|
|
|
|
def update_power_graph(n, maxPower):
|
|
|
|
for i, device in enumerate(devices):
|
|
|
|
# device.UpdatePower()
|
|
|
|
if device.name == "Battery":
|
|
|
|
continue
|
|
|
|
power = device.power
|
|
|
|
|
|
|
|
#TODO as long as device is not inverter None will be in power
|
|
|
|
|
|
|
|
if power == [None, None] and device.Buspower == []:
|
|
|
|
continue
|
|
|
|
rpower = [] # Left top bottom right
|
|
|
|
|
|
|
|
sum = psum(power)
|
|
|
|
if sum > maxPower:
|
|
|
|
maxPower = sum
|
|
|
|
|
|
|
|
|
|
|
|
#TODO handle inverter two power measurements..
|
|
|
|
|
|
|
|
# Setting nodes to either a producer or consumer depending on power out/in
|
|
|
|
for node in ( nodes[y] for y in range(0, len(nodes)) if nodes[y]['data']['id'] == device.name):
|
|
|
|
if sum >= 0:
|
|
|
|
node.update({'classes': node.get('classes') + ' producer'})
|
|
|
|
else:
|
|
|
|
node.update({'classes': node.get('classes') + ' consumer'})
|
|
|
|
|
|
|
|
# Updates the power values on the connections
|
|
|
|
if device.Buspower != []:
|
|
|
|
for remote in device.Buspower:
|
|
|
|
remoteDevice = find_Device_strict(remote.split(":", 1)[0], devices)[0]
|
|
|
|
if None in remoteDevice.power:
|
|
|
|
remotePower = remoteDevice.power[0] if remoteDevice.power[0] != None else remoteDevice.power[1]
|
|
|
|
|
|
|
|
else: #Inverter
|
|
|
|
if "Ac" in device.name:
|
|
|
|
remotePower = remoteDevice.power[0]
|
|
|
|
else:
|
|
|
|
remotePower = remoteDevice.power[1]
|
|
|
|
|
|
|
|
rpower.append(remotePower)
|
|
|
|
|
|
|
|
|
|
|
|
#or edges[y]['data']['source'] in device.Buspower)
|
|
|
|
if None in power:
|
|
|
|
if rpower != []:
|
|
|
|
if rpower[0] <= 0:
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((rpower[0]/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(abs(rpower[0]))) + "W"]})
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
else:
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((rpower[0]/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(abs(rpower[0]))) + "W"]})
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
|
|
|
|
if rpower[3] >= 0:
|
|
|
|
#This should always be the case
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((rpower[3]/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(rpower[3])) + "W"]})
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
else:
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str((rpower[3] / maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(abs(rpower[3]))) + "W"]})
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
|
|
|
|
if not rpower[2]:
|
|
|
|
rpower[2] = 0
|
|
|
|
|
|
|
|
for connection in (y for y in connectionsB if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str((abs(rpower[0] + rpower[2] - rpower[3]) / maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(abs(rpower[0] + rpower[2] - rpower[3]))) + "W"]})
|
|
|
|
continue
|
|
|
|
if not sum:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if sum >= 0:
|
|
|
|
for connection in (y for y in connectionsR + connectionsT if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((sum/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(sum)) + "W"]})
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
|
|
|
|
elif sum < 0:
|
|
|
|
for connection in (y for y in connectionsR + connectionsT if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((sum/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(abs(int(sum))) + "W"]})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
#This is the inverter
|
|
|
|
if not sum:
|
|
|
|
continue
|
|
|
|
# if power[0] < 0:
|
|
|
|
# #Ac side
|
|
|
|
# # for connection in (y for y in connectionsL if device.name == y[0]):
|
|
|
|
# # for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
# # edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
# # edge.update({'style': {'width': str((power[0]/maxPower) * 10) + 'px'}})
|
|
|
|
# # edge['data'].update({'label': [str(int(power[0])) + "W"]})
|
|
|
|
# # else:
|
|
|
|
# # for connection in (y for y in connectionsL if device.name == y[0]):
|
|
|
|
# # for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
# # edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
# # edge.update({'style': {'width': 0 + 'px'}})
|
|
|
|
# # edge['data'].update({'label': []})
|
|
|
|
|
|
|
|
if power[1] > 0:
|
|
|
|
#DC side
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str((power[1]/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(power[1])) + "W"]})
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] ==
|
|
|
|
connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
elif power[1] < 0:
|
|
|
|
#DC side
|
|
|
|
for connection in (y for y in connectionsL if device.name == y[1]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str(abs(power[1]/maxPower) * 20) + 'px'}})
|
|
|
|
edge['data'].update({'label': [str(int(abs(power[1]))) + "W"]})
|
|
|
|
for connection in (y for y in connectionsR if device.name == y[0]):
|
|
|
|
for edge in (edges[y] for y in range(0, len(edges)) if
|
|
|
|
edges[y]['data']['source'] == connection[0] and edges[y]['data']['target'] == connection[1]):
|
|
|
|
edge.update({'style': {'width': str(0) + 'px'}})
|
|
|
|
edge['data'].update({'label': []})
|
|
|
|
|
|
|
|
return elements, maxPower
|
|
|
|
|
|
|
|
# Handles the visibility checkmarks through wildcards
|
|
|
|
@app.callback([Output({'type': 'graphs', 'index': MATCH}, "children"),
|
|
|
|
Output({'type': 'checklist', 'index': MATCH}, "value")],
|
|
|
|
[Input({'type': 'checklist', 'index': MATCH}, "value"),
|
|
|
|
Input({'type': 'topChecklist', 'index': MATCH}, "value")],
|
|
|
|
State({'type': 'graphs', 'index': MATCH}, "children")
|
|
|
|
, prevent_initiall_call=True)
|
|
|
|
def graph_visibility_checklist(value, top, graphs):
|
|
|
|
if not graphs or not ctx.triggered_id:
|
|
|
|
raise PreventUpdate
|
|
|
|
|
|
|
|
#Setting all invisible and turning selected back on
|
|
|
|
for graph in graphs:
|
|
|
|
graph["props"]["hidden"] = True
|
|
|
|
|
|
|
|
|
|
|
|
if top != [] and ctx.triggered_id.type == 'topChecklist':
|
|
|
|
id = []
|
|
|
|
for graph in graphs:
|
|
|
|
id.append(graph["props"]["children"]["props"]["id"])
|
|
|
|
graph["props"]["hidden"] = False
|
|
|
|
|
|
|
|
for i in id:
|
|
|
|
for dev in find_Device(i, devices):
|
|
|
|
dev.hidden = False
|
|
|
|
|
|
|
|
return graphs, id
|
|
|
|
|
|
|
|
elif ctx.triggered_id.type == 'topChecklist':
|
|
|
|
return graphs, []
|
|
|
|
|
|
|
|
if not value:
|
|
|
|
return graphs, dash.no_update
|
|
|
|
|
|
|
|
for graph in (graph for graph in graphs if (any(graph["props"]["children"]["props"]["id"]["role"] in i["role"] for i in value) or any(i["role"] in graph["props"]["children"]["props"]["id"]["role"] for i in value))):
|
|
|
|
graph["props"]["hidden"] = False
|
|
|
|
for i in value:
|
|
|
|
for dev in find_Device(i["role"], devices):
|
|
|
|
dev.hidden = False
|
|
|
|
|
|
|
|
return graphs, dash.no_update
|
|
|
|
|
|
|
|
# The small number next to the graph triggers are the latest Value or an average thereof (AC)
|
|
|
|
@app.callback(Output({'type': 'checklist', 'index': ALL}, "options"),
|
|
|
|
Input('interval-component3', 'n_intervals'),
|
|
|
|
[State({'type': 'graphs', 'index': ALL}, "children"),
|
|
|
|
State({'type': 'checklist', 'index': ALL}, "options")]
|
|
|
|
)
|
|
|
|
def lable_previews(n, graphs, options):
|
|
|
|
for x in graphs:
|
|
|
|
if x != []:
|
|
|
|
for g in x:
|
|
|
|
for l in options:
|
|
|
|
if l!=[]:
|
|
|
|
for a in l:
|
|
|
|
if g["props"]["children"]["props"]["id"]["role"].split(" ",1)[1].replace(" ", "") in a["label"][0].replace(" ", ""):
|
|
|
|
val = 0
|
|
|
|
if g["props"]["children"]["props"]["figure"]["data"] != [] and len(g["props"]["children"]["props"]["figure"]["data"][0]["y"]) != 0:
|
|
|
|
if len(g["props"]["children"]["props"]["figure"]["data"]) == 3:
|
|
|
|
val = round((g["props"]["children"]["props"]["figure"]["data"][0]["y"][-1]
|
|
|
|
+ g["props"]["children"]["props"]["figure"]["data"][1]["y"][-1]
|
|
|
|
+ g["props"]["children"]["props"]["figure"]["data"][2]["y"][-1])/3, 1)
|
|
|
|
else:
|
|
|
|
if g["props"]["children"]["props"]["figure"]["data"][0]["y"][-1] != []:
|
|
|
|
val = round(g["props"]["children"]["props"]["figure"]["data"][0]["y"][-1], 1)
|
|
|
|
|
|
|
|
|
|
|
|
a["label"][0] = (a["label"][0].rstrip(string.digits + string.punctuation + string.whitespace) + " " + str(val))
|
|
|
|
return options
|
|
|
|
|
|
|
|
# the second output triggers the viewport callback nodeViewShift TODO MAKE ME CLIENTSIDE?
|
|
|
|
@app.callback(Output('view', 'data'),
|
|
|
|
[Input('cytoscape-elements', 'tapNodeData'),
|
|
|
|
Input('cytoscape-elements', 'tapEdgeData')],
|
|
|
|
State('view', 'data'),)
|
|
|
|
def cytoscape_visibility_clicks(nodeData, edgeData, view):
|
|
|
|
if nodeData and nodeData != {}:
|
|
|
|
return '{"index":%s,"type":"topChecklist"}' % (find_Device_strict(nodeData["id"], devices)[0].index)
|
|
|
|
|
|
|
|
elif edgeData and edgeData != {}:
|
|
|
|
return '{"index":%s,"type":"topChecklist"}' % (find_Device_strict(edgeData["source"], devices)[0].index)
|
|
|
|
return dash.no_update
|
|
|
|
|
|
|
|
# Updates the individual graphs every second
|
|
|
|
@app.callback(Output('OurGraphs', 'children'),
|
|
|
|
Input('interval-component', 'n_intervals'),
|
|
|
|
State('OurGraphs', 'children'),
|
|
|
|
prevent_initiall_call=True)
|
|
|
|
def update_graphs(n, graphs):
|
|
|
|
|
|
|
|
# TODO GRAB NEW DATA FROM DISK OR S3
|
|
|
|
timeData = {}
|
|
|
|
#Local copy of allFileNames because we loop over them at the moment
|
2023-04-06 12:28:35 +00:00
|
|
|
number = [item for item in allFileNames if item not in fileNames][n % len(allFileNames)-1]
|
2023-02-16 12:57:06 +00:00
|
|
|
|
|
|
|
fileNames.pop(0)
|
|
|
|
fileNames.append(str(number))
|
|
|
|
|
|
|
|
filename = path + str(number)
|
|
|
|
file = json.load(open(filename))
|
|
|
|
|
|
|
|
timeData.update({filename: {}})
|
|
|
|
for device in file["Devices"]:
|
|
|
|
timeData[filename].update(flattenDevice(device))
|
|
|
|
|
|
|
|
for device in devices:
|
|
|
|
if not filename in (device.data.keys()):
|
|
|
|
device.data.update({filename: {}})
|
|
|
|
for key in (key for key in timeData[filename].keys() if key.split("/",1)[0] == device.name):
|
|
|
|
device.data[filename].update({key.split("/", 1)[1]: timeData[filename][key]})
|
|
|
|
|
|
|
|
for k, device in enumerate(graphs):
|
|
|
|
for graph in device["props"]["children"][0]["props"]["children"]:
|
|
|
|
id = graph["props"]["children"]["props"]["id"]
|
|
|
|
fig = graph["props"]["children"]["props"]["figure"]
|
|
|
|
if "Power" in id["role"].split(" ", 1)[1]:
|
|
|
|
if "Ac" in id["role"].split(" ", 1)[1]:
|
|
|
|
# 3-phase AC
|
|
|
|
fig["data"][0]['y'][0] = (devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][0]["Voltage"] *
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][0]["Current"] *
|
|
|
|
cos(devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][0]["Phi"])+
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][1]["Voltage"] *
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][1]["Current"] *
|
|
|
|
cos(devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][1]["Phi"])+
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][2]["Voltage"] *
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][2]["Current"] *
|
|
|
|
cos(devices[k].data[list(devices[k].data.keys())[-1]]["Ac"][2]["Phi"]))
|
|
|
|
|
|
|
|
devices[k].power[0] = fig["data"][0]['y'][0]
|
|
|
|
if "Dc" in id["role"].split(" ", 1)[1] and not "Dc48" in devices[k].data[list(devices[k].data.keys())[-1]]:
|
|
|
|
# DC
|
|
|
|
fig["data"][0]['y'][0] = (devices[k].data[list(devices[k].data.keys())[-1]]["Dc"]["Voltage"] *
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Dc"]["Current"])
|
|
|
|
devices[k].power[1] = fig["data"][0]['y'][0]
|
|
|
|
if "Dc48" in devices[k].data[list(devices[k].data.keys())[-1]]:
|
|
|
|
fig["data"][0]['y'][0] = (devices[k].data[list(devices[k].data.keys())[-1]]["Dc48"]["Voltage"] *
|
|
|
|
devices[k].data[list(devices[k].data.keys())[-1]]["Dc48"]["Current"])
|
|
|
|
devices[k].power[1] = fig["data"][0]['y'][0]
|
|
|
|
|
|
|
|
fig["data"][0]['x'][0] = now(filename)
|
2023-04-06 12:28:35 +00:00
|
|
|
fig["data"][0]['y'] = fig["data"][0]['y'][-1:] + fig["data"][0]['y'][:-1]
|
|
|
|
fig["data"][0]['x'] = fig["data"][0]['x'][-1:] + fig["data"][0]['x'][:-1]
|
2023-02-16 12:57:06 +00:00
|
|
|
continue
|
|
|
|
|
|
|
|
if "Dc" in id["role"].split(" ",1)[1] or "Ac" in id["role"].split(" ",1)[1] or "Alarm" in id["role"] and "Actual" not in id["role"] :
|
|
|
|
for i, g in enumerate(fig["data"]):
|
|
|
|
if g["x"] == [] or g["y"] == []:
|
|
|
|
continue
|
|
|
|
g["x"].pop(0)
|
|
|
|
g["y"].pop(0)
|
|
|
|
g["x"].append(now(filename))
|
|
|
|
|
|
|
|
if "Ac" in id["role"].split(" ",1)[1] and "Actual" not in id["role"] and "Power" not in id["role"]:
|
|
|
|
g["y"].append(devices[k].data[list(devices[k].data.keys())[-1]][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[0]][i][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[1]])
|
|
|
|
elif "Dc48" in devices[k].data[list(devices[k].data.keys())[-1]] and "Actual" not in id["role"] and "Power" not in id["role"]:
|
|
|
|
g["y"].append(devices[k].data[list(devices[k].data.keys())[-1]]["Dc48"][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[1]])
|
|
|
|
elif "Dc" in id["role"].split(" ",1)[1] and "Actual" not in id["role"] and "Power" not in id["role"]:
|
|
|
|
g["y"].append(devices[k].data[list(devices[k].data.keys())[-1]][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[0]][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[1]])
|
|
|
|
elif "Alarm" in id["role"].split(" ",1)[1]:
|
|
|
|
g["y"].append(1 if g["name"] in devices[k].data[list(devices[k].data.keys())[-1]][
|
|
|
|
id["role"].split(" ", 1)[1].split(' ')[0]] else 0)
|
|
|
|
|
|
|
|
continue
|
|
|
|
elif "Actual" in id["role"] or fig["data"] ==[]:
|
|
|
|
continue
|
|
|
|
|
|
|
|
fig["data"][0]['y'][0] = devices[k].data[list(devices[k].data.keys())[-1]][id["role"].split(" ", 1)[1]]
|
|
|
|
fig["data"][0]['x'][0] = now(filename)
|
2023-04-06 12:28:35 +00:00
|
|
|
fig["data"][0]['y'] = fig["data"][0]['y'][-1:] + fig["data"][0]['y'][:-1]
|
|
|
|
fig["data"][0]['x'] = fig["data"][0]['x'][-1:] + fig["data"][0]['x'][:-1]
|
|
|
|
|
2023-02-16 12:57:06 +00:00
|
|
|
return graphs
|
|
|
|
|
|
|
|
# Main HTML Layout of the app ------------------------------------------------------------------------------------------
|
|
|
|
app.layout = html.Div(children=[
|
|
|
|
# Static Header with logo and Clock
|
|
|
|
html.Header(className='bp-page-header', children=[
|
|
|
|
html.Img(src="./assets/innovenergy_Logo_onOrange.png", className="logo"),
|
|
|
|
html.H1(children='Salimax Gui Demo'),
|
|
|
|
html.H4(className="clock", id="title", children=['''
|
|
|
|
Echtzeit Daten-Monitoring
|
|
|
|
''' + str(datetime.now().replace(microsecond=0))])
|
|
|
|
]),
|
|
|
|
|
|
|
|
#Big powergraph "overview"
|
|
|
|
html.Div(id="powerGraph", className='powerGraph', children=
|
|
|
|
cyto.Cytoscape(
|
|
|
|
id='cytoscape-elements',
|
|
|
|
layout={'name': 'preset'},
|
|
|
|
style={'position': 'relative', 'width': '100%', 'height': '600px', 'margin': 'auto'},
|
|
|
|
elements=elements,
|
|
|
|
stylesheet=style,
|
|
|
|
userZoomingEnabled=False,
|
|
|
|
userPanningEnabled=False,
|
|
|
|
)
|
|
|
|
),
|
|
|
|
#html.Div(id="Divider", className="divider"),
|
|
|
|
# html.Div(id="graph_buttons", children=[device.BuildButton() for device in devices]),
|
|
|
|
# Don't be fooled, these are all the plots! They are built up in the callbacks
|
|
|
|
html.Div(id="OurGraphs",className="graphs",children=[device.BuildDiv(i) for i,device in enumerate(devices)]),
|
|
|
|
html.Div(id="Checklist",className="sidenav",children=[device.BuildChecklist(i) for i,device in enumerate(devices)]),
|
|
|
|
|
|
|
|
# Every Second triggers updates to clock and plots
|
|
|
|
dcc.Interval(
|
|
|
|
id='interval-component3',
|
|
|
|
interval=1 * 1000, # in milliseconds
|
|
|
|
n_intervals=0
|
|
|
|
),
|
|
|
|
|
|
|
|
# Every two seconds updates plots
|
|
|
|
dcc.Interval(
|
|
|
|
id='interval-component',
|
|
|
|
interval=1 * 1000, # in milliseconds
|
|
|
|
n_intervals=0
|
|
|
|
),
|
|
|
|
|
|
|
|
# Every 5 seconds triggers PowerGraph Update
|
|
|
|
dcc.Interval(
|
|
|
|
id='interval-component-2',
|
|
|
|
interval=1 * 1100, # in milliseconds
|
|
|
|
n_intervals=0
|
|
|
|
),
|
|
|
|
|
|
|
|
# fake Cookie for the highest seen power used for graphing
|
|
|
|
dcc.Store(
|
|
|
|
id='max-power',
|
|
|
|
data=1000
|
|
|
|
),
|
|
|
|
|
|
|
|
# fake cookie used to transfer viewport shift id data to clientside javascript
|
|
|
|
dcc.Store(
|
|
|
|
id='view',
|
|
|
|
data=0
|
|
|
|
),
|
|
|
|
|
|
|
|
# Placeholder for Output callbacks which have no return value
|
|
|
|
dcc.Store(id='placeholder', data=0)
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
# Main App Start on specified ip (here local)
|
|
|
|
if __name__ == '__main__':
|
2023-04-06 12:28:35 +00:00
|
|
|
app.run_server(host= 'localhost', debug=False, port=8080)
|