1

Problem - I have a Python function that reads values from an Excel sheet, performs some calculations, and writes back the data to the Excel sheet. Let's say this entire function takes 1 minute to complete. Now, I have converted my Python file to an .exe file and I am calling this .exe file through a VBA macro, and it is running.

Need Help - Once my code starts running, I need a stop button on the sg window (which I have created) that will stop my running code immediately. Is it possible to stop the Python code externally?

# Create GUI window
window = sg.Window("Peak Shave Simualtion", layout ,icon="C:\Env\Profit_calculator\Tricera-logo.ico") # main window configuration
progress_bar = window['-PROGRESS-']




# Event loop
while True:
    event, values = window.read()
    if event == sg.WIN_CLOSED: # if closed or click on cancel button window will close
        break
    elif event == 'Process': 
        file_path = sys.argv[1]
        start_time = time.time()
        Python_profitcalculator_simulation(file_path)
        end_time = time.time()
        sg.popup(f"Processing complete!\nSimulation total time: {(f'{((end_time - start_time)/60):.2f} minutes' if (end_time - start_time) >= 60 else f'{(end_time - start_time):.2f} seconds')}")
        break
    elif event == 'Stop':
        exit()
        break

# Close GUI window
window.close()

In above code - I tried to create a stop button and when I click on 'Process' my sg.window is freeze and i can not press on button until it completes. So, basically stop button is useless.

Updated - I tried to used flag

import time
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
from peak_shave.peak_shave_reader import Peakshave_Reader
from Seb.profit_calculator_dataclass_neu import *
from peak_shave.raw_data_setup_profit_calculator import Raw_data_from_Peak_shave, peak_shave_final_data
import PySimpleGUI as sg
from tkinter.filedialog import askopenfilename
import sys
import threading
import pythoncom


# Define a flag to indicate whether the function should stop running or not
stop_flag = False


def Python_profitcalculator_simulation(excel_path):

    global stop_flag
    progress_bar.update(10) # 10% of progress for funtion loads
    excel_workbook = Peakshave_Reader(excel_path)
    progress_bar.update(20) # 20% of progress for excel sheet load

    # Reading Peak_shave excel

    Pv_Profile_Raw = Raw_data_from_Peak_shave.PV_profile_raw(excel_workbook)
    Load_Profile_raw = Raw_data_from_Peak_shave.Load_profile_raw(excel_workbook)
    Investtabelle_Raw = Raw_data_from_Peak_shave.Investtabelle_raw_data(excel_workbook)
    battery_raw_Eingabe_Übersicht_sheet = Raw_data_from_Peak_shave.Raw_data_from_Eingabe_Übersicht_sheet(excel_workbook)
    battery_raw_Simulation_sheet = Raw_data_from_Peak_shave.Raw_data_from_Simulation_sheet(excel_workbook)
    battery_raw_AdvanceSettingsDatabase_sheet = Raw_data_from_Peak_shave.Raw_data_from_AdvanceSettingsDatabase_sheet(excel_workbook)
    battery_raw_Wirtschaftlichkeitsmatrix_sheet = Raw_data_from_Peak_shave.Raw_data_from_Wirtschaftlichkeitsmatrix_sheet(excel_workbook)
    Wirtschaftlichkeitsmatrix_Raw = Raw_data_from_Peak_shave.wirtschaftlichkeitsmatrix_sheet_battery_configuration_values(excel_workbook)

    # Convertion of values in SI unit

    PV_Profile = peak_shave_final_data.PVprofile_conversion_setup(Pv_Profile_Raw)
    Load_Profile = peak_shave_final_data.Loadprofile_conversion_setup(Load_Profile_raw)

    Eingabe_Übersicht_sheet = peak_shave_final_data.Eingabe_Übersicht_conversion(battery_raw_Eingabe_Übersicht_sheet)
    Simulation_sheet = peak_shave_final_data.Simulation_convertsion(battery_raw_Simulation_sheet)
    AdvanceSettingsDatabase_sheet = peak_shave_final_data.AdvanceSettingsDatabase_conversion(battery_raw_AdvanceSettingsDatabase_sheet)
    Wirtschaftlichkeitsmatrix_sheet = peak_shave_final_data.Wirtschaftlichkeitsmatrix_conversion(battery_raw_Wirtschaftlichkeitsmatrix_sheet)
    Wirtschaftlichkeitsmatrix_battery_configuration = peak_shave_final_data.Wirtschaftlichkeitsmatrix_battery_configuration_values(Wirtschaftlichkeitsmatrix_Raw)

    #profile setup for simulation
    p_pv = np.array(PV_Profile['PV_profile'])
    p_load = np.array(Load_Profile['Load_profile'])*-1

    
    #general input
    technical_input = dict(
    p_cut_consumption_W = Eingabe_Übersicht_sheet.P_cut_consumption_W,
    PV_W_peak = Eingabe_Übersicht_sheet.PV_Pinstall,
    cp_rate = Eingabe_Übersicht_sheet.Cp_rate,
    bat_capacity_E_Wh = Eingabe_Übersicht_sheet.Battery_energy_nominal,
    DoD_pu = Simulation_sheet.DoD,
    p_reserve_W = Eingabe_Übersicht_sheet.Battery_Pmax_reserve,
    efficiency = Eingabe_Übersicht_sheet.Battery_efficiency,
    SOC_target_pu = Eingabe_Übersicht_sheet.Battery_soll_soc_1,
    SOC_reserve_pu = Eingabe_Übersicht_sheet.Battery_reserve_soc_1
    )
    eco_input = dict(
    cost_pv_per_kWp = Eingabe_Übersicht_sheet.Investment_cost_PV,
    cost_bat = Eingabe_Übersicht_sheet.Investment_cost_battery,
    equity = Eingabe_Übersicht_sheet.Equity_share,
    duration_funding_years = Eingabe_Übersicht_sheet.Financing_period,
    duration_operation_years = Eingabe_Übersicht_sheet.Observation_period
    )

    general = {
    'technical_input' : technical_input,
    'eco_input' : eco_input
    }

    #advanced input
    tech_param = dict(
    SOC_start_pu= AdvanceSettingsDatabase_sheet.Battery_soc_start,
    degradation = AdvanceSettingsDatabase_sheet.Degredation,
    pv_deg_per_year = AdvanceSettingsDatabase_sheet.PV_degradation_per_year, #/a
    bat_deg_per_year = AdvanceSettingsDatabase_sheet.Battery_degradation
    )
    maintanance_spare = dict(
    maintained = AdvanceSettingsDatabase_sheet.Maintained,
    pv_maintain_per_akWp = AdvanceSettingsDatabase_sheet.Maintenance_cost_PV, 
    bat_maintain_per_akWh = AdvanceSettingsDatabase_sheet.Maintenance_cost_battery, 
    pv_spare_per_akWp = AdvanceSettingsDatabase_sheet.PV_spare_per_kWp,
    bat_spare_per_akWh = AdvanceSettingsDatabase_sheet.Bat_spare_per_kWh
    )
    insurance = dict(
    pv_insured = AdvanceSettingsDatabase_sheet.Consider_insurance_PV,
    pv_insurance_per_akWp = AdvanceSettingsDatabase_sheet.PV_insurance, #/kWp * a
    bat_insured = AdvanceSettingsDatabase_sheet.Consider_insurance_bat,
    bat_insurance_per_akWh = AdvanceSettingsDatabase_sheet.Battery_insurance #/kWp * a
    )
    finance = dict(
    borrowed_interest = AdvanceSettingsDatabase_sheet.Borrowing_rate
    )
    tax = dict(
    corporate_tax = AdvanceSettingsDatabase_sheet.Corporate_tax,
    trade_tax = AdvanceSettingsDatabase_sheet.Trade_tax
    )
    discount = dict(
    discounted = AdvanceSettingsDatabase_sheet.Consider_discounting,
    discounting = AdvanceSettingsDatabase_sheet.Discount
    )
    price_per_unit = dict(
    cost_current_jbd_below_per_kWh = AdvanceSettingsDatabase_sheet.Electricity_price_jbd_above_per_kWp,
    cost_current_jbd_above_per_kWh = AdvanceSettingsDatabase_sheet.Electricity_price_jbd_below_per_kWp,
    cost_power_jbd_below_per_kW = Eingabe_Übersicht_sheet.Power_price_jbd_above_per_kWp,
    cost_power_jbd_above_per_kW = Eingabe_Übersicht_sheet.Power_price_jbd_below_per_kWp,
    revenue_injected_current_per_kWh = Eingabe_Übersicht_sheet.Feedin_with_PPA_Direct_feedin_PV,
    staggering_current_per_year = AdvanceSettingsDatabase_sheet.Electricity_price_increase_rate
    )
    lease = dict(
    leased = AdvanceSettingsDatabase_sheet.Consider_lease,
    needed_space_pv_ha_per_MWp = AdvanceSettingsDatabase_sheet.PV_space_requirement,
    needed_space_bat_ha_per_MWh = AdvanceSettingsDatabase_sheet.Battery_space_requirement,
    lease_pv_per_ha = AdvanceSettingsDatabase_sheet.PV_lease,
    lease_bat_per_ha = AdvanceSettingsDatabase_sheet.Battery_lease
    )

    advanced = {
    'tech_param': tech_param,
    'maintanance_spare' : maintanance_spare,
    'insurance' : insurance,
    'finance' : finance,
    'tax' : tax,
    'discount' : discount,
    'price_per_unit' : price_per_unit,
    'lease' : lease
    }

    #other input not important
    other_input = dict(
    abschreibung_pv = 25_000,
    abschreibung_bat = 13_750.66
    )

    profit_calc_params = {
    'general' : general,
    'advanced' : advanced,
    'other_input' : other_input
    }

    
    W_matrix = {'energy_column_1': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_1,
    'energy_column_2' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_2,
    'energy_column_3': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_3,
    'energy_column_4' :Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_4,
    'energy_column_5': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_5,
    'energy_column_6': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_6,
    'energy_column_7' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_7,
    'energy_column_8' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_8,
    'energy_column_9' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_9,
    'energy_column_10' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_10}

    W_matrix_bool = {'energy_column_1': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_1_bool,
    'energy_column_2' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_2_bool,
    'energy_column_3': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_3_bool,
    'energy_column_4' :Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_4_bool,
    'energy_column_5': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_5_bool,
    'energy_column_6': Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_6_bool,
    'energy_column_7' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_7_bool,
    'energy_column_8' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_8_bool,
    'energy_column_9' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_9_bool,
    'energy_column_10' : Wirtschaftlichkeitsmatrix_battery_configuration.energy_column_10_bool}


    W_matrix_C_rate = {'C_Rate_row_1' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_1,
    'C_Rate_row_2' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_2,
    'C_Rate_row_3' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_3,
    'C_Rate_row_4' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_4,
    'C_Rate_row_5' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_5}

    W_matrix_C_rate_bool = {'C_Rate_row_1' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_1_bool,
    'C_Rate_row_2' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_2_bool,
    'C_Rate_row_3' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_3_bool,
    'C_Rate_row_4' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_4_bool,
    'C_Rate_row_5' : Wirtschaftlichkeitsmatrix_battery_configuration.C_Rate_row_5_bool}


    # Investtabelle battery cost setup
    bats = []
    for key in W_matrix:
        if W_matrix_bool[key] == 'berechnen':
            bats.append(W_matrix[key])

    rates = []
    for key in W_matrix_C_rate:
        if W_matrix_C_rate_bool[key] == 'berechnen':
            rates.append(W_matrix_C_rate[key])

    investtable = pd.DataFrame(index = rates, columns = bats)
    for bat in bats:
        for rate in rates:
            if Eingabe_Übersicht_sheet.Battery_type == 'Generisch':
                battery_type_key = 'PB300'
            else:
                battery_type_key = Eingabe_Übersicht_sheet.Battery_type
            try:
                investtable[bat][rate] = Investtabelle_Raw[Eingabe_Übersicht_sheet.Battery_module][battery_type_key][Eingabe_Übersicht_sheet.Installation_site]['energy'+'_'+str(int(bat/1000))+'_C_rate_'+str(rate)]
            except KeyError: 
                investtable[bat][rate]= 0
                print("KeyError:", rate)

    

    battery_type = Eingabe_Übersicht_sheet.Battery_type
    SOC_res1 = Eingabe_Übersicht_sheet.Battery_reserve_soc_1
    SOC_res2 = Eingabe_Übersicht_sheet.Battery_reserve_soc_2
    SOC1_max = Wirtschaftlichkeitsmatrix_sheet.Soll_soc_1_max
    SOC1_min = Wirtschaftlichkeitsmatrix_sheet.Soll_soc_1_min
    SOC2_max = Wirtschaftlichkeitsmatrix_sheet.Soll_soc_2_max
    SOC2_min = Wirtschaftlichkeitsmatrix_sheet.Soll_soc_2_min
    SOC_step_percent = Wirtschaftlichkeitsmatrix_sheet.Step_size_soc
    accuracy = Wirtschaftlichkeitsmatrix_sheet.Break_off_precision_peakshaving
    finanzierung = Wirtschaftlichkeitsmatrix_sheet.Financing
    
    SOC_start = AdvanceSettingsDatabase_sheet.Battery_soc_start
    year_list = get_year_list(Eingabe_Übersicht_sheet.Battery_soll_soc_2_start,Eingabe_Übersicht_sheet.Battery_soll_soc_2_end)

    timestep =0.25 #=15min
    
    progress_bar.update(50) # 50% of progress for pre process done and simulation is starting

    # Simulation
    amount_SOCs = Wirtschaftlichkeitsmatrix_sheet.Number_of_SOCs
    algorithm = Wirtschaftlichkeitsmatrix_sheet.Algorithm_for_12_SOCs
    simulation = Wirtschaftlichkeitsmatrix_sheet.Year_simulation_of_the_best_system

    if amount_SOCs == 2:
        w_matrix, wartung = w_matrix_modular_excel(p_load, p_pv,0,SOC_res1, SOC_res2, SOC_start, SOC1_min, SOC1_max,  SOC2_min, SOC2_max,SOC_step_percent, accuracy, Wirtschaftlichkeitsmatrix_sheet.Consideration_case,finanzierung,bats,rates, profit_calc_params, investtable, battery_type,year_list, timestep)
    else:
        if algorithm == 'dispatcher':
            algo = 1
        elif algorithm == 'brute-force':
            algo = 2
        else: 
            algo = 3
        w_matrix = w_matrix_modular_monthly(algo, p_load, p_pv, SOC_start, SOC1_min, SOC1_max,  SOC_step_percent, Wirtschaftlichkeitsmatrix_sheet.Consideration_case,finanzierung,bats,rates, profit_calc_params, investtable, battery_type, timestep)
    
    system,bat_capacity_E,c_rate = get_most_profitable_system(w_matrix)
    progress_bar.update(80) # 80% of progress for Simulation done and writing results
    if simulation == 'EXCEL' and amount_SOCs == 2:
        Peakshave_Reader.write_Best_system_to_excel(excel_workbook,number_of_SOCs=amount_SOCs,matrix_data=system,bat_energie=bat,c_rate=c_rate)
        Peakshave_Reader.write_matrixdata_SOCs_to_excel(excel_workbook,number_of_SOCs=amount_SOCs,Profit_calculator_matrix_result=w_matrix)
        ##just write the data back,  no simulatioon
    elif simulation == 'BETSI':
        pass
        #results = GlobalFactory
        #write it back
    else:
        results = do_simulation_with_numba(p_load, p_pv, system, bat_capacity_E, c_rate, battery_type, SOC_start, year_list)
        Peakshave_Reader.write_Best_system_to_excel(excel_workbook,number_of_SOCs=amount_SOCs,matrix_data=system,bat_energie=bat,c_rate=c_rate)
        Peakshave_Reader.write_data_to_excel(excel_workbook,data=results)
        Peakshave_Reader.write_matrixdata_SOCs_to_excel(excel_workbook,number_of_SOCs=amount_SOCs,Profit_calculator_matrix_result=w_matrix)
        #write it back  
    progress_bar.update(100) # 100% process completed
    while True:
        # Check the stop flag and break out of the loop if it's set to True
        if stop_flag:
            break


def run_simulation(excel_path):
    global stop_flag
    start_time = time.time()
    # Create a new thread to run the simulation function
    simulation_thread = threading.Thread(target=Python_profitcalculator_simulation, args=(excel_path,))
    simulation_thread.start()
    # Wait for the simulation to complete or for the stop button to be pressed
    while simulation_thread.is_alive():
        event, values = window.read()
        if event == sg.WIN_CLOSED or event == 'Cancel':
            break
        elif event == 'Stop':
            stop_flag = True
            break
    # Calculate the simulation time and show a message
    end_time = time.time()
    if not stop_flag:
        sg.popup(f"Processing complete!\nSimulation total time: {(f'{((end_time - start_time)/60):.2f} minutes' if (end_time - start_time) >= 60 else f'{(end_time - start_time):.2f} seconds')}")

# Define GUI layout
layout = [[sg.Text("Python Peak Shave Simulation:")], # For title
        [sg.ProgressBar(100, orientation='h', size=(20, 20), key='-PROGRESS-')], # progress bar configuration
        [sg.Button("Process"), sg.Button("Stop")]] # Stop button configuration 



# Create GUI window
window = sg.Window("Peak Shave Simulation", layout, icon="C:\Env\Profit_calculator\Tricera-logo.ico") # main window configuration
progress_bar = window['-PROGRESS-']

# Event loop
event, values = window.read()
if event == sg.WIN_CLOSED : # if closed or click on cancel button window will close
    window.close()
elif event == 'Process': 
    excel_path = sys.argv[1]
    if excel_path == '':
        sg.popup('Please select an Excel file.')
    else:
        run_simulation(excel_path)
elif event == 'Stop':
    # This event will only be triggered if the simulation is running
    stop_flag = True
    window.close()
    exit()

I added thread and flag but, i dont know what i am doing wrong. I am getting this error

  File "C:\Python395\lib\threading.py", line 980, in _bootstrap_inner
    self.run()
  File "C:\Python395\lib\threading.py", line 917, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Env\profit_calculator_2.py", line 257, in Python_profitcalculator_simulation
    Peakshave_Reader.write_Best_system_to_excel(excel_workbook,number_of_SOCs=amount_SOCs,matrix_data=system,bat_energie=bat,c_rate=c_rate)
  File "C:\Env\peak_shave\peak_shave_reader.py", line 151, in write_Best_system_to_excel
    app = xw.App(visible=False)
  File "C:\Env\Profit_Calculator_env\lib\site-packages\xlwings\main.py", line 294, in __init__
    self.impl = engines.active.apps.add(
  File "C:\Env\Profit_Calculator_env\lib\site-packages\xlwings\main.py", line 203, in add
    return App(impl=self.impl.add(**kwargs))
  File "C:\Env\Profit_Calculator_env\lib\site-packages\xlwings\_xlwindows.py", line 497, in add
    return App(spec=spec, add_book=add_book, xl=xl, visible=visible)
  File "C:\Env\Profit_Calculator_env\lib\site-packages\xlwings\_xlwindows.py", line 521, in __init__
    self._xl = COMRetryObjectWrapper(DispatchEx("Excel.Application"))
  File "C:\Env\Profit_Calculator_env\lib\site-packages\win32com\client\__init__.py", line 145, in DispatchEx
    dispatch = pythoncom.CoCreateInstanceEx(
pywintypes.com_error: (-2147221008, 'CoInitialize wurde nicht aufgerufen.', None, None)
Akash
  • 11
  • 3
  • This function will run one line at a time, while the python function is busy no other code will get excuted. You can just exit the entire program with a keyboardinterrupt, usually `control + C` – Blazing Blast Feb 24 '23 at 10:13
  • Actually, user can not use control + c because python file is opens in .exe, python function is running in backend and sg.window is frontend – Akash Feb 24 '23 at 10:21
  • 4
    You have to move the calculation to another thread or process. On stop, a flag can be set which is regularly checked by the other thread or the separate process can be killed. In the latter case the writing to the Excel sheet must be handled specially to avoid an incomplete file if process is killed. – Michael Butscher Feb 24 '23 at 10:22
  • I added flag thread, but i am getting error when my program is writing data back to excel. before it was working properly, after using thread all process is working til calculation step, but when function trying to write data back to excel with xlwings i am getting this error, posted below – Akash Feb 24 '23 at 11:11
  • Just run the function in a background process and kill it if you want to stop it. You can check if is still running just checking if the process is till alive – kithuto Feb 24 '23 at 11:36
  • It seems that a Windows COM object was somehow passed between threads which is a problem. See e. g. https://stackoverflow.com/questions/26764978/using-win32com-with-multithreading – Michael Butscher Feb 24 '23 at 11:39

1 Answers1

1

I will do it by therading in PySimpleGUI.

Example Code

from time import sleep
import threading
import PySimpleGUI as sg

def job(window):
    global running
    for i in range(10):
        if not running:
            break
        sleep(1)
        window.write_event_value('Step', i+1)
    window.write_event_value('Done', None)

layout = [
    [sg.Button('Start'), sg.Button('Stop')],
    [sg.StatusBar('', size=20, key='Status')],
]
window = sg.Window('Title', layout, enable_close_attempted_event=True)
running, ending = False, False

while True:

    event, values = window.read()

    if event == sg.WINDOW_CLOSE_ATTEMPTED_EVENT:
        if running:
            running = False
            ending = True
        else:
            break
    elif event == 'Start':
        if not running:
            running = True
            threading.Thread(target=job, args=(window,), daemon=True).start()
    elif event == 'Step':
        window['Status'].update(f'Step {values[event]}/10')
    elif event == 'Stop':
        running = False
        if ending:
            break

window.close()

enter image description here

Jason Yang
  • 11,284
  • 2
  • 9
  • 23
  • If i use range, my function will run more than 1 time or not? and if i am using threading then, in last step writing data back to excel i am getting pythoncom.CoCreateInstanceEx() error – Akash Feb 25 '23 at 11:53
  • `for i in range(10):`, it will run for i from 0 to 9, totally 10 times. Not sure what the `pythoncom.CoCreateInstanceEx()` error is. – Jason Yang Feb 25 '23 at 15:21