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 |             # maintain SoC if not assigned to the site | ||||||
|             new_soc = unit["SoC"] |             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 +73,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,13 +86,15 @@ 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): | def arrange_swap(bess_data, c): | ||||||
|     for unit in bess_data["units"]: |     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 | # dashboard.py | ||||||
| import streamlit as st | import streamlit as st | ||||||
| import matplotlib.pyplot as plt | import matplotlib.pyplot as pl | ||||||
| from main import start_sim, stop_sim, reset_sim, bess_soc_since_start | import pandas as pd | ||||||
|  | import main | ||||||
|  | from main import ( | ||||||
|  |     start_sim, | ||||||
|  |     stop_sim, | ||||||
|  |     reset_sim, | ||||||
|  | ) | ||||||
|  | import time | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | st.set_page_config(layout="wide") | ||||||
| 
 | 
 | ||||||
| # Header | # Header | ||||||
| st.logo("https://rooftop.my/logo.svg", size="large") | st.logo("https://rooftop.my/logo.svg", size="large") | ||||||
| @ -31,3 +41,54 @@ with col3: | |||||||
|     if st.button("Reset", use_container_width=True): |     if st.button("Reset", use_container_width=True): | ||||||
|         reset_sim() |         reset_sim() | ||||||
|         st.session_state.running = False |         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, |     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 | ||||||
| @ -29,10 +30,13 @@ 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) | ||||||
| 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"])) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| ### <<< CONTROL ADDED >>> Initialize simulation state globals | ### <<< CONTROL ADDED >>> Initialize simulation state globals | ||||||
| sim_i = 0 | sim_i = 0 | ||||||
| running = False | running = False | ||||||
| @ -42,7 +46,8 @@ sim_lock = threading.Lock() | |||||||
| 
 | 
 | ||||||
| # initialise BESS | # initialise BESS | ||||||
| def _init_state(): | 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 = initialise_SoC(bess_data.copy()) | ||||||
|     bd = initial_site_assignment(c, bd) |     bd = initial_site_assignment(c, bd) | ||||||
|     bess_data = bd |     bess_data = bd | ||||||
| @ -64,7 +69,7 @@ _init_state() | |||||||
| 
 | 
 | ||||||
| def simulation_loop(): | def simulation_loop(): | ||||||
|     """Runs the loop, stepping through timestamps until stopped or finished.""" |     """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: |     with ThreadPoolExecutor() as executor: | ||||||
|         while True: |         while True: | ||||||
|             with sim_lock: |             with sim_lock: | ||||||
| @ -100,7 +105,7 @@ def simulation_loop(): | |||||||
| 
 | 
 | ||||||
|             # update cycle SoC and predict swaps |             # update cycle SoC and predict swaps | ||||||
|             bess_soc_for_cycle = update_cycle_SoC( |             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) |             swap_times = predict_swap_time(bess_soc_for_cycle) | ||||||
| 
 | 
 | ||||||
| @ -113,15 +118,16 @@ def simulation_loop(): | |||||||
|                 print(len(cumulative_load_profiles), "profiles generated") |                 print(len(cumulative_load_profiles), "profiles generated") | ||||||
|                 is_running_in_async = False |                 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 |             # small sleep to allow dashboard to refresh / release GIL | ||||||
|             time.sleep(0.01) |             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 | ### <<< CONTROL ADDED >>> Control functions | ||||||
| def start_sim(): | def start_sim(): | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user