Compare commits
6 Commits
milestone/
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 089078a9ea | |||
| 7c5d3f5b1d | |||
| d3dbd9c9f3 | |||
| 6d1199e37a | |||
| 6c994e970c | |||
| 35dd46e799 |
34
.streamlit/config.toml
Normal file
34
.streamlit/config.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[server]
|
||||||
|
enableStaticServing = true
|
||||||
|
|
||||||
|
[[theme.fontFaces]]
|
||||||
|
family = "Exo2"
|
||||||
|
url = "app/static/EXO2-VARIABLEFONT_WGHT.TTF"
|
||||||
|
style = "normal"
|
||||||
|
weight = 400
|
||||||
|
|
||||||
|
|
||||||
|
[[theme.fontFaces]]
|
||||||
|
family = "Exo2"
|
||||||
|
url = "app/static/EXO2-BOLD.TTF"
|
||||||
|
style = "bold"
|
||||||
|
weight = 700
|
||||||
|
|
||||||
|
[[theme.fontFaces]]
|
||||||
|
family = "Exo2"
|
||||||
|
url = "app/static/EXO2-ITALIC.TTF"
|
||||||
|
style = "italic"
|
||||||
|
weight = 400
|
||||||
|
|
||||||
|
[[theme.fontFaces]]
|
||||||
|
family = "Exo2"
|
||||||
|
url = "app/static/EXO2-BOLDITALIC.TTF"
|
||||||
|
style = "bold italic"
|
||||||
|
weight = 7
|
||||||
|
|
||||||
|
[theme]
|
||||||
|
base="dark"
|
||||||
|
primaryColor="#fcd913"
|
||||||
|
font="Exo2"
|
||||||
|
codeFont="Exo2"
|
||||||
|
|
||||||
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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -31,13 +31,13 @@ def discharge_bess(bess, site_name, dt, discharge_power):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if unit["site"] == site_name:
|
if unit["site"] == site_name:
|
||||||
new_soc = unit["SoC"] - (dt * discharge_energy) / unit["capacity_kWh"]
|
new_soc = unit["SoC"] - discharge_energy / unit["capacity_kWh"]
|
||||||
new_soc = 0 if new_soc < 0 else new_soc
|
new_soc = 0 if new_soc < 0 else new_soc
|
||||||
else:
|
else:
|
||||||
# maintain SoC if not assigned to the site
|
continue
|
||||||
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
|
bess["units"][index]["SoC"] = new_soc
|
||||||
return bess
|
return bess
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ def predict_swap_time(bess_soc_for_cycle):
|
|||||||
return swap_times
|
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"])
|
init_df = pd.DataFrame(columns=["Timestamp", "SoC"])
|
||||||
# assign SoC for cycle
|
# assign SoC for cycle
|
||||||
for unit in bess_data["units"]:
|
for unit in bess_data["units"]:
|
||||||
@ -85,20 +85,60 @@ def update_cycle_SoC(bess_data, bess_soc_for_cycle, timestamps):
|
|||||||
[
|
[
|
||||||
bess_soc_for_cycle[unit_name],
|
bess_soc_for_cycle[unit_name],
|
||||||
pd.DataFrame(
|
pd.DataFrame(
|
||||||
[[timestamps[i], unit["SoC"]]],
|
[[timestamp, unit["SoC"]]],
|
||||||
columns=["Timestamp", "SoC"],
|
columns=["Timestamp", "SoC"],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
axis=0,
|
axis=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return bess_soc_for_cycle
|
||||||
|
|
||||||
def arrange_swap(bess_data, c):
|
|
||||||
for unit in bess_data["units"]:
|
def arrange_swap(c, bess_data, bess_soc_for_cycle):
|
||||||
if unit["SoC"] < c["bess"]["buffer"]:
|
# identify BESS units that need swapping
|
||||||
# find for unassigned BESS unit with SOC at 100%
|
units_needing_swap = [
|
||||||
for candidate in bess_data["units"]:
|
unit for unit in bess_data["units"] if unit["SoC"] < bess_data["buffer"]["min"]
|
||||||
if candidate["SoC"] == 1 and candidate["site"] == "Unassigned":
|
]
|
||||||
# assign the candidate to the site
|
|
||||||
candidate["site"] = unit["site"]
|
if not units_needing_swap:
|
||||||
break
|
return bess_data, bess_soc_for_cycle
|
||||||
|
|
||||||
|
# identify BESS units that are unassigned and fully charged
|
||||||
|
unassigned_fully_charged = [
|
||||||
|
unit
|
||||||
|
for unit in bess_data["units"]
|
||||||
|
if unit["SoC"] == 1 and unit["site"] == "Unassigned"
|
||||||
|
]
|
||||||
|
|
||||||
|
if not unassigned_fully_charged:
|
||||||
|
return bess_data, bess_soc_for_cycle
|
||||||
|
|
||||||
|
# assign unassigned fully charged units to units needing swap
|
||||||
|
for unit in units_needing_swap:
|
||||||
|
# take the first unassigned fully charged unit
|
||||||
|
new_unit = unassigned_fully_charged.pop(0)
|
||||||
|
# assign it to the site of the unit needing swap
|
||||||
|
new_unit["site"] = unit["site"]
|
||||||
|
# reset SoC to 1 (fully charged)
|
||||||
|
new_unit["SoC"] = 1
|
||||||
|
# set current load to existing load
|
||||||
|
new_unit["current_load_kW"] = unit["current_load_kW"]
|
||||||
|
|
||||||
|
# reset old unit
|
||||||
|
unit["site"] = "Unassigned" # mark the old unit as unassigned
|
||||||
|
unit["current_load_kW"] = 0 # reset current load
|
||||||
|
|
||||||
|
# update the BESS data
|
||||||
|
# search for the index of the unit needing swap and replace it with the new unit
|
||||||
|
index = next(
|
||||||
|
i for i, d in enumerate(bess_data["units"]) if d["name"] == unit["name"]
|
||||||
|
)
|
||||||
|
bess_data["units"][index] = new_unit
|
||||||
|
# search for index of new unit, and replace with old unit
|
||||||
|
new_index = next(
|
||||||
|
i for i, d in enumerate(bess_data["units"]) if d["name"] == new_unit["name"]
|
||||||
|
)
|
||||||
|
bess_data["units"][new_index] = unit
|
||||||
|
|
||||||
|
return bess_data, bess_soc_for_cycle
|
||||||
|
|||||||
67
Utilities/DataVis.py
Normal file
67
Utilities/DataVis.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def format_dataframe(
|
||||||
|
bess_soc_for_cycle, bess_data, load_profiles_since_start, swap_time, current_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",
|
||||||
|
"Estimated Time To Swap",
|
||||||
|
"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")
|
||||||
|
# calculate estimated time to swap
|
||||||
|
if isinstance(predicted_swap_time, float):
|
||||||
|
estimated_time_to_swap = predicted_swap_time - current_time
|
||||||
|
estimated_time_to_swap = pd.to_timedelta(estimated_time_to_swap, unit="s")
|
||||||
|
else:
|
||||||
|
estimated_time_to_swap = "N/A"
|
||||||
|
# convert predicted_swap_time to a readable format
|
||||||
|
if isinstance(predicted_swap_time, float):
|
||||||
|
predicted_swap_time = pd.to_datetime(
|
||||||
|
predicted_swap_time, unit="s"
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
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,
|
||||||
|
"Estimated Time To Swap": estimated_time_to_swap,
|
||||||
|
"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
|
||||||
111
dashboard.py
Normal file
111
dashboard.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# dashboard.py
|
||||||
|
import streamlit as st
|
||||||
|
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")
|
||||||
|
st.title("MEOS Control Dashboard")
|
||||||
|
st.subheader("Mobile Energy Operations Simulation (MEOS)")
|
||||||
|
st.text("Run MEOS Simulation and Monitor MBESS Status")
|
||||||
|
|
||||||
|
# some instructions
|
||||||
|
|
||||||
|
# --- SESSION STATE SETUP ---
|
||||||
|
if "running" not in st.session_state:
|
||||||
|
st.session_state.running = False
|
||||||
|
if "plot_area" not in st.session_state:
|
||||||
|
st.session_state.plot_area = st.empty()
|
||||||
|
|
||||||
|
# --- CONTROL BUTTONS ---
|
||||||
|
col1, col2, col3 = st.columns(3)
|
||||||
|
with col1:
|
||||||
|
if st.button("Start", use_container_width=True):
|
||||||
|
start_sim()
|
||||||
|
st.session_state.running = True
|
||||||
|
with col2:
|
||||||
|
if st.button("Stop", use_container_width=True):
|
||||||
|
stop_sim()
|
||||||
|
st.session_state.running = False
|
||||||
|
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="%.1f%%",
|
||||||
|
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:
|
||||||
|
# display simulation start time
|
||||||
|
st.metric(
|
||||||
|
"Simulation Start Time",
|
||||||
|
value=pd.to_datetime(main.c["sim_start_time"], unit="s").strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
st.metric(
|
||||||
|
"Current Time",
|
||||||
|
value=pd.to_datetime(
|
||||||
|
main.c["sim_start_time"] + main.sim_i * main.dt, unit="s"
|
||||||
|
).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
|
||||||
|
st.metric(
|
||||||
|
"Time Elapsed in DD:HH:MM:SS",
|
||||||
|
value=str(pd.to_timedelta(main.sim_i * main.dt, unit="s")),
|
||||||
|
)
|
||||||
|
# display BESS data, SoC, Load Consumption
|
||||||
|
show_table()
|
||||||
|
time.sleep(1)
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
show_table()
|
||||||
|
st.info("Simulation not running")
|
||||||
200
main.py
200
main.py
@ -3,16 +3,20 @@ import yaml
|
|||||||
from Utilities.Time import get_start_time
|
from Utilities.Time import get_start_time
|
||||||
from Utilities.LoadProfile import get_load_profiles
|
from Utilities.LoadProfile import get_load_profiles
|
||||||
from Utilities.BESS import (
|
from Utilities.BESS import (
|
||||||
|
arrange_swap,
|
||||||
initialise_SoC,
|
initialise_SoC,
|
||||||
initial_site_assignment,
|
initial_site_assignment,
|
||||||
discharge_bess,
|
discharge_bess,
|
||||||
predict_swap_time,
|
predict_swap_time,
|
||||||
update_cycle_SoC,
|
update_cycle_SoC,
|
||||||
)
|
)
|
||||||
|
from Utilities.DataVis import format_dataframe
|
||||||
import matplotlib.pyplot as pl
|
import matplotlib.pyplot as pl
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
import threading ### <<< CONTROL ADDED >>>
|
||||||
|
import time ### <<< CONTROL ADDED >>>
|
||||||
|
|
||||||
# read config file
|
# read config file
|
||||||
c = yaml.safe_load(open("YAMLs/config.yml"))
|
c = yaml.safe_load(open("YAMLs/config.yml"))
|
||||||
@ -21,105 +25,139 @@ c = yaml.safe_load(open("YAMLs/config.yml"))
|
|||||||
bess_data = yaml.safe_load(open(c["paths"]["bess"]))
|
bess_data = yaml.safe_load(open(c["paths"]["bess"]))
|
||||||
|
|
||||||
## simulation time setup
|
## simulation time setup
|
||||||
# get current time
|
|
||||||
c["sim_start_time"] = get_start_time()
|
c["sim_start_time"] = get_start_time()
|
||||||
# get time step in minutes, then convert to seconds
|
|
||||||
dt = c["sim_time"]["time_step_minutes"] * 60
|
dt = c["sim_time"]["time_step_minutes"] * 60
|
||||||
# compute end time based on duration in days
|
|
||||||
duration = c["sim_time"]["duration_days"] * 24 * 60 * 60
|
duration = c["sim_time"]["duration_days"] * 24 * 60 * 60
|
||||||
c["sim_end_time"] = c["sim_start_time"] + duration
|
c["sim_end_time"] = c["sim_start_time"] + duration
|
||||||
timestamps = np.arange(c["sim_start_time"], c["sim_end_time"] + 1, dt)
|
timestamps = np.arange(c["sim_start_time"], c["sim_end_time"] + 1, dt)
|
||||||
|
|
||||||
# batch process hours in seconds
|
|
||||||
c["sim_time"]["batch_process_seconds"] = c["sim_time"]["batch_process_hours"] * 60 * 60
|
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
|
# load site info
|
||||||
c["site_info"] = yaml.safe_load(open(c["paths"]["site_info"]))
|
c["site_info"] = yaml.safe_load(open(c["paths"]["site_info"]))
|
||||||
|
|
||||||
|
|
||||||
def generate_and_cache_profiles(c, dt):
|
### <<< CONTROL ADDED >>> Initialize simulation state globals
|
||||||
"""Generates load profiles for all sites and caches them."""
|
sim_i = 0
|
||||||
return get_load_profiles(
|
running = False
|
||||||
|
is_running_in_async = False
|
||||||
|
sim_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
# initialise BESS
|
||||||
|
def _init_state():
|
||||||
|
global bess_data, bess_soc_since_start, bess_soc_for_cycle, cumulative_load_profiles, load_profiles_since_start, init_df
|
||||||
|
|
||||||
|
bd = initialise_SoC(bess_data.copy())
|
||||||
|
bd = initial_site_assignment(c, bd)
|
||||||
|
bess_data = bd
|
||||||
|
|
||||||
|
bess_soc_since_start = pd.DataFrame(
|
||||||
|
columns=[unit["name"] for unit in bess_data["units"]]
|
||||||
|
)
|
||||||
|
init_df = pd.DataFrame(columns=["Timestamp", "SoC"])
|
||||||
|
bess_soc_for_cycle = {unit["name"]: init_df for unit in bess_data["units"]}
|
||||||
|
|
||||||
|
cumulative_load_profiles = get_load_profiles(
|
||||||
c, dt, c["sim_start_time"], c["sim_time"]["batch_process_seconds"]
|
c, dt, c["sim_start_time"], c["sim_time"]["batch_process_seconds"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# initialise BESS
|
# do initial setup
|
||||||
bess_data = initialise_SoC(bess_data)
|
_init_state()
|
||||||
bess_data = initial_site_assignment(c, bess_data)
|
|
||||||
# bess SoC dataframe
|
|
||||||
bess_soc_since_start = pd.DataFrame(
|
|
||||||
columns=[unit["name"] for unit in bess_data["units"]]
|
|
||||||
)
|
|
||||||
# bess SoC dictionary, meant to track SoC progress over each cycle.
|
|
||||||
# resets after each charging cycle. This is for predicting swap times.
|
|
||||||
init_df = pd.DataFrame(columns=["Timestamp", "SoC"])
|
|
||||||
bess_soc_for_cycle = {unit["name"]: init_df for unit in bess_data["units"]}
|
|
||||||
|
|
||||||
# get initial load profiles
|
|
||||||
cumulative_load_profiles = get_load_profiles(
|
|
||||||
c, dt, c["sim_start_time"], c["sim_time"]["batch_process_seconds"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# async function is running
|
def simulation_loop():
|
||||||
is_running_in_async = False
|
"""Runs the loop, stepping through timestamps until stopped or finished."""
|
||||||
|
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:
|
||||||
|
if not running or sim_i >= len(timestamps):
|
||||||
|
break
|
||||||
|
|
||||||
# loop through
|
i = sim_i
|
||||||
with ThreadPoolExecutor() as executor:
|
sim_i += 1
|
||||||
for i in range(0, len(timestamps)):
|
|
||||||
# start generating load profiles 200 seconds before data required
|
|
||||||
if len(cumulative_load_profiles) <= len(timestamps):
|
|
||||||
if is_running_in_async is False:
|
|
||||||
# generate load profiles
|
|
||||||
future = executor.submit(generate_and_cache_profiles, c, dt)
|
|
||||||
is_running_in_async = True
|
|
||||||
else:
|
|
||||||
is_running_in_async = False
|
|
||||||
|
|
||||||
# check if any BESS units are below threshold (buffer as defined in config)
|
# pre-fetch next batch if needed
|
||||||
|
if len(cumulative_load_profiles) <= len(timestamps):
|
||||||
# discharge BESS for each site
|
if not is_running_in_async:
|
||||||
for site in c["site_info"]["sites"]:
|
future = executor.submit(
|
||||||
site_name = site["name"]
|
get_load_profiles,
|
||||||
discharge_power = cumulative_load_profiles[site_name].iloc[i]
|
c,
|
||||||
bess_data = discharge_bess(bess_data, site_name, dt, discharge_power)
|
dt,
|
||||||
temp_soc = [unit["SoC"] for unit in bess_data["units"]]
|
c["sim_start_time"],
|
||||||
|
c["sim_time"]["batch_process_seconds"],
|
||||||
# append SoC to dataframe
|
)
|
||||||
bess_soc_since_start = pd.concat(
|
is_running_in_async = True
|
||||||
[
|
else:
|
||||||
bess_soc_since_start,
|
|
||||||
pd.DataFrame(
|
|
||||||
[temp_soc],
|
|
||||||
columns=bess_soc_since_start.columns,
|
|
||||||
index=[timestamps[i]],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
axis=0,
|
|
||||||
)
|
|
||||||
|
|
||||||
# update cycle SoC
|
|
||||||
# this is for predicting swap times
|
|
||||||
bess_soc_for_cycle = update_cycle_SoC(bess_data, bess_soc_for_cycle, timestamps)
|
|
||||||
|
|
||||||
# predict swap times
|
|
||||||
swap_times = predict_swap_time(bess_soc_for_cycle)
|
|
||||||
|
|
||||||
# add to cumulative load profiles
|
|
||||||
# check if future exists and is done
|
|
||||||
if is_running_in_async:
|
|
||||||
if future.done():
|
|
||||||
load_profiles = future.result()
|
|
||||||
cumulative_load_profiles = pd.concat(
|
|
||||||
[
|
|
||||||
cumulative_load_profiles,
|
|
||||||
load_profiles,
|
|
||||||
],
|
|
||||||
axis=0,
|
|
||||||
)
|
|
||||||
print(len(cumulative_load_profiles), "load profiles generated")
|
|
||||||
is_running_in_async = False
|
is_running_in_async = False
|
||||||
|
|
||||||
pl.plot(cumulative_load_profiles)
|
# discharge BESS for each site
|
||||||
pl.show()
|
for site in c["site_info"]["sites"]:
|
||||||
pl.plot(bess_soc_since_start)
|
name = site["name"]
|
||||||
|
p = cumulative_load_profiles[name].iloc[i]
|
||||||
|
bess_data = discharge_bess(bess_data, name, dt, p)
|
||||||
|
|
||||||
|
# record SoC
|
||||||
|
temp_soc = [u["SoC"] for u in bess_data["units"]]
|
||||||
|
bess_soc_since_start.loc[timestamps[i]] = temp_soc
|
||||||
|
|
||||||
|
# update cycle SoC and predict swaps
|
||||||
|
bess_soc_for_cycle = update_cycle_SoC(
|
||||||
|
bess_data, bess_soc_for_cycle, timestamps[i]
|
||||||
|
)
|
||||||
|
swap_times = predict_swap_time(bess_soc_for_cycle)
|
||||||
|
|
||||||
|
# trigger swap if needed
|
||||||
|
bess_data, bess_soc_for_cycle = arrange_swap(
|
||||||
|
c, bess_data, bess_soc_for_cycle
|
||||||
|
)
|
||||||
|
|
||||||
|
# integrate newly fetched profiles
|
||||||
|
if is_running_in_async and future.done():
|
||||||
|
load_profiles = future.result()
|
||||||
|
cumulative_load_profiles = pd.concat(
|
||||||
|
[cumulative_load_profiles, load_profiles], axis=0
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
timestamps[i],
|
||||||
|
)
|
||||||
|
|
||||||
|
# small sleep to allow dashboard to refresh / release GIL
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
### <<< CONTROL ADDED >>> Control functions
|
||||||
|
def start_sim():
|
||||||
|
"""Starts the simulation in a background thread."""
|
||||||
|
global running, sim_thread
|
||||||
|
if not running:
|
||||||
|
running = True
|
||||||
|
sim_thread = threading.Thread(target=simulation_loop, daemon=True)
|
||||||
|
sim_thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
def stop_sim():
|
||||||
|
"""Stops the simulation loop."""
|
||||||
|
global running
|
||||||
|
running = False
|
||||||
|
|
||||||
|
|
||||||
|
def reset_sim():
|
||||||
|
"""Stops and re-initializes the simulation state."""
|
||||||
|
global running, sim_i
|
||||||
|
running = False
|
||||||
|
sim_i = 0
|
||||||
|
_init_state()
|
||||||
|
|||||||
BIN
static/EXO2-BLACK.TTF
Normal file
BIN
static/EXO2-BLACK.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-BLACKITALIC.TTF
Normal file
BIN
static/EXO2-BLACKITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-BOLD.TTF
Normal file
BIN
static/EXO2-BOLD.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-BOLDITALIC.TTF
Normal file
BIN
static/EXO2-BOLDITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-EXTRABOLD.TTF
Normal file
BIN
static/EXO2-EXTRABOLD.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-EXTRABOLDITALIC.TTF
Normal file
BIN
static/EXO2-EXTRABOLDITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-EXTRALIGHT.TTF
Normal file
BIN
static/EXO2-EXTRALIGHT.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-EXTRALIGHTITALIC.TTF
Normal file
BIN
static/EXO2-EXTRALIGHTITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-LIGHT.TTF
Normal file
BIN
static/EXO2-LIGHT.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-LIGHTITALIC.TTF
Normal file
BIN
static/EXO2-LIGHTITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-MEDIUM.TTF
Normal file
BIN
static/EXO2-MEDIUM.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-MEDIUMITALIC.TTF
Normal file
BIN
static/EXO2-MEDIUMITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-SEMIBOLD.TTF
Normal file
BIN
static/EXO2-SEMIBOLD.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-SEMIBOLDITALIC.TTF
Normal file
BIN
static/EXO2-SEMIBOLDITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-THIN.TTF
Normal file
BIN
static/EXO2-THIN.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-THINITALIC.TTF
Normal file
BIN
static/EXO2-THINITALIC.TTF
Normal file
Binary file not shown.
BIN
static/EXO2-VARIABLEFONT_WGHT.TTF
Normal file
BIN
static/EXO2-VARIABLEFONT_WGHT.TTF
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user