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