""" Aplicación principal del Simulador/Trace Maselli Une todos los módulos y maneja la interfaz principal """ import tkinter as tk from tkinter import ttk, messagebox import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure import matplotlib.animation as animation from config_manager import ConfigManager from utils import Utils from tabs.simulator_tab import SimulatorTab from tabs.trace_tab import TraceTab from tabs.netcom_tab import NetComTab class MaselliApp: def __init__(self, root): self.root = root self.root.title("Simulador/Trace/NetCom Protocolo Maselli") self.root.geometry("1000x800") # Cargar icono Utils.load_icon(self.root) # Gestor de configuración self.config_manager = ConfigManager() self.config = self.config_manager.load_config() # Diccionario para compartir configuración entre tabs self.shared_config = { 'config_manager': self.config_manager } # Crear interfaz self.create_widgets() # Cargar configuración inicial self.load_config_to_gui() # Configurar eventos self.root.protocol("WM_DELETE_WINDOW", self.on_closing) # Inicializar animaciones de gráficos self.sim_ani = animation.FuncAnimation( self.sim_fig, self.update_sim_graph, interval=100, blit=False ) self.trace_ani = animation.FuncAnimation( self.trace_fig, self.update_trace_graph, interval=100, blit=False ) def create_widgets(self): """Crea todos los widgets de la aplicación""" # Frame principal main_frame = ttk.Frame(self.root) main_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # Configurar pesos self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(0, weight=1) main_frame.rowconfigure(1, weight=1) # Frame de configuración compartida self.create_shared_config_frame(main_frame) # Notebook para tabs self.notebook = ttk.Notebook(main_frame) self.notebook.grid(row=1, column=0, sticky="nsew") # Tab Simulador sim_frame = ttk.Frame(self.notebook) self.notebook.add(sim_frame, text="Simulador") self.simulator_tab = SimulatorTab(sim_frame, self.shared_config) # Tab Trace trace_frame = ttk.Frame(self.notebook) self.notebook.add(trace_frame, text="Trace") self.trace_tab = TraceTab(trace_frame, self.shared_config) # Tab NetCom netcom_frame = ttk.Frame(self.notebook) self.notebook.add(netcom_frame, text="NetCom (Gateway)") self.netcom_tab = NetComTab(netcom_frame, self.shared_config) # Crear gráficos self.create_graphs() # Establecer callbacks para actualización de gráficos self.simulator_tab.graph_update_callback = self.update_sim_graph self.trace_tab.graph_update_callback = self.update_trace_graph def create_shared_config_frame(self, parent): """Crea el frame de configuración compartida""" config_frame = ttk.LabelFrame(parent, text="Configuración de Conexión") config_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5)) # Tipo de conexión ttk.Label(config_frame, text="Tipo:").grid(row=0, column=0, padx=5, pady=5, sticky="w") self.connection_type_var = tk.StringVar() self.connection_type_combo = ttk.Combobox( config_frame, textvariable=self.connection_type_var, values=["Serial", "TCP", "UDP"], state="readonly", width=10 ) self.connection_type_combo.grid(row=0, column=1, padx=5, pady=5) self.connection_type_combo.bind("<>", self.on_connection_type_change) # Frame para Serial self.serial_frame = ttk.Frame(config_frame) self.serial_frame.grid(row=0, column=2, columnspan=4, padx=5, pady=5, sticky="ew") ttk.Label(self.serial_frame, text="Puerto:").grid(row=0, column=0, padx=5, sticky="w") self.com_port_var = tk.StringVar() self.com_port_entry = ttk.Entry(self.serial_frame, textvariable=self.com_port_var, width=10) self.com_port_entry.grid(row=0, column=1, padx=5) ttk.Label(self.serial_frame, text="Baud:").grid(row=0, column=2, padx=5, sticky="w") self.baud_rate_var = tk.StringVar() self.baud_rate_entry = ttk.Entry(self.serial_frame, textvariable=self.baud_rate_var, width=10) self.baud_rate_entry.grid(row=0, column=3, padx=5) # Frame para Ethernet self.ethernet_frame = ttk.Frame(config_frame) self.ethernet_frame.grid(row=0, column=2, columnspan=4, padx=5, pady=5, sticky="ew") self.ethernet_frame.grid_remove() ttk.Label(self.ethernet_frame, text="IP:").grid(row=0, column=0, padx=5, sticky="w") self.ip_address_var = tk.StringVar() self.ip_address_entry = ttk.Entry(self.ethernet_frame, textvariable=self.ip_address_var, width=15) self.ip_address_entry.grid(row=0, column=1, padx=5) ttk.Label(self.ethernet_frame, text="Puerto:").grid(row=0, column=2, padx=5, sticky="w") self.port_var = tk.StringVar() self.port_entry = ttk.Entry(self.ethernet_frame, textvariable=self.port_var, width=8) self.port_entry.grid(row=0, column=3, padx=5) # Parámetros de mapeo ttk.Label(config_frame, text="Min Brix [4mA]:").grid(row=1, column=0, padx=5, pady=5, sticky="w") self.min_brix_map_var = tk.StringVar() self.min_brix_map_entry = ttk.Entry(config_frame, textvariable=self.min_brix_map_var, width=10) self.min_brix_map_entry.grid(row=1, column=1, padx=5, pady=5) ttk.Label(config_frame, text="Max Brix [20mA]:").grid(row=1, column=2, padx=5, pady=5, sticky="w") self.max_brix_map_var = tk.StringVar() self.max_brix_map_entry = ttk.Entry(config_frame, textvariable=self.max_brix_map_var, width=10) self.max_brix_map_entry.grid(row=1, column=3, padx=5, pady=5) # Botones ttk.Button(config_frame, text="Guardar Config", command=self.save_config).grid(row=1, column=4, padx=5, pady=5) ttk.Button(config_frame, text="Cargar Config", command=self.load_config).grid(row=1, column=5, padx=5, pady=5) # Guardar referencias para compartir self.shared_config.update({ 'connection_type_var': self.connection_type_var, 'com_port_var': self.com_port_var, 'baud_rate_var': self.baud_rate_var, 'ip_address_var': self.ip_address_var, 'port_var': self.port_var, 'min_brix_map_var': self.min_brix_map_var, 'max_brix_map_var': self.max_brix_map_var, 'shared_widgets': [ self.connection_type_combo, self.com_port_entry, self.baud_rate_entry, self.ip_address_entry, self.port_entry, self.min_brix_map_entry, self.max_brix_map_entry ] }) def create_graphs(self): """Crea los gráficos para simulador y trace""" # Gráfico del simulador sim_graph_frame = self.simulator_tab.get_graph_frame() self.sim_fig = Figure(figsize=(8, 3.5), dpi=100) self.sim_ax1 = self.sim_fig.add_subplot(111) self.sim_ax2 = self.sim_ax1.twinx() self.sim_ax1.set_xlabel('Tiempo (s)') self.sim_ax1.set_ylabel('Brix', color='b') self.sim_ax2.set_ylabel('mA', color='r') self.sim_ax1.tick_params(axis='y', labelcolor='b') self.sim_ax2.tick_params(axis='y', labelcolor='r') self.sim_ax1.grid(True, alpha=0.3) self.sim_line_brix, = self.sim_ax1.plot([], [], 'b-', label='Brix', linewidth=2) self.sim_line_ma, = self.sim_ax2.plot([], [], 'r-', label='mA', linewidth=2) self.sim_canvas = FigureCanvasTkAgg(self.sim_fig, master=sim_graph_frame) self.sim_canvas.draw() self.sim_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Gráfico del trace (ahora con doble eje Y) trace_graph_frame = self.trace_tab.get_graph_frame() self.trace_fig = Figure(figsize=(8, 4), dpi=100) self.trace_ax1 = self.trace_fig.add_subplot(111) self.trace_ax2 = self.trace_ax1.twinx() self.trace_ax1.set_xlabel('Tiempo (s)') self.trace_ax1.set_ylabel('Brix', color='b') self.trace_ax2.set_ylabel('mA', color='r') self.trace_ax1.tick_params(axis='y', labelcolor='b') self.trace_ax2.tick_params(axis='y', labelcolor='r') self.trace_ax1.grid(True, alpha=0.3) self.trace_line_brix, = self.trace_ax1.plot([], [], 'b-', label='Brix', linewidth=2, marker='o', markersize=4) self.trace_line_ma, = self.trace_ax2.plot([], [], 'r-', label='mA', linewidth=2, marker='s', markersize=3) self.trace_canvas = FigureCanvasTkAgg(self.trace_fig, master=trace_graph_frame) self.trace_canvas.draw() self.trace_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) def update_sim_graph(self, frame=None): """Actualiza el gráfico del simulador""" time_data = list(self.simulator_tab.time_data) brix_data = list(self.simulator_tab.brix_data) ma_data = list(self.simulator_tab.ma_data) if len(time_data) > 0: self.sim_line_brix.set_data(time_data, brix_data) self.sim_line_ma.set_data(time_data, ma_data) if len(time_data) > 1: self.sim_ax1.set_xlim(min(time_data), max(time_data)) if brix_data: brix_min = min(brix_data) - 1 brix_max = max(brix_data) + 1 self.sim_ax1.set_ylim(brix_min, brix_max) if ma_data: ma_min = min(ma_data) - 0.5 ma_max = max(ma_data) + 0.5 self.sim_ax2.set_ylim(ma_min, ma_max) self.sim_canvas.draw_idle() return self.sim_line_brix, self.sim_line_ma def update_trace_graph(self, frame=None): """Actualiza el gráfico del trace""" time_data = list(self.trace_tab.time_data) brix_data = list(self.trace_tab.brix_data) ma_data = list(self.trace_tab.ma_data) if len(time_data) > 0: self.trace_line_brix.set_data(time_data, brix_data) self.trace_line_ma.set_data(time_data, ma_data) if len(time_data) > 1: self.trace_ax1.set_xlim(min(time_data), max(time_data)) if brix_data: brix_min = min(brix_data) - 1 brix_max = max(brix_data) + 1 self.trace_ax1.set_ylim(brix_min, brix_max) if ma_data: ma_min = min(ma_data) - 0.5 ma_max = max(ma_data) + 0.5 self.trace_ax2.set_ylim(ma_min, ma_max) self.trace_canvas.draw_idle() return self.trace_line_brix, self.trace_line_ma def on_connection_type_change(self, event=None): """Maneja el cambio de tipo de conexión""" conn_type = self.connection_type_var.get() if conn_type == "Serial": self.ethernet_frame.grid_remove() self.serial_frame.grid() else: self.serial_frame.grid_remove() self.ethernet_frame.grid() # Actualizar info en NetCom if hasattr(self, 'netcom_tab'): self.netcom_tab.update_net_info() def save_config(self): """Guarda la configuración actual""" # Recopilar configuración de todos los componentes config = { 'connection_type': self.connection_type_var.get(), 'com_port': self.com_port_var.get(), 'baud_rate': self.baud_rate_var.get(), 'ip_address': self.ip_address_var.get(), 'port': self.port_var.get(), 'min_brix_map': self.min_brix_map_var.get(), 'max_brix_map': self.max_brix_map_var.get() } # Agregar configuración de cada tab config.update(self.simulator_tab.get_config()) config.update(self.netcom_tab.get_config()) # Validar configuración errors = self.config_manager.validate_config(config) if errors: messagebox.showerror("Error de Configuración", "\n".join(errors)) return # Guardar if self.config_manager.save_config(config): messagebox.showinfo("Éxito", "Configuración guardada correctamente.") else: messagebox.showerror("Error", "No se pudo guardar la configuración.") def load_config(self): """Carga la configuración desde archivo""" self.config = self.config_manager.load_config() self.load_config_to_gui() messagebox.showinfo("Éxito", "Configuración cargada correctamente.") def load_config_to_gui(self): """Carga la configuración en los widgets de la GUI""" # Configuración compartida self.connection_type_var.set(self.config.get('connection_type', 'Serial')) self.com_port_var.set(self.config.get('com_port', 'COM3')) self.baud_rate_var.set(self.config.get('baud_rate', '115200')) self.ip_address_var.set(self.config.get('ip_address', '192.168.1.100')) self.port_var.set(self.config.get('port', '502')) self.min_brix_map_var.set(self.config.get('min_brix_map', '0')) self.max_brix_map_var.set(self.config.get('max_brix_map', '80')) # Configuración específica de cada tab self.simulator_tab.set_config(self.config) self.netcom_tab.set_config(self.config) # Actualizar vista self.on_connection_type_change() def on_closing(self): """Maneja el cierre de la aplicación""" # Detener cualquier operación activa if hasattr(self.simulator_tab, 'simulating') and self.simulator_tab.simulating: self.simulator_tab.stop_simulation() if hasattr(self.trace_tab, 'tracing') and self.trace_tab.tracing: self.trace_tab.stop_trace() if hasattr(self.netcom_tab, 'bridging') and self.netcom_tab.bridging: self.netcom_tab.stop_bridge() # Cerrar ventana self.root.destroy()