""" Tab del Simulador - Genera valores de prueba en protocolo ADAM """ import tkinter as tk from tkinter import ttk, scrolledtext, messagebox import threading import time import math from collections import deque from protocol_handler import ProtocolHandler from connection_manager import ConnectionManager from utils import Utils class SimulatorTab: def __init__(self, parent_frame, shared_config): self.frame = parent_frame self.shared_config = shared_config # Estado del simulador self.simulating = False self.simulation_thread = None self.simulation_step = 0 self.connection_manager = ConnectionManager() # Datos para el gráfico self.max_points = 100 self.time_data = deque(maxlen=self.max_points) self.brix_data = deque(maxlen=self.max_points) self.ma_data = deque(maxlen=self.max_points) self.start_time = time.time() # Cargar configuración inicial para obtener valores por defecto para StringVars # Esto es para asegurar que las StringVars tengan un valor inicial antes de que set_config sea llamado # por maselli_app.py. initial_config = self.shared_config['config_manager'].load_config() self.adam_address_var = tk.StringVar(value=initial_config.get('adam_address', '01')) self.function_type_var = tk.StringVar(value=initial_config.get('function_type', 'Lineal')) self.cycle_time_var = tk.StringVar(value=initial_config.get('cycle_time', '10.0')) self.samples_per_cycle_var = tk.StringVar(value=initial_config.get('samples_per_cycle', '100')) self.manual_input_type_var = tk.StringVar(value=initial_config.get('manual_input_type', 'Brix')) self.manual_value_var = tk.StringVar(value=initial_config.get('manual_value', '10.0')) try: manual_value_float = float(initial_config.get('manual_value', '10.0')) except ValueError: manual_value_float = 10.0 # Fallback self.manual_slider_var = tk.DoubleVar(value=manual_value_float) self.current_brix_var = tk.StringVar(value="---") self.current_ma_var = tk.StringVar(value="--.-- mA") self.current_voltage_var = tk.StringVar(value="-.-- V") # Nueva para voltaje self.create_widgets() def create_widgets(self): """Crea los widgets del tab simulador""" # Frame de configuración del simulador config_frame = ttk.LabelFrame(self.frame, text="Configuración Simulador") config_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew", columnspan=2) # Dirección ADAM ttk.Label(config_frame, text="ADAM Address (2c):").grid(row=0, column=0, padx=5, pady=5, sticky="w") # self.adam_address_var inicializada en __init__ self.adam_address_entry = ttk.Entry(config_frame, textvariable=self.adam_address_var, width=5) self.adam_address_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew") # Función ttk.Label(config_frame, text="Función:").grid(row=0, column=2, padx=5, pady=5, sticky="w") # self.function_type_var inicializada en __init__ self.function_type_combo = ttk.Combobox(config_frame, textvariable=self.function_type_var, values=["Lineal", "Sinusoidal", "Manual"], state="readonly", width=10) self.function_type_combo.grid(row=0, column=3, padx=5, pady=5, sticky="ew") self.function_type_combo.bind("<>", self.on_function_type_change) # Tiempo de ciclo completo (nueva característica) ttk.Label(config_frame, text="Tiempo Ciclo (s):").grid(row=0, column=4, padx=5, pady=5, sticky="w") # self.cycle_time_var inicializada en __init__ self.cycle_time_entry = ttk.Entry(config_frame, textvariable=self.cycle_time_var, width=8) self.cycle_time_entry.grid(row=0, column=5, padx=5, pady=5, sticky="ew") # Velocidad de muestreo (calculada automáticamente) ttk.Label(config_frame, text="Muestras/ciclo:").grid(row=0, column=6, padx=5, pady=5, sticky="w") # self.samples_per_cycle_var inicializada en __init__ self.samples_per_cycle_entry = ttk.Entry(config_frame, textvariable=self.samples_per_cycle_var, width=8) self.samples_per_cycle_entry.grid(row=0, column=7, padx=5, pady=5, sticky="ew") # --- Frame para modo Manual (Modificado) --- manual_frame = ttk.LabelFrame(config_frame, text="Modo Manual") manual_frame.grid(row=1, column=0, columnspan=8, padx=5, pady=5, sticky="ew") ttk.Label(manual_frame, text="Entrada Por:").grid(row=0, column=0, padx=5, pady=5, sticky="w") # self.manual_input_type_var inicializada en __init__ self.manual_input_type_combo = ttk.Combobox(manual_frame, textvariable=self.manual_input_type_var, values=["Brix", "mA", "Voltaje"], state="readonly", width=8) self.manual_input_type_combo.grid(row=0, column=1, padx=5, pady=5, sticky="ew") self.manual_input_type_combo.bind("<>", self.on_manual_input_type_change) self.manual_value_label = ttk.Label(manual_frame, text="Valor Brix:") # Se actualiza dinámicamente self.manual_value_label.grid(row=1, column=0, padx=5, pady=5, sticky="w") # self.manual_value_var y self.manual_slider_var inicializadas en __init__ self.manual_value_entry = ttk.Entry(manual_frame, textvariable=self.manual_value_var, width=10, state=tk.DISABLED) self.manual_value_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew") self.manual_value_entry.bind('', lambda e: self.update_slider_from_entry()) self.manual_value_entry.bind('', lambda e: self.update_slider_from_entry()) # Slider self.manual_slider = ttk.Scale(manual_frame, orient=tk.HORIZONTAL, # from_ y to_ se configuran dinámicamente variable=self.manual_slider_var, command=self.on_slider_change, state=tk.DISABLED, length=200) self.manual_slider.grid(row=1, column=2, padx=5, pady=5, sticky="ew") self.manual_send_button = ttk.Button(manual_frame, text="Enviar Manual", command=self.send_manual_value, state=tk.DISABLED) self.manual_send_button.grid(row=1, column=3, padx=5, pady=5, sticky="ew") manual_frame.columnconfigure(2, weight=1) # Controls Frame controls_frame = ttk.LabelFrame(self.frame, text="Control Simulación") controls_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew") self.start_button = ttk.Button(controls_frame, text="Iniciar", command=self.start_simulation) self.start_button.pack(side=tk.LEFT, padx=5) self.stop_button = ttk.Button(controls_frame, text="Detener", command=self.stop_simulation, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=5) self.clear_graph_button = ttk.Button(controls_frame, text="Limpiar Gráfico", command=self.clear_graph) self.clear_graph_button.pack(side=tk.LEFT, padx=5) # Display Frame display_frame = ttk.LabelFrame(self.frame, text="Valores Actuales") display_frame.grid(row=1, column=1, padx=10, pady=5, sticky="ew") ttk.Label(display_frame, text="Brix:").grid(row=0, column=0, padx=5, pady=5, sticky="w") # self.current_brix_var inicializada en __init__ ttk.Label(display_frame, textvariable=self.current_brix_var, font=("Courier", 14, "bold")).grid(row=0, column=1, padx=5, pady=5, sticky="w") ttk.Label(display_frame, text="mA:").grid(row=1, column=0, padx=5, pady=5, sticky="w") # self.current_ma_var inicializada en __init__ ttk.Label(display_frame, textvariable=self.current_ma_var, font=("Courier", 14, "bold")).grid(row=1, column=1, padx=5, pady=5, sticky="w") ttk.Label(display_frame, text="Voltaje:").grid(row=2, column=0, padx=5, pady=5, sticky="w") # self.current_voltage_var inicializada en __init__ ttk.Label(display_frame, textvariable=self.current_voltage_var, font=("Courier", 14, "bold")).grid(row=2, column=1, padx=5, pady=5, sticky="w") # Log Frame log_frame = ttk.LabelFrame(self.frame, text="Log de Comunicación") log_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=5, sticky="nsew") self.log_text = scrolledtext.ScrolledText(log_frame, height=8, width=70, wrap=tk.WORD, state=tk.DISABLED) self.log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True) # Configurar pesos self.frame.columnconfigure(0, weight=1) self.frame.columnconfigure(1, weight=1) self.frame.rowconfigure(2, weight=1) # Log frame # Inicializar estado self.on_function_type_change() def get_graph_frame(self): """Crea y retorna el frame para el gráfico""" graph_frame = ttk.LabelFrame(self.frame, text="Gráfico Simulador") graph_frame.grid(row=3, column=0, columnspan=2, padx=10, pady=5, sticky="nsew") self.frame.rowconfigure(3, weight=1) # Graph frame return graph_frame def on_function_type_change(self, event=None): """Maneja el cambio de tipo de función""" func_type = self.function_type_var.get() if func_type == "Manual": if self.simulating: self.stop_simulation() self.manual_input_type_combo.config(state=tk.NORMAL) self.manual_value_entry.config(state=tk.NORMAL) self.manual_send_button.config(state=tk.NORMAL) self.manual_slider.config(state=tk.NORMAL) self.cycle_time_entry.config(state=tk.DISABLED) self.samples_per_cycle_entry.config(state=tk.DISABLED) self.start_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.DISABLED) self.on_manual_input_type_change() # Configurar según el tipo actual else: self.manual_input_type_combo.config(state=tk.DISABLED) self.manual_value_entry.config(state=tk.DISABLED) self.manual_send_button.config(state=tk.DISABLED) self.manual_slider.config(state=tk.DISABLED) self.cycle_time_entry.config(state=tk.NORMAL) self.samples_per_cycle_entry.config(state=tk.NORMAL) if not self.simulating: self.start_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) def on_manual_input_type_change(self, event=None): """Maneja el cambio de tipo de entrada manual (Brix, mA, Voltaje)""" input_type = self.manual_input_type_var.get() min_val, max_val, default_val, label_text, precision = 0, 100, 10.0, "Valor Brix:", 2 if input_type == "Brix": try: min_val = float(self.shared_config['min_brix_map_var'].get()) max_val = float(self.shared_config['max_brix_map_var'].get()) if min_val >= max_val: min_val, max_val = 0.0, 80.0 # Fallback default_val = min_val + (max_val - min_val) / 4 except (ValueError, KeyError, TypeError): min_val, max_val = 0.0, 80.0 default_val = 10.0 label_text = "Valor Brix:" precision = 2 elif input_type == "mA": min_val, max_val = 0.0, 20.0 default_val = 12.0 label_text = "Valor mA:" precision = 3 elif input_type == "Voltaje": min_val, max_val = 0.0, 10.0 default_val = 5.0 label_text = "Valor Voltaje:" precision = 2 self.manual_value_label.config(text=label_text) self.manual_slider.config(from_=min_val, to=max_val) try: current_numeric_val = float(self.manual_value_var.get()) if not (min_val <= current_numeric_val <= max_val): self.manual_value_var.set(f"{default_val:.{precision}f}") self.manual_slider_var.set(default_val) else: self.manual_slider_var.set(current_numeric_val) self.manual_value_var.set(f"{current_numeric_val:.{precision}f}") except ValueError: self.manual_value_var.set(f"{default_val:.{precision}f}") self.manual_slider_var.set(default_val) def on_slider_change(self, value_str): """Actualiza el valor del entry cuando cambia el slider""" value = float(value_str) input_type = self.manual_input_type_var.get() precision = 2 if input_type == "Brix": precision = 2 elif input_type == "mA": precision = 3 elif input_type == "Voltaje": precision = 2 self.manual_value_var.set(f"{value:.{precision}f}") def update_slider_from_entry(self): """Actualiza el slider cuando cambia el entry""" try: value = float(self.manual_value_var.get()) input_type = self.manual_input_type_var.get() min_val, max_val, precision = 0,100,2 if input_type == "Brix": min_val = float(self.shared_config['min_brix_map_var'].get()) max_val = float(self.shared_config['max_brix_map_var'].get()) if min_val >= max_val: min_val, max_val = 0.0, 80.0 precision = 2 elif input_type == "mA": min_val, max_val, precision = 0.0, 20.0, 3 elif input_type == "Voltaje": min_val, max_val, precision = 0.0, 10.0, 2 value = max(min_val, min(max_val, value)) # Clampear al rango self.manual_slider_var.set(value) self.manual_value_var.set(f"{value:.{precision}f}") except (ValueError, KeyError, TypeError): # Si el valor no es un número o shared_config no está listo, resetear al valor del slider current_slider_val = self.manual_slider_var.get() precision_fallback = 2 if self.manual_input_type_var.get() == "mA": precision_fallback = 3 self.manual_value_var.set(f"{current_slider_val:.{precision_fallback}f}") def send_manual_value(self): """Envía un valor manual único""" try: # Obtener valores de mapeo min_brix_map = float(self.shared_config['min_brix_map_var'].get()) max_brix_map = float(self.shared_config['max_brix_map_var'].get()) adam_address = self.adam_address_var.get() if len(adam_address) != 2: messagebox.showerror("Error", "La dirección ADAM debe tener 2 caracteres.") return if min_brix_map >= max_brix_map: messagebox.showerror("Error de Configuración", "Min Brix debe ser menor que Max Brix.") return input_type = self.manual_input_type_var.get() manual_numeric_value = float(self.manual_value_var.get()) final_brix, final_ma, final_voltage = 0.0, 0.0, 0.0 if input_type == "Brix": final_brix = manual_numeric_value final_ma = ProtocolHandler.scale_to_ma(final_brix, min_brix_map, max_brix_map) final_voltage = ProtocolHandler.ma_to_voltage(final_ma) elif input_type == "mA": final_ma = manual_numeric_value final_brix = ProtocolHandler.ma_to_brix(final_ma, min_brix_map, max_brix_map) final_voltage = ProtocolHandler.ma_to_voltage(final_ma) elif input_type == "Voltaje": final_voltage = manual_numeric_value final_ma = ProtocolHandler.voltage_to_ma(final_voltage) final_brix = ProtocolHandler.ma_to_brix(final_ma, min_brix_map, max_brix_map) # Usar el nuevo método que toma ma_value directamente para el modo manual message, returned_ma = ProtocolHandler.create_adam_message_from_ma(adam_address, final_ma) # Actualizar display brix_display_text = "" # Si la entrada fue mA o Voltaje y el resultado es un mA < 4 (estado de error) if (input_type == "mA" or input_type == "Voltaje") and final_ma < 4.0: brix_display_text = "Error (Sub 4mA)" else: # Para entrada Brix, o mA/Voltaje que resultan en mA >= 4.0 brix_display_text = Utils.format_brix_display(final_brix) self.current_brix_var.set(brix_display_text) self.current_ma_var.set(Utils.format_ma_display(returned_ma)) # Usar returned_ma que es igual a final_ma self.current_voltage_var.set(ProtocolHandler.format_voltage_display(final_voltage)) # Agregar al gráfico # final_brix aquí es el valor numérico calculado (ej. min_brix_map si mA < 4), # lo cual es consistente para el gráfico. self.add_data_point(final_brix, returned_ma) # Enviar por conexión temporal current_config_values = { 'connection_type': self.shared_config['connection_type_var'].get(), 'com_port': self.shared_config['com_port_var'].get(), 'baud_rate': self.shared_config['baud_rate_var'].get(), 'ip_address': self.shared_config['ip_address_var'].get(), 'port': self.shared_config['port_var'].get(), } conn_type = current_config_values['connection_type'] conn_params = self.shared_config['config_manager'].get_connection_params(current_config_values) if conn_type == "TCP-Server": if not self.connection_manager.is_client_connected(): # Verificar el connection_manager de la pestaña Utils.log_message(self.log_text, "Envío Manual (TCP Server): Ningún cliente conectado.") messagebox.showinfo("TCP Server", "Ningún cliente conectado para enviar datos manualmente.") return # No necesitamos 'listening_details' aquí porque la conexión ya está establecida # y el log de inicio ya se hizo. Solo usamos la conexión existente. # La llamada a open_connection no ocurre aquí para TCP-Server en modo manual. try: # message ya es bytes self.connection_manager.send_data(message) Utils.log_message(self.log_text, f"Enviando Manual (TCP Server): {ProtocolHandler.format_for_display(message)}") # No se espera respuesta en modo servidor para el simulador except self.connection_manager.ClientDisconnectedError: Utils.log_message(self.log_text, "Envío Manual (TCP Server): Cliente desconectado durante el envío.") messagebox.showerror("TCP Server Error", "El cliente se desconectó durante el envío manual.") except Exception as e_manual_server: Utils.log_message(self.log_text, f"Error al enviar manualmente (TCP Server): {e_manual_server}") messagebox.showerror("Error", str(e_manual_server)) return # Terminar aquí para envío manual en TCP-Server # Lógica existente para otros tipos de conexión (Serial, TCP Client, UDP) else: temp_conn = ConnectionManager() try: # open_connection ahora devuelve (connection_object, listening_info) _, _ = temp_conn.open_connection(conn_type, conn_params) # Ignoramos listening_info para conexión temporal Utils.log_message(self.log_text, f"Conexión {conn_type} abierta temporalmente.") Utils.log_message(self.log_text, f"Enviando Manual: {ProtocolHandler.format_for_display(message)}") temp_conn.send_data(message) # message ya es bytes response = temp_conn.read_response(timeout=0.5) if response and response.strip(): Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}") parsed = ProtocolHandler.parse_adam_message(response) if parsed: brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map) Utils.log_message(self.log_text, f" -> Addr: {parsed['address']}, " f"mA: {parsed['ma']:.3f}, " f"Brix: {brix_resp:.3f}") except Exception as e: Utils.log_message(self.log_text, f"Error al enviar: {e}") messagebox.showerror("Error", str(e)) finally: temp_conn.close_connection() Utils.log_message(self.log_text, "Conexión cerrada.") except (ValueError, KeyError, TypeError) as e: messagebox.showerror("Error", f"Valores inválidos en la configuración o entrada: {e}") def start_simulation(self): """Inicia la simulación continua""" if self.simulating: messagebox.showwarning("Advertencia", "La simulación ya está en curso.") return try: adam_address = self.adam_address_var.get() if len(adam_address) != 2: messagebox.showerror("Error", "La dirección ADAM debe tener 2 caracteres.") return cycle_time = float(self.cycle_time_var.get()) if cycle_time <= 0: messagebox.showerror("Error", "El tiempo de ciclo debe ser mayor que 0.") return samples_per_cycle = int(self.samples_per_cycle_var.get()) if samples_per_cycle <= 0: messagebox.showerror("Error", "Las muestras por ciclo deben ser mayor que 0.") return # Validar mapeo Brix float(self.shared_config['min_brix_map_var'].get()) float(self.shared_config['max_brix_map_var'].get()) except (ValueError, KeyError, TypeError): messagebox.showerror("Error", "Valores inválidos en la configuración (ADAM, ciclo, muestras o mapeo Brix).") return try: current_config_values = { 'connection_type': self.shared_config['connection_type_var'].get(), 'com_port': self.shared_config['com_port_var'].get(), 'baud_rate': self.shared_config['baud_rate_var'].get(), 'ip_address': self.shared_config['ip_address_var'].get(), 'port': self.shared_config['port_var'].get(), } conn_type = current_config_values['connection_type'] conn_params = self.shared_config['config_manager'].get_connection_params(current_config_values) # open_connection ahora devuelve (connection_object, listening_info) # El connection_object se guarda internamente en self.connection_manager _, listening_details = self.connection_manager.open_connection(conn_type, conn_params) if conn_type == "TCP-Server": Utils.log_message(self.log_text, f"{listening_details} para simulación.") elif conn_type != "TCP-Server": # Para otros tipos, el mensaje genérico Utils.log_message(self.log_text, f"Conexión {conn_type} abierta para simulación.") except Exception as e: messagebox.showerror("Error de Conexión", str(e)) return self.simulating = True self.simulation_step = 0 self.start_time = time.time() # Reset start time for graph self.start_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self._set_entries_state(tk.DISABLED) if conn_type == "TCP-Server": self.shared_config['client_connected_var'].set("Esperando...") self.simulation_thread = threading.Thread(target=self.run_simulation, daemon=True) self.simulation_thread.start() Utils.log_message(self.log_text, "Simulación iniciada.") def stop_simulation(self): """Detiene la simulación""" if not self.simulating: return self.simulating = False if self.simulation_thread and self.simulation_thread.is_alive(): self.simulation_thread.join(timeout=2.0) self.connection_manager.close_connection() Utils.log_message(self.log_text, "Conexión cerrada.") self.start_button.config(state=tk.NORMAL) self.stop_button.config(state=tk.DISABLED) self._set_entries_state(tk.NORMAL) self.on_function_type_change() # Re-evaluar estado de controles manuales if self.connection_manager.connection_type == "TCP-Server": # Limpiar info del cliente self.shared_config['client_connected_var'].set("Ninguno") Utils.log_message(self.log_text, "Simulación detenida.") self.current_brix_var.set("---") self.current_ma_var.set("--.-- mA") self.current_voltage_var.set("-.-- V") def run_simulation(self): """Thread principal de simulación""" try: adam_address = self.adam_address_var.get() min_brix_map = float(self.shared_config['min_brix_map_var'].get()) max_brix_map = float(self.shared_config['max_brix_map_var'].get()) function_type = self.function_type_var.get() cycle_time = float(self.cycle_time_var.get()) samples_per_cycle = int(self.samples_per_cycle_var.get()) conn_type = self.connection_manager.connection_type # Obtener el tipo de conexión actual # Obtener la configuración actual para el log del puerto en TCP-Server current_config_values = { 'connection_type': self.shared_config['connection_type_var'].get(), 'com_port': self.shared_config['com_port_var'].get(), 'baud_rate': self.shared_config['baud_rate_var'].get(), 'ip_address': self.shared_config['ip_address_var'].get(), 'port': self.shared_config['port_var'].get(), } sample_period = cycle_time / samples_per_cycle while self.simulating: current_brix = 0.0 progress = (self.simulation_step % samples_per_cycle) / samples_per_cycle if function_type == "Lineal": cycle_progress = (self.simulation_step % (2 * samples_per_cycle)) / samples_per_cycle if cycle_progress > 1.0: cycle_progress = 2.0 - cycle_progress current_brix = min_brix_map + (max_brix_map - min_brix_map) * cycle_progress elif function_type == "Sinusoidal": phase = progress * 2 * math.pi sin_val = (math.sin(phase) + 1) / 2 current_brix = min_brix_map + (max_brix_map - min_brix_map) * sin_val message, ma_value = ProtocolHandler.create_adam_message(adam_address, current_brix, min_brix_map, max_brix_map) voltage_value = ProtocolHandler.ma_to_voltage(ma_value) self.current_brix_var.set(Utils.format_brix_display(current_brix)) self.current_ma_var.set(Utils.format_ma_display(ma_value)) self.current_voltage_var.set(ProtocolHandler.format_voltage_display(voltage_value)) self.frame.after(0, lambda b=current_brix, m=ma_value: self.add_data_point(b, m)) try: if conn_type == "TCP-Server": if not self.connection_manager.is_client_connected(): # Loguear solo si el estado cambia o periódicamente para evitar spam if not hasattr(self, '_waiting_for_client_logged') or not self._waiting_for_client_logged: port_to_log = self.shared_config['config_manager'].get_connection_params(current_config_values)['port'] Utils.log_message(self.log_text, f"TCP Server: Esperando cliente en puerto {port_to_log}...") self._waiting_for_client_logged = True if self.connection_manager.accept_client(timeout=0.05): # Intento corto no bloqueante Utils.log_message(self.log_text, f"TCP Server: Cliente conectado desde {self.connection_manager.client_address}") client_info = f"{self.connection_manager.client_address[0]}:{self.connection_manager.client_address[1]}" self.shared_config['client_connected_var'].set(client_info) self._waiting_for_client_logged = False # Resetear flag de log elif not self.connection_manager.is_client_connected() and \ self.shared_config['client_connected_var'].get() != "Esperando...": self.shared_config['client_connected_var'].set("Esperando...") Utils.log_message(self.log_text, f"Enviando: {ProtocolHandler.format_for_display(message)}") self.connection_manager.send_data(message) if conn_type != "TCP-Server": # No leer respuesta en modo servidor response = self.connection_manager.read_response(timeout=0.1) if response and response.strip(): Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}") parsed = ProtocolHandler.parse_adam_message(response) if parsed: brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map) Utils.log_message(self.log_text, f" -> Addr: {parsed['address']}, " f"mA: {parsed['ma']:.3f}, " f"Brix: {brix_resp:.3f}") except self.connection_manager.ClientDisconnectedError: Utils.log_message(self.log_text, "TCP Server: Cliente desconectado. Esperando nueva conexión.") if conn_type == "TCP-Server": self.shared_config['client_connected_var'].set("Esperando...") self._waiting_for_client_logged = False # Permitir que se loguee "esperando" de nuevo except Exception as e: Utils.log_message(self.log_text, f"Error en comunicación ({conn_type}): {e}") self.frame.after(0, self.stop_simulation_error) # Schedule GUI update from main thread break self.simulation_step += 1 time.sleep(sample_period) except Exception as e: # Catches errors in parameter fetching or main loop logic Utils.log_message(self.log_text, f"Error en simulación: {e}") if self.simulating: # Ensure stop is called only if an error occurs while simulating self.frame.after(0, self.stop_simulation_error) def stop_simulation_error(self): """Detiene la simulación debido a un error y muestra mensaje""" if self.simulating: # Solo actuar si la simulación estaba activa messagebox.showerror("Error de Simulación", "Error durante la simulación. Simulación detenida.") self.stop_simulation() # Llama al método normal de parada def add_data_point(self, brix_value, ma_value): """Agrega un punto de datos al gráfico""" current_time = time.time() - self.start_time self.time_data.append(current_time) self.brix_data.append(brix_value) self.ma_data.append(ma_value) if hasattr(self, 'graph_update_callback'): self.graph_update_callback() def clear_graph(self): """Limpia los datos del gráfico""" Utils.clear_graph_data(self.time_data, self.brix_data, self.ma_data) self.start_time = time.time() Utils.log_message(self.log_text, "Gráfico limpiado.") if hasattr(self, 'graph_update_callback'): self.graph_update_callback() def _set_entries_state(self, state): """Habilita/deshabilita los controles durante la simulación""" sim_specific_widgets = [ self.adam_address_entry, self.function_type_combo, self.cycle_time_entry, self.samples_per_cycle_entry ] # No deshabilitar controles de modo manual aquí, se manejan en on_function_type_change Utils.set_widgets_state(sim_specific_widgets, state) if 'shared_widgets' in self.shared_config: Utils.set_widgets_state(self.shared_config['shared_widgets'], state) def get_config(self): """Obtiene la configuración actual del simulador""" return { 'adam_address': self.adam_address_var.get(), 'function_type': self.function_type_var.get(), 'cycle_time': self.cycle_time_var.get(), 'samples_per_cycle': self.samples_per_cycle_var.get(), 'manual_input_type': self.manual_input_type_var.get(), 'manual_value': self.manual_value_var.get() } def set_config(self, config): """Establece la configuración del simulador""" self.adam_address_var.set(config.get('adam_address', '01')) self.function_type_var.set(config.get('function_type', 'Lineal')) self.cycle_time_var.set(config.get('cycle_time', '10.0')) self.samples_per_cycle_var.set(config.get('samples_per_cycle', '100')) self.manual_input_type_var.set(config.get('manual_input_type', 'Brix')) self.manual_value_var.set(config.get('manual_value', '10.0')) try: self.manual_slider_var.set(float(self.manual_value_var.get())) except ValueError: # Si el valor no es un float válido, intentar con un default o el valor del tipo # Esto se manejará mejor en on_manual_input_type_change pass self.on_function_type_change() # Esto llamará a on_manual_input_type_change si es necesario