I'm working on a project where I've integrated a Tkinter GUI with Matplotlib to monitor voltage and current in a circuit simulation. I have a manual switch button to simulate circuit faults by toggling the switch. However, I'm facing an issue where the voltage and current graphs don't reflect the real-time changes when I toggle the switch.
The code is as follows:
import tkinter as tkimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib.backends.backend_tkagg import FigureCanvasTkAggfrom matplotlib.animation import FuncAnimationt_max = 10.0 # Maximum time (seconds)fps = 20 # Frames per secondt = np.linspace(0, t_max, int(fps * t_max)) # Time sequence# Circuit parametersV_battery = 15 # Battery voltage (V)R_load = 10 # Load resistance (ohms)class CircuitSimulator: def __init__(self, t_values, initial_switch_state=True): self.t_values = t_values self.VoltageOverTime = np.full_like(t_values, V_battery) # All time point voltages are initially battery voltage self.CurrentOverTime = np.full_like(t_values, V_battery / R_load) # Initialize current array self.switch_states = np.ones_like(t_values, dtype=bool) * initial_switch_state # Record the state of the switch at each time point self.previous_switch_state = initial_switch_state # Record the previous switch state self.previous_switch_time_index = -1 # Initialize to -1, indicating no switch has been made def calculate_circuit_response(self, current_time_index): # Check if there is a switch change at the current time point if current_time_index > self.previous_switch_time_index: # Check if the switch state at the current time point is different from the previous time point if self.switch_states[current_time_index] != self.switch_states[current_time_index - 1]: self.previous_switch_state = self.switch_states[current_time_index] next_switch_index = current_time_index + np.argmax(self.switch_states[current_time_index:] != self.switch_states[current_time_index]) if not self.previous_switch_state: # Switch is open self.VoltageOverTime[current_time_index:next_switch_index] = 0 self.CurrentOverTime[current_time_index:next_switch_index] = 0 else: # Switch is closed self.VoltageOverTime[current_time_index:next_switch_index] = V_battery * np.ones_like(self.VoltageOverTime[current_time_index:next_switch_index]) self.CurrentOverTime[current_time_index:next_switch_index] = V_battery / R_load * np.ones_like(self.CurrentOverTime[current_time_index:next_switch_index]) # Update the time index of the last switch change self.previous_switch_time_index = next_switch_index def toggle_switch_at_time(self, switch_time_index): if 0 <= switch_time_index < len(self.switch_states): # Only change the switch state at the specified time point and the defined time period after it end_of_simulation = min(switch_time_index + 1, len(self.switch_states)) # Prevent overflow self.switch_states[switch_time_index:end_of_simulation] = not self.switch_states[switch_time_index] else: raise IndexError(f"Invalid time index: {switch_time_index}. Maximum allowed index is {len(self.switch_states) - 1}")# Create a new window class for integrating matplotlib charts with Tkinter GUIclass CircuitSimulationGUI(tk.Tk): def __init__(self): super().__init__() # Set window title self.title("Circuit Operation Monitoring") # Create the main container container = tk.Frame(self) container.pack(side="top", fill="both", expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) # Initialize the chart self.fig, self.axs = plt.subplots(2, 1, figsize=(10, 6)) self.V_line, = self.axs[0].plot([], [], label='Circuit Voltage (V)') self.I_line, = self.axs[1].plot([], [], label='Circuit Current (A)', color='r') for ax in self.axs: ax.set_xlabel('Time (s)') ax.set_ylabel('Value') ax.legend() # Embed the matplotlib chart into the Tkinter window self.canvas = FigureCanvasTkAgg(self.fig, master=container) self.canvas.draw() self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) # Create the switch button self.manual_switch_button = tk.Button(master=self, text="Close Circuit", command=self.toggle_manual_switch) self.manual_switch_button.pack(pady=10) # Initialize the animation self.simulator = CircuitSimulator(t) self.ani = None # Initialize the animation instance variable to None # Initialize state variables self.current_time_index = 0 # Start the simulation self.start_simulation() def toggle_manual_switch(self):""" Handle the click event of the manual switch button, toggle the switch state at the current time point and continue to affect subsequent states""" # Get the index of the current time point current_index = int(self.current_time_index) # Toggle the switch state self.simulator.toggle_switch_at_time(current_index) self.simulator.calculate_circuit_response(current_index) # Update the button text and command if self.manual_switch_button["text"] == "Close Circuit": self.manual_switch_button["text"] = "Open Circuit" else: self.manual_switch_button["text"] = "Close Circuit" # Set the transition duration (assume 0.5 seconds) transition_duration = 0.5 # in seconds transition_frames = int(transition_duration * fps) # Get the start and end time indices of the transition start_index = current_index end_index = min(start_index + transition_frames, len(t)) # Transition voltage and current arrays step by step for i in range(start_index, end_index): transition_ratio = (i - start_index) / transition_frames if self.manual_switch_button["text"] == "Open Circuit": # If the circuit is opened # Transition from battery voltage and current to 0 self.simulator.VoltageOverTime[i] = (1 - transition_ratio) * V_battery self.simulator.CurrentOverTime[i] = (1 - transition_ratio) * (V_battery / R_load) else: # Transition from 0 to battery voltage and current self.simulator.VoltageOverTime[i] = transition_ratio * V_battery self.simulator.CurrentOverTime[i] = transition_ratio * (V_battery / R_load) # Update the entire chart, pass the index of the current time point self.update_plot(current_index) self.canvas.draw_idle() def start_simulation(self): # Use a more modern and compatible way to detect and control animation status if self.ani is None: self.ani = FuncAnimation( fig=self.fig, func=self.update_plot, # Pass the function instead of the method frames=len(t), interval=200, blit=True ) self.canvas.draw_idle() elif getattr(self.ani, '_is_running', False): # Change here self.ani.event_source.stop() self.ani.event_source.start() else: self.ani.event_source.start() def update_plot(self, frame): self.simulator.calculate_circuit_response(frame) time = t[frame] V_circuit = self.simulator.VoltageOverTime[:frame+1] I_circuit = self.simulator.CurrentOverTime[:frame+1] self.V_line.set_data(t[:len(V_circuit)], V_circuit) self.I_line.set_data(t[:len(I_circuit)], I_circuit) self.axs[0].set_xlim(0, t_max) self.axs[1].set_xlim(0, t_max) self.axs[0].set_ylim(0, 20) self.axs[1].set_ylim(0, 2) print("Plot updated") # Add this line of code to confirm if the chart has been updated print("Plot Voltage:", V_circuit[-1], "V") return self.V_line, self.I_line# Create and start the GUI applicationif __name__ == "__main__": app = CircuitSimulationGUI() app.mainloop()I've tried updating the voltage and current arrays based on the switch state inside the toggle_manual_switch method, followed by updating the plot. I expected the voltage and current graphs to dynamically update with each switch toggle, showing the changes in real-time. However, the graphs only update after the simulation ends, not during the simulation.