432 lines
20 KiB
Python
432 lines
20 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, cache_frame_data=False
|
|
)
|
|
self.trace_ani = animation.FuncAnimation(
|
|
self.trace_fig, self.update_trace_graph, interval=100, blit=False, cache_frame_data=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) # Notebook
|
|
|
|
# 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", "TCP-Server"], 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=6, padx=5, pady=5, sticky="ew") # Ajustado columnspan
|
|
|
|
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=6, padx=5, pady=5, sticky="ew") # Aumentado columnspan
|
|
self.ethernet_frame.grid_remove()
|
|
|
|
self.ip_address_label_widget = ttk.Label(self.ethernet_frame, text="IP:")
|
|
self.ip_address_label_widget.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)
|
|
|
|
self.port_label_widget = ttk.Label(self.ethernet_frame, text="Puerto:")
|
|
self.port_label_widget.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=(0,5), pady=5) # Ajustado padx
|
|
|
|
# Label para mostrar el cliente conectado en modo TCP-Server
|
|
self.client_connected_label_widget = ttk.Label(self.ethernet_frame, text="Cliente Conectado:")
|
|
# Se mostrará/ocultará en on_connection_type_change
|
|
self.client_connected_var = tk.StringVar(value="Ninguno")
|
|
self.client_connected_display = ttk.Label(self.ethernet_frame, textvariable=self.client_connected_var, width=25)
|
|
|
|
# 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,
|
|
'client_connected_var': self.client_connected_var, # Para actualizar desde el simulador
|
|
'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,
|
|
# self.client_connected_display # No deshabilitar el display, solo su contenido
|
|
]
|
|
})
|
|
|
|
def create_graphs(self):
|
|
"""Crea los gráficos para simulador y trace"""
|
|
# Gráfico del simulador
|
|
# get_graph_frame() ahora devuelve el contenedor LabelFrame donde debe ir el canvas.
|
|
# Este contenedor ya está posicionado por SimulatorTab.create_widgets().
|
|
sim_graph_canvas_parent = 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_canvas_parent) # sim_graph_canvas_parent es un LabelFrame
|
|
self.sim_canvas.draw()
|
|
sim_canvas_widget = self.sim_canvas.get_tk_widget()
|
|
sim_canvas_widget.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
|
# Configurar el LabelFrame (sim_graph_canvas_parent) para que el canvas (su hijo) se expanda
|
|
sim_graph_canvas_parent.rowconfigure(0, weight=1)
|
|
sim_graph_canvas_parent.columnconfigure(0, weight=1)
|
|
|
|
# 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) # trace_graph_frame es un LabelFrame
|
|
self.trace_canvas.draw()
|
|
trace_canvas_widget = self.trace_canvas.get_tk_widget()
|
|
trace_canvas_widget.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
|
|
# Configurar el LabelFrame (trace_graph_frame) para que el canvas (su hijo) se expanda
|
|
trace_graph_frame.rowconfigure(0, weight=1)
|
|
trace_graph_frame.columnconfigure(0, weight=1)
|
|
|
|
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()
|
|
is_server_mode = (conn_type == "TCP-Server")
|
|
|
|
if conn_type == "Serial":
|
|
self.ethernet_frame.grid_remove()
|
|
self.serial_frame.grid()
|
|
# No es necesario manipular los widgets dentro de ethernet_frame si está oculto
|
|
self.client_connected_label_widget.grid_remove()
|
|
self.client_connected_display.grid_remove()
|
|
self.client_connected_var.set("Ninguno")
|
|
elif conn_type == "TCP-Server":
|
|
self.serial_frame.grid_remove()
|
|
self.ethernet_frame.grid()
|
|
|
|
self.ip_address_label_widget.grid_remove() # Ocultar etiqueta IP
|
|
self.ip_address_entry.config(state=tk.DISABLED) # IP no se usa para el servidor
|
|
self.ip_address_entry.grid_remove() # Ocultar campo IP
|
|
|
|
self.port_label_widget.grid(row=0, column=2, padx=5, sticky="w") # Asegurar que la etiqueta Puerto esté visible
|
|
self.port_entry.config(state=tk.NORMAL) # Puerto es para escuchar
|
|
self.port_entry.grid(row=0, column=3, padx=(0,5), pady=5) # Asegurar que el campo Puerto esté visible
|
|
|
|
self.client_connected_label_widget.grid(row=0, column=4, padx=(10,2), pady=5, sticky="w")
|
|
self.client_connected_display.grid(row=0, column=5, padx=(0,5), pady=5, sticky="w")
|
|
else: # TCP, UDP
|
|
self.serial_frame.grid_remove()
|
|
self.ethernet_frame.grid()
|
|
|
|
self.ip_address_label_widget.grid(row=0, column=0, padx=5, sticky="w") # Asegurar que la etiqueta IP esté visible
|
|
self.ip_address_entry.config(state=tk.NORMAL)
|
|
self.ip_address_entry.grid(row=0, column=1, padx=5) # Asegurar que el campo IP esté visible
|
|
|
|
self.port_label_widget.grid(row=0, column=2, padx=5, sticky="w") # Asegurar que la etiqueta Puerto esté visible
|
|
self.port_entry.config(state=tk.NORMAL)
|
|
self.port_entry.grid(row=0, column=3, padx=(0,5), pady=5) # Asegurar que el campo Puerto esté visible
|
|
|
|
self.client_connected_label_widget.grid_remove()
|
|
self.client_connected_display.grid_remove()
|
|
self.client_connected_var.set("Ninguno")
|
|
|
|
# Actualizar info en NetCom
|
|
if hasattr(self, 'netcom_tab'):
|
|
self.netcom_tab.update_net_info()
|
|
|
|
# Habilitar/deshabilitar botones Start en otras pestañas según compatibilidad
|
|
if hasattr(self, 'simulator_tab'):
|
|
# El simulador maneja TCP-Server, su lógica de botón es interna
|
|
pass
|
|
|
|
if hasattr(self, 'trace_tab'):
|
|
if is_server_mode:
|
|
self.trace_tab.start_button.config(state=tk.DISABLED)
|
|
if self.trace_tab.tracing: # Si estaba trazando y el modo cambió
|
|
self.trace_tab.stop_trace()
|
|
messagebox.showinfo("Trace Detenido", "El modo Trace se detuvo porque el tipo de conexión cambió a TCP-Server, que no es compatible.")
|
|
elif not self.trace_tab.tracing : # Habilitar solo si no está trazando
|
|
self.trace_tab.start_button.config(state=tk.NORMAL)
|
|
|
|
if hasattr(self, 'netcom_tab'):
|
|
if is_server_mode:
|
|
self.netcom_tab.start_button.config(state=tk.DISABLED)
|
|
if self.netcom_tab.bridging: # Si estaba en modo bridge
|
|
self.netcom_tab.stop_bridge()
|
|
messagebox.showinfo("NetCom Detenido", "El modo NetCom se detuvo porque el tipo de conexión cambió a TCP-Server, que no es compatible.")
|
|
elif not self.netcom_tab.bridging: # Habilitar solo si no está en modo bridge
|
|
self.netcom_tab.start_button.config(state=tk.NORMAL)
|
|
|
|
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()
|
|
if self.connection_type_var.get() != "TCP-Server":
|
|
self.client_connected_var.set("Ninguno")
|
|
|
|
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()
|