dashboard wip
This commit is contained in:
parent
35dd46e799
commit
6c994e970c
28
.vscode/launch.json
vendored
Normal file
28
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "🔍 Debug Streamlit",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
|
||||
// Tell VS Code to use `python -m streamlit run ...`
|
||||
"module": "streamlit",
|
||||
|
||||
// Replace `app.py` (or dashboard.py) with your entry-point
|
||||
"args": [
|
||||
"run",
|
||||
"dashboard.py",
|
||||
|
||||
// (optional but highly recommended) disable the auto-reloader
|
||||
"--server.runOnSave=false"
|
||||
],
|
||||
|
||||
// so you can interact with the app and see logs
|
||||
"console": "integratedTerminal",
|
||||
|
||||
// only step into *your* code, not the Streamlit internals
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -37,7 +37,8 @@ def discharge_bess(bess, site_name, dt, discharge_power):
|
||||
# maintain SoC if not assigned to the site
|
||||
new_soc = unit["SoC"]
|
||||
|
||||
# update SoC
|
||||
# update SoC and current load
|
||||
bess["units"][index]["current_load_kW"] = discharge_power
|
||||
bess["units"][index]["SoC"] = new_soc
|
||||
return bess
|
||||
|
||||
@ -72,7 +73,7 @@ def predict_swap_time(bess_soc_for_cycle):
|
||||
return swap_times
|
||||
|
||||
|
||||
def update_cycle_SoC(bess_data, bess_soc_for_cycle, timestamps):
|
||||
def update_cycle_SoC(bess_data, bess_soc_for_cycle, timestamp):
|
||||
init_df = pd.DataFrame(columns=["Timestamp", "SoC"])
|
||||
# assign SoC for cycle
|
||||
for unit in bess_data["units"]:
|
||||
@ -85,13 +86,15 @@ def update_cycle_SoC(bess_data, bess_soc_for_cycle, timestamps):
|
||||
[
|
||||
bess_soc_for_cycle[unit_name],
|
||||
pd.DataFrame(
|
||||
[[timestamps[i], unit["SoC"]]],
|
||||
[[timestamp, unit["SoC"]]],
|
||||
columns=["Timestamp", "SoC"],
|
||||
),
|
||||
],
|
||||
axis=0,
|
||||
)
|
||||
|
||||
return bess_soc_for_cycle
|
||||
|
||||
|
||||
def arrange_swap(bess_data, c):
|
||||
for unit in bess_data["units"]:
|
||||
|
||||
54
Utilities/DataVis.py
Normal file
54
Utilities/DataVis.py
Normal file
@ -0,0 +1,54 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def format_dataframe(
|
||||
bess_soc_for_cycle, bess_data, load_profiles_since_start, swap_time
|
||||
):
|
||||
"""Formats the DataFrame for display in the dashboard."""
|
||||
# Create a DataFrame for sites
|
||||
# columns = ["Site Name", "MBESS Unit", "Current Load (kW)", "SoC (%)", "Predicted Swap Time"]
|
||||
|
||||
status_df = pd.DataFrame(
|
||||
columns=[
|
||||
"Site Name",
|
||||
"MBESS Unit",
|
||||
"Current Load (kW)",
|
||||
"SoC (%)",
|
||||
"Predicted Swap Time",
|
||||
"Cycle Discharge Profile",
|
||||
"Load Profile Since Start",
|
||||
]
|
||||
)
|
||||
|
||||
for site in load_profiles_since_start.keys():
|
||||
index = next(i for i, d in enumerate(bess_data["units"]) if d["site"] == site)
|
||||
soc = bess_data["units"][index]["SoC"]
|
||||
current_load = bess_data["units"][index]["current_load_kW"]
|
||||
unit_name = bess_data["units"][index]["name"]
|
||||
predicted_swap_time = swap_time.get(unit_name, "N/A")
|
||||
|
||||
status_df = pd.concat(
|
||||
[
|
||||
status_df,
|
||||
pd.DataFrame(
|
||||
[
|
||||
{
|
||||
"Site Name": site,
|
||||
"MBESS Unit": unit_name,
|
||||
"Current Load (kW)": current_load,
|
||||
"SoC (%)": soc * 100, # Convert to percentage
|
||||
"Predicted Swap Time": predicted_swap_time,
|
||||
"Cycle Discharge Profile": bess_soc_for_cycle[unit_name][
|
||||
"SoC"
|
||||
].tolist(),
|
||||
"Load Profile Since Start": load_profiles_since_start[
|
||||
site
|
||||
].tolist(),
|
||||
}
|
||||
]
|
||||
),
|
||||
],
|
||||
ignore_index=True,
|
||||
)
|
||||
|
||||
return status_df
|
||||
65
dashboard.py
65
dashboard.py
@ -1,7 +1,17 @@
|
||||
# dashboard.py
|
||||
import streamlit as st
|
||||
import matplotlib.pyplot as plt
|
||||
from main import start_sim, stop_sim, reset_sim, bess_soc_since_start
|
||||
import matplotlib.pyplot as pl
|
||||
import pandas as pd
|
||||
import main
|
||||
from main import (
|
||||
start_sim,
|
||||
stop_sim,
|
||||
reset_sim,
|
||||
)
|
||||
import time
|
||||
|
||||
|
||||
st.set_page_config(layout="wide")
|
||||
|
||||
# Header
|
||||
st.logo("https://rooftop.my/logo.svg", size="large")
|
||||
@ -31,3 +41,54 @@ with col3:
|
||||
if st.button("Reset", use_container_width=True):
|
||||
reset_sim()
|
||||
st.session_state.running = False
|
||||
|
||||
placeholder = st.empty()
|
||||
|
||||
|
||||
def show_table():
|
||||
df = main.status_df
|
||||
if df is None or df.empty:
|
||||
placeholder.text("Waiting for first simulation step…")
|
||||
else:
|
||||
placeholder.dataframe(
|
||||
df,
|
||||
column_config={
|
||||
"Site Name": st.column_config.TextColumn("Site Name"),
|
||||
"MBESS Unit": st.column_config.TextColumn(
|
||||
"MBESS Unit", help="Name of the MBESS unit at the site"
|
||||
),
|
||||
"Current Load (kW)": st.column_config.NumberColumn(
|
||||
"Current Load (kW)", help="Current BESS discharge load in kW"
|
||||
),
|
||||
"SoC (%)": st.column_config.ProgressColumn(
|
||||
"State of Charge",
|
||||
help="State of Charge of the BESS unit",
|
||||
format="%d%%",
|
||||
min_value=0,
|
||||
max_value=100,
|
||||
),
|
||||
"Predicted Swap Time": st.column_config.TextColumn(
|
||||
"Predicted Swap Time", help="Predicted time for BESS swap"
|
||||
),
|
||||
"Cycle Discharge Profile": st.column_config.LineChartColumn(
|
||||
"Cycle Discharge Profile",
|
||||
help="Cycle discharge profile of the BESS unit",
|
||||
),
|
||||
"Load Profile Since Start": st.column_config.LineChartColumn(
|
||||
"Load Profile Since Start",
|
||||
help="Load profile since the start of the simulation",
|
||||
),
|
||||
},
|
||||
use_container_width=True,
|
||||
)
|
||||
|
||||
|
||||
if st.session_state.running:
|
||||
st.subheader("BESS State of Charge (SoC) and Energy Consumption")
|
||||
# display BESS data, SoC, Load Consumption
|
||||
show_table()
|
||||
time.sleep(1)
|
||||
st.rerun()
|
||||
else:
|
||||
show_table()
|
||||
st.info("Simulation paused.")
|
||||
|
||||
24
main.py
24
main.py
@ -9,6 +9,7 @@ from Utilities.BESS import (
|
||||
predict_swap_time,
|
||||
update_cycle_SoC,
|
||||
)
|
||||
from Utilities.DataVis import format_dataframe
|
||||
import matplotlib.pyplot as pl
|
||||
import pandas as pd
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
@ -29,10 +30,13 @@ duration = c["sim_time"]["duration_days"] * 24 * 60 * 60
|
||||
c["sim_end_time"] = c["sim_start_time"] + duration
|
||||
timestamps = np.arange(c["sim_start_time"], c["sim_end_time"] + 1, dt)
|
||||
c["sim_time"]["batch_process_seconds"] = c["sim_time"]["batch_process_hours"] * 60 * 60
|
||||
load_profiles_since_start = None
|
||||
status_df = None
|
||||
|
||||
# load site info
|
||||
c["site_info"] = yaml.safe_load(open(c["paths"]["site_info"]))
|
||||
|
||||
|
||||
### <<< CONTROL ADDED >>> Initialize simulation state globals
|
||||
sim_i = 0
|
||||
running = False
|
||||
@ -42,7 +46,8 @@ sim_lock = threading.Lock()
|
||||
|
||||
# initialise BESS
|
||||
def _init_state():
|
||||
global bess_data, bess_soc_since_start, bess_soc_for_cycle, cumulative_load_profiles
|
||||
global bess_data, bess_soc_since_start, bess_soc_for_cycle, cumulative_load_profiles, load_profiles_since_start
|
||||
|
||||
bd = initialise_SoC(bess_data.copy())
|
||||
bd = initial_site_assignment(c, bd)
|
||||
bess_data = bd
|
||||
@ -64,7 +69,7 @@ _init_state()
|
||||
|
||||
def simulation_loop():
|
||||
"""Runs the loop, stepping through timestamps until stopped or finished."""
|
||||
global sim_i, running, is_running_in_async, cumulative_load_profiles, bess_data
|
||||
global sim_i, running, is_running_in_async, cumulative_load_profiles, bess_data, bess_soc_for_cycle, load_profiles_since_start, status_df
|
||||
with ThreadPoolExecutor() as executor:
|
||||
while True:
|
||||
with sim_lock:
|
||||
@ -100,7 +105,7 @@ def simulation_loop():
|
||||
|
||||
# update cycle SoC and predict swaps
|
||||
bess_soc_for_cycle = update_cycle_SoC(
|
||||
bess_data, bess_soc_for_cycle, timestamps
|
||||
bess_data, bess_soc_for_cycle, timestamps[i]
|
||||
)
|
||||
swap_times = predict_swap_time(bess_soc_for_cycle)
|
||||
|
||||
@ -113,15 +118,16 @@ def simulation_loop():
|
||||
print(len(cumulative_load_profiles), "profiles generated")
|
||||
is_running_in_async = False
|
||||
|
||||
load_profiles_since_start = cumulative_load_profiles.iloc[: i + 1]
|
||||
|
||||
# format data for display
|
||||
status_df = format_dataframe(
|
||||
bess_soc_for_cycle, bess_data, load_profiles_since_start, swap_times
|
||||
)
|
||||
|
||||
# small sleep to allow dashboard to refresh / release GIL
|
||||
time.sleep(0.01)
|
||||
|
||||
# once loop ends, you can plot or notify completion here
|
||||
pl.plot(cumulative_load_profiles)
|
||||
pl.show()
|
||||
pl.plot(bess_soc_since_start)
|
||||
pl.show()
|
||||
|
||||
|
||||
### <<< CONTROL ADDED >>> Control functions
|
||||
def start_sim():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user