Innovenergy_trunk/python/gui/graphs.py

632 lines
30 KiB
Python
Raw Normal View History

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)