MaselliSimulatorApp/maselli_app.py

355 lines
15 KiB
Python

"""
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("<<ComboboxSelected>>", 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()