Separado de eventos en el log de los eventos ciclicos
This commit is contained in:
parent
d7d52b2565
commit
fe0abb9965
|
@ -33,8 +33,6 @@ class SimulatorTab:
|
|||
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'))
|
||||
|
@ -54,7 +52,7 @@ class SimulatorTab:
|
|||
|
||||
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.current_voltage_var = tk.StringVar(value="-.-- V")
|
||||
|
||||
# Para simulación de errores
|
||||
self.random_error_timer = None
|
||||
|
@ -66,69 +64,54 @@ class SimulatorTab:
|
|||
|
||||
self.actual_graph_frame_container = None # Inicializar ANTES de create_widgets
|
||||
self.create_widgets()
|
||||
# La línea anterior que asignaba None aquí ha sido eliminada.
|
||||
|
||||
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("<<ComboboxSelected>>", 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("<<ComboboxSelected>>", 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 = ttk.Label(manual_frame, text="Valor Brix:")
|
||||
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('<Return>', lambda e: self.update_slider_from_entry())
|
||||
self.manual_value_entry.bind('<FocusOut>', 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
|
||||
self.manual_slider = ttk.Scale(manual_frame, orient=tk.HORIZONTAL,
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
|
@ -138,90 +121,75 @@ class SimulatorTab:
|
|||
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_comm_log_button = ttk.Button(controls_frame, text="Limpiar Log Com.", command=self.clear_comm_log)
|
||||
self.clear_comm_log_button.pack(side=tk.LEFT, padx=5)
|
||||
self.clear_cyclic_log_button = ttk.Button(controls_frame, text="Limpiar Log Cíclico", command=self.clear_cyclic_log)
|
||||
self.clear_cyclic_log_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="nsew")
|
||||
|
||||
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")
|
||||
|
||||
# Event Log Frame (antes era el log principal)
|
||||
event_log_frame = ttk.LabelFrame(self.frame, text="Log de Eventos")
|
||||
event_log_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=5, sticky="nsew")
|
||||
|
||||
self.event_log_text = scrolledtext.ScrolledText(event_log_frame, height=8, width=70, wrap=tk.WORD, state=tk.DISABLED)
|
||||
self.event_log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
|
||||
|
||||
# --- Frame para Comunicación y Gráfico ---
|
||||
comm_and_graph_parent_frame = ttk.Frame(self.frame)
|
||||
comm_and_graph_parent_frame.grid(row=3, column=0, columnspan=2, padx=10, pady=5, sticky="nsew")
|
||||
comm_and_graph_parent_frame.columnconfigure(0, weight=1) # Comm log
|
||||
comm_and_graph_parent_frame.columnconfigure(1, weight=1) # Graph
|
||||
comm_and_graph_parent_frame.rowconfigure(0, weight=1) # Ambos toman la altura completa de esta fila
|
||||
comm_and_graph_parent_frame.columnconfigure(0, weight=1)
|
||||
comm_and_graph_parent_frame.columnconfigure(1, weight=1)
|
||||
comm_and_graph_parent_frame.rowconfigure(0, weight=1)
|
||||
|
||||
# Comm Log Frame (Nuevo)
|
||||
comm_log_frame = ttk.LabelFrame(comm_and_graph_parent_frame, text="Log de Comunicación (Simulador)")
|
||||
comm_log_frame.grid(row=0, column=0, padx=(0, 5), pady=0, sticky="nsew")
|
||||
comm_log_frame.rowconfigure(0, weight=1)
|
||||
comm_log_frame.columnconfigure(0, weight=1)
|
||||
cyclic_log_frame = ttk.LabelFrame(comm_and_graph_parent_frame, text="Log Cíclico (Simulador)")
|
||||
cyclic_log_frame.grid(row=0, column=0, padx=(0, 5), pady=0, sticky="nsew")
|
||||
cyclic_log_frame.rowconfigure(0, weight=1)
|
||||
cyclic_log_frame.columnconfigure(0, weight=1)
|
||||
|
||||
self.comm_log_text = scrolledtext.ScrolledText(comm_log_frame, height=10, width=70, wrap=tk.WORD, state=tk.DISABLED)
|
||||
self.comm_log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
|
||||
self.cyclic_log_text = scrolledtext.ScrolledText(cyclic_log_frame, height=10, width=70, wrap=tk.WORD, state=tk.DISABLED)
|
||||
self.cyclic_log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Graph Frame Container (el gráfico se insertará aquí por MaselliApp)
|
||||
# self.get_graph_frame() ahora devuelve este contenedor.
|
||||
self.actual_graph_frame_container = ttk.LabelFrame(comm_and_graph_parent_frame, text="Gráfico Simulador")
|
||||
self.actual_graph_frame_container.grid(row=0, column=1, padx=(5, 0), pady=0, sticky="nsew")
|
||||
|
||||
# --- Frame para Simulación de Errores ---
|
||||
self._setup_error_simulation_ui() # Se añade al final de create_widgets
|
||||
self._setup_error_simulation_ui()
|
||||
|
||||
# Configurar pesos
|
||||
self.frame.columnconfigure(0, weight=1)
|
||||
self.frame.columnconfigure(1, weight=1)
|
||||
self.frame.rowconfigure(2, weight=1) # Event log frame
|
||||
self.frame.rowconfigure(3, weight=3) # Comm and Graph parent frame (más altura)
|
||||
self.frame.rowconfigure(4, weight=0) # Error frame no se expande tanto
|
||||
self.frame.rowconfigure(2, weight=1)
|
||||
self.frame.rowconfigure(3, weight=3)
|
||||
self.frame.rowconfigure(4, weight=0)
|
||||
|
||||
# Inicializar estado
|
||||
self.on_function_type_change()
|
||||
|
||||
def get_graph_frame(self):
|
||||
"""Retorna el frame contenedor donde se debe dibujar el gráfico del simulador."""
|
||||
return self.actual_graph_frame_container
|
||||
|
||||
def _setup_error_simulation_ui(self):
|
||||
"""Crea los controles para la simulación de errores."""
|
||||
error_frame = ttk.LabelFrame(self.frame, text="Simulación de Errores (Modo TCP Server)")
|
||||
error_frame.grid(row=4, column=0, columnspan=2, padx=10, pady=10, sticky="ew")
|
||||
self.frame.rowconfigure(4, weight=0) # Error frame no se expande tanto como el log o gráfico
|
||||
|
||||
ttk.Label(error_frame, text="Tipo de Error:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
self.error_type_var = tk.StringVar(value="Ninguno")
|
||||
self.error_type_combo = ttk.Combobox(
|
||||
error_frame,
|
||||
textvariable=self.error_type_var,
|
||||
state="disabled", # Se habilita/deshabilita dinámicamente
|
||||
state="disabled",
|
||||
values=[
|
||||
"Ninguno", # Para enviar una trama normal desde este control
|
||||
"Ninguno",
|
||||
"ID Erróneo",
|
||||
"Valor Fuera de Escala (mA)",
|
||||
"Checksum Erróneo",
|
||||
|
@ -239,74 +207,58 @@ class SimulatorTab:
|
|||
self.random_error_var = tk.BooleanVar(value=False)
|
||||
self.random_error_check = ttk.Checkbutton(
|
||||
error_frame,
|
||||
text="Errores Aleatorios (cada ~10s)",
|
||||
text="Errores Aleatorios",
|
||||
variable=self.random_error_var,
|
||||
command=self.toggle_random_errors,
|
||||
state="disabled" # Se habilita/deshabilita dinámicamente
|
||||
state="disabled"
|
||||
)
|
||||
self.random_error_check.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
self.random_error_check.grid(row=1, column=0, columnspan=1, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Checkbox para reemplazar trama normal con error (ahora en su propia fila para claridad)
|
||||
self.replace_with_error_check = ttk.Checkbutton(
|
||||
error_frame,
|
||||
text="Reemplazar trama normal con error",
|
||||
variable=self.replace_normal_with_error_var,
|
||||
state="disabled" # Se habilita/deshabilita dinámicamente
|
||||
state="disabled"
|
||||
)
|
||||
self.replace_with_error_check.grid(row=1, column=2, padx=(10,5), pady=5, sticky="w")
|
||||
|
||||
ttk.Label(error_frame, text="Intervalo Errores Aleatorios (s):").grid(row=2, column=0, padx=5, pady=5, sticky="w")
|
||||
ttk.Label(error_frame, text="Intervalo Err. Aleat. (s):").grid(row=2, column=0, padx=5, pady=5, sticky="w")
|
||||
self.random_error_interval_entry = ttk.Entry(
|
||||
error_frame,
|
||||
textvariable=self.random_error_interval_var,
|
||||
width=8, # El Entry solo necesita el parent, textvariable, width y state.
|
||||
state="disabled" # Se habilita/deshabilita dinámicamente
|
||||
width=8,
|
||||
state="disabled"
|
||||
)
|
||||
self.random_error_interval_entry.grid(row=2, column=1, padx=5, pady=5, sticky="ew") # Añadir el grid para el Entry
|
||||
# El grid para self.replace_with_error_check ya está definido donde se crea ese widget.
|
||||
self.random_error_interval_entry.grid(row=2, column=1, padx=5, pady=5, sticky="ew")
|
||||
error_frame.columnconfigure(1, weight=1)
|
||||
|
||||
self.update_error_controls_state() # Establecer estado inicial
|
||||
self.update_error_controls_state()
|
||||
|
||||
def update_error_controls_state(self):
|
||||
"""Habilita o deshabilita los controles de error según el modo de conexión."""
|
||||
# Asegurarse de que los widgets de error existan antes de intentar configurarlos
|
||||
if not hasattr(self, 'error_type_combo'):
|
||||
return
|
||||
|
||||
is_tcp_server_mode = self.shared_config['connection_type_var'].get() == "TCP-Server"
|
||||
# Considerar si la simulación (conexión) está activa para habilitar el envío
|
||||
# is_connection_active = self.simulating # O una propiedad más directa de ConnectionManager
|
||||
|
||||
# Los controles de error solo tienen sentido si estamos en modo TCP-Server
|
||||
# y la conexión está activa (es decir, la simulación principal está corriendo o
|
||||
# el servidor está escuchando de alguna forma).
|
||||
# Por ahora, lo basaremos en is_tcp_server_mode y self.simulating
|
||||
|
||||
enable_controls = is_tcp_server_mode and self.simulating
|
||||
|
||||
new_state_tk = tk.NORMAL if enable_controls else tk.DISABLED
|
||||
new_state_str = "normal" if enable_controls else "disabled" # Para Checkbutton
|
||||
new_state_str = "normal" if enable_controls else "disabled"
|
||||
|
||||
self.error_type_combo.config(state=new_state_tk if is_tcp_server_mode else tk.DISABLED) # Combo siempre según modo
|
||||
self.error_type_combo.config(state=new_state_tk if is_tcp_server_mode else tk.DISABLED)
|
||||
self.send_error_button.config(state=new_state_tk)
|
||||
self.random_error_check.config(state=new_state_str)
|
||||
|
||||
# El entry del intervalo de errores aleatorios depende de que el check de errores aleatorios esté activo
|
||||
interval_entry_state_tk = tk.NORMAL if enable_controls and self.random_error_var.get() else tk.DISABLED
|
||||
self.random_error_interval_entry.config(state=interval_entry_state_tk)
|
||||
|
||||
# El check de "Reemplazar trama normal" se habilita si los controles de error están habilitados
|
||||
self.replace_with_error_check.config(state=new_state_str)
|
||||
|
||||
if not enable_controls and self.random_error_var.get():
|
||||
self.random_error_var.set(False)
|
||||
self.toggle_random_errors() # Detiene el timer si estaba activo y se deshabilitan controles
|
||||
self.toggle_random_errors()
|
||||
|
||||
def get_current_error_sim_parameters(self):
|
||||
"""Obtiene parámetros para la simulación de errores (dirección ADAM, valor mA base)."""
|
||||
adam_address = self.adam_address_var.get()
|
||||
base_ma_value = 12.345 # Valor por defecto
|
||||
base_ma_value = 12.345
|
||||
|
||||
if self.function_type_var.get() == "Manual":
|
||||
try:
|
||||
|
@ -315,57 +267,42 @@ class SimulatorTab:
|
|||
if input_type == "Brix":
|
||||
min_b = float(self.shared_config['min_brix_map_var'].get())
|
||||
max_b = float(self.shared_config['max_brix_map_var'].get())
|
||||
base_ma_value = ProtocolHandler.scale_to_ma(manual_val, min_b, max_b)
|
||||
elif input_type == "mA": # noqa: E721
|
||||
base_ma_value = ProtocolHandler.scale_to_ma(manual_val, min_b, max_b) # noqa: E701
|
||||
elif input_type == "mA":
|
||||
base_ma_value = manual_val
|
||||
elif input_type == "Voltaje":
|
||||
base_ma_value = ProtocolHandler.voltage_to_ma(manual_val)
|
||||
except (ValueError, KeyError, TypeError):
|
||||
Utils.log_message(self.log_text, "Error Sim: Usando valor mA base por defecto para error.")
|
||||
else: # Si no es manual, o para tener un valor si la simulación principal no corre
|
||||
# Podríamos tomar el self.current_ma_var si la simulación está corriendo
|
||||
# pero para simplicidad, un valor fijo si no es manual.
|
||||
pass # Mantiene 12.345
|
||||
|
||||
Utils.log_message(self.event_log_text, "Error Sim: Usando valor mA base por defecto para error.")
|
||||
return adam_address, base_ma_value
|
||||
|
||||
def on_function_type_change(self, event=None):
|
||||
"""Maneja el cambio de tipo de función"""
|
||||
func_type = self.function_type_var.get()
|
||||
is_manual_mode = (func_type == "Manual")
|
||||
|
||||
# Si la simulación está corriendo y el tipo de función cambia, detenerla.
|
||||
if self.simulating:
|
||||
self.stop_simulation()
|
||||
|
||||
# Configurar controles de entrada manual
|
||||
manual_specific_state = tk.NORMAL if is_manual_mode else tk.DISABLED
|
||||
self.manual_input_type_combo.config(state=manual_specific_state)
|
||||
self.manual_value_entry.config(state=manual_specific_state)
|
||||
self.manual_slider.config(state=manual_specific_state)
|
||||
|
||||
# Tiempo de ciclo y muestras por ciclo ahora están habilitados para todos los modos continuos
|
||||
self.cycle_time_entry.config(state=tk.NORMAL)
|
||||
self.samples_per_cycle_entry.config(state=tk.NORMAL)
|
||||
|
||||
if is_manual_mode:
|
||||
self.on_manual_input_type_change() # Actualizar rangos de slider/entry y valor actual
|
||||
self.on_manual_input_type_change()
|
||||
|
||||
# El estado de los botones Start/Stop depende de si la simulación está (o estaba) corriendo.
|
||||
# Como stop_simulation() se llama arriba si estaba corriendo, self.simulating debería ser False aquí.
|
||||
if not self.simulating:
|
||||
self.start_button.config(state=tk.NORMAL)
|
||||
self.stop_button.config(state=tk.DISABLED)
|
||||
else:
|
||||
# Este estado idealmente no se alcanzaría si stop_simulation()
|
||||
# establece correctamente self.simulating a False y actualiza los botones.
|
||||
# Sin embargo, como salvaguarda:
|
||||
self.start_button.config(state=tk.DISABLED)
|
||||
self.stop_button.config(state=tk.NORMAL)
|
||||
self.update_error_controls_state() # Actualizar estado de controles de error
|
||||
self.update_error_controls_state()
|
||||
|
||||
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
|
||||
|
||||
|
@ -373,14 +310,14 @@ class SimulatorTab:
|
|||
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
|
||||
if min_val >= max_val: min_val, max_val = 0.0, 80.0
|
||||
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 # noqa: F841
|
||||
default_val = 10.0
|
||||
label_text = "Valor Brix:"
|
||||
precision = 2
|
||||
elif input_type == "mA": # noqa: E721
|
||||
elif input_type == "mA":
|
||||
min_val, max_val = 0.0, 20.0
|
||||
default_val = 12.0
|
||||
label_text = "Valor mA:"
|
||||
|
@ -407,17 +344,15 @@ class SimulatorTab:
|
|||
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 # noqa: E701
|
||||
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()
|
||||
|
@ -428,21 +363,19 @@ class SimulatorTab:
|
|||
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 # noqa: E701
|
||||
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
|
||||
value = max(min_val, min(max_val, value))
|
||||
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 start_simulation(self):
|
||||
"""Inicia la simulación continua"""
|
||||
if self.simulating:
|
||||
messagebox.showwarning("Advertencia", "La simulación ya está en curso.")
|
||||
return
|
||||
|
@ -452,21 +385,16 @@ class SimulatorTab:
|
|||
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
|
||||
|
@ -481,44 +409,39 @@ class SimulatorTab:
|
|||
}
|
||||
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.comm_log_text, f"{listening_details} para simulación.")
|
||||
elif conn_type != "TCP-Server": # Para otros tipos, el mensaje genérico
|
||||
Utils.log_message(self.comm_log_text, f"Conexión {conn_type} abierta para simulación.")
|
||||
Utils.log_message(self.event_log_text, f"{listening_details} para simulación.")
|
||||
elif conn_type != "TCP-Server":
|
||||
Utils.log_message(self.event_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_time = time.time()
|
||||
self.start_button.config(state=tk.DISABLED)
|
||||
self.stop_button.config(state=tk.NORMAL)
|
||||
self._set_entries_state(tk.DISABLED)
|
||||
self.update_error_controls_state() # Habilitar controles de error si es TCP Server
|
||||
self.update_error_controls_state()
|
||||
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() # noqa: E701
|
||||
self.simulation_thread.start()
|
||||
Utils.log_message(self.event_log_text, "Simulación iniciada.")
|
||||
|
||||
def stop_simulation(self):
|
||||
"""Detiene la simulación"""
|
||||
if not self.simulating:
|
||||
return
|
||||
|
||||
self.simulating = False
|
||||
|
||||
# Detener el timer de errores aleatorios primero
|
||||
if self.random_error_timer and self.random_error_timer.is_alive():
|
||||
self.random_error_timer_stop_event.set()
|
||||
self.random_error_timer.join(timeout=1.0) # Esperar un poco
|
||||
self.random_error_timer.join(timeout=1.0)
|
||||
self.random_error_timer = None
|
||||
self.next_frame_is_error_event.clear()
|
||||
self.error_details_for_replacement = None
|
||||
|
@ -527,11 +450,11 @@ class SimulatorTab:
|
|||
self.simulation_thread.join(timeout=2.0)
|
||||
|
||||
self.connection_manager.close_connection()
|
||||
Utils.log_message(self.comm_log_text, "Conexión cerrada.")
|
||||
Utils.log_message(self.event_log_text, "Conexión cerrada.")
|
||||
|
||||
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.on_function_type_change()
|
||||
if self.connection_manager.connection_type == "TCP-Server":
|
||||
self.shared_config['client_connected_var'].set("Ninguno")
|
||||
|
||||
Utils.log_message(self.event_log_text, "Simulación detenida.")
|
||||
|
@ -539,12 +462,11 @@ class SimulatorTab:
|
|||
self.current_ma_var.set("--.-- mA")
|
||||
self.current_voltage_var.set("-.-- V")
|
||||
|
||||
self.start_button.config(state=tk.NORMAL) # Mover después de _set_entries_state y on_function_type_change
|
||||
self.start_button.config(state=tk.NORMAL)
|
||||
self.stop_button.config(state=tk.DISABLED)
|
||||
self.update_error_controls_state() # Deshabilitar controles de error
|
||||
self.update_error_controls_state()
|
||||
|
||||
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())
|
||||
|
@ -552,9 +474,8 @@ class SimulatorTab:
|
|||
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
|
||||
conn_type = self.connection_manager.connection_type
|
||||
|
||||
# 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(),
|
||||
|
@ -562,23 +483,15 @@ class SimulatorTab:
|
|||
'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:
|
||||
message_to_send = None
|
||||
ma_value_for_message_generation = 0.0 # mA que se usaría para generar la trama (normal o base para error)
|
||||
|
||||
# --- Determinar valores base de la simulación para este ciclo (Brix, mA) ---
|
||||
# Esta lógica calcula los valores que se mostrarían y graficarían,
|
||||
# y que se usarían para generar una trama normal.
|
||||
target_brix = 0.0 # Brix consistente con target_ma para display/graph
|
||||
# target_ma es el valor de mA que se usaría para generar el mensaje ADAM si fuera normal
|
||||
# o el valor base si un error lo reemplaza.
|
||||
|
||||
current_manual_input_type = self.manual_input_type_var.get() # Cache para este ciclo
|
||||
ma_value_for_message_generation = 0.0
|
||||
target_brix = 0.0
|
||||
current_manual_input_type = self.manual_input_type_var.get()
|
||||
|
||||
if function_type == "Manual": # Lógica para modo Manual
|
||||
if function_type == "Manual":
|
||||
manual_input_type = self.manual_input_type_var.get()
|
||||
manual_numeric_value = 0.0
|
||||
try:
|
||||
|
@ -599,14 +512,12 @@ class SimulatorTab:
|
|||
voltage_input = manual_numeric_value
|
||||
ma_value_for_message_generation = ProtocolHandler.voltage_to_ma(voltage_input)
|
||||
target_brix = ProtocolHandler.ma_to_brix(ma_value_for_message_generation, min_brix_map, max_brix_map)
|
||||
|
||||
elif 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
|
||||
target_brix = min_brix_map + (max_brix_map - min_brix_map) * cycle_progress
|
||||
ma_value_for_message_generation = ProtocolHandler.scale_to_ma(target_brix, min_brix_map, max_brix_map)
|
||||
|
||||
elif function_type == "Sinusoidal":
|
||||
progress = (self.simulation_step % samples_per_cycle) / samples_per_cycle
|
||||
phase = progress * 2 * math.pi
|
||||
|
@ -614,34 +525,26 @@ class SimulatorTab:
|
|||
target_brix = min_brix_map + (max_brix_map - min_brix_map) * sin_val
|
||||
ma_value_for_message_generation = ProtocolHandler.scale_to_ma(target_brix, min_brix_map, max_brix_map)
|
||||
|
||||
# ma_value_in_message es el valor de mA que realmente se usaría en la trama o que se mostraría
|
||||
# Si la trama es reemplazada por un error, este valor sigue siendo el de la simulación normal
|
||||
# para la UI, pero la trama enviada será diferente.
|
||||
ma_value_for_ui_display = ma_value_for_message_generation
|
||||
voltage_value_display = ProtocolHandler.ma_to_voltage(ma_value_for_ui_display)
|
||||
|
||||
# --- Preparar la trama a enviar (normal o error de reemplazo) ---
|
||||
log_prefix_for_send = "Enviando"
|
||||
log_suffix_for_send = ""
|
||||
actual_error_type_sent = "Normal" # Para el log
|
||||
actual_error_type_sent = "Normal"
|
||||
|
||||
if self.next_frame_is_error_event.is_set() and \
|
||||
self.error_details_for_replacement is not None and \
|
||||
self.replace_normal_with_error_var.get():
|
||||
|
||||
error_msg_bytes, error_log_suffix, error_type_str = self.error_details_for_replacement
|
||||
message_to_send = error_msg_bytes
|
||||
log_prefix_for_send = "Error Sim (Reemplazo Programado)"
|
||||
log_suffix_for_send = error_log_suffix
|
||||
actual_error_type_sent = error_type_str
|
||||
|
||||
self.next_frame_is_error_event.clear()
|
||||
self.error_details_for_replacement = None
|
||||
else:
|
||||
# Generar trama normal
|
||||
message_to_send, _ = ProtocolHandler.create_adam_message_from_ma(adam_address, ma_value_for_message_generation)
|
||||
|
||||
# Preparar texto para display
|
||||
brix_display_text = ""
|
||||
if ma_value_for_ui_display < 4.0 and function_type == "Manual" and \
|
||||
(current_manual_input_type == "mA" or current_manual_input_type == "Voltaje"):
|
||||
|
@ -649,25 +552,21 @@ class SimulatorTab:
|
|||
else:
|
||||
brix_display_text = Utils.format_brix_display(target_brix)
|
||||
|
||||
# Actualizar GUI (StringVars son thread-safe para .set())
|
||||
self.current_brix_var.set(brix_display_text)
|
||||
self.current_ma_var.set(Utils.format_ma_display(ma_value_for_ui_display))
|
||||
self.current_voltage_var.set(ProtocolHandler.format_voltage_display(voltage_value_display))
|
||||
|
||||
# Agregar punto de datos al gráfico (desde el thread GUI)
|
||||
self.frame.after(0, lambda b=target_brix, m=ma_value_for_ui_display: self.add_data_point(b, m))
|
||||
|
||||
# --- Enviar la trama (normal o de error) ---
|
||||
if message_to_send: # Si hay algo que enviar (no es "Trama Faltante" de reemplazo)
|
||||
if message_to_send:
|
||||
try:
|
||||
if conn_type == "TCP-Server":
|
||||
if not self.connection_manager.is_client_connected():
|
||||
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.comm_log_text, f"TCP Server: Esperando cliente en puerto {port_to_log}...")
|
||||
Utils.log_message(self.event_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):
|
||||
Utils.log_message(self.comm_log_text, f"TCP Server: Cliente conectado desde {self.connection_manager.client_address}")
|
||||
Utils.log_message(self.event_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
|
||||
|
@ -677,52 +576,49 @@ class SimulatorTab:
|
|||
|
||||
log_content = ProtocolHandler.format_for_display(message_to_send, hex_non_printable=True)
|
||||
if actual_error_type_sent != "Normal" and log_prefix_for_send.startswith("Error Sim (Reemplazo Programado)"):
|
||||
Utils.log_message(self.comm_log_text, f"{log_prefix_for_send}: Trama '{actual_error_type_sent}'{log_suffix_for_send} -> {log_content}")
|
||||
Utils.log_message(self.cyclic_log_text, f"{log_prefix_for_send}: Trama '{actual_error_type_sent}'{log_suffix_for_send} -> {log_content}")
|
||||
else:
|
||||
Utils.log_message(self.comm_log_text, f"{log_prefix_for_send}: {log_content}")
|
||||
Utils.log_message(self.cyclic_log_text, f"{log_prefix_for_send}: {log_content}")
|
||||
|
||||
self.connection_manager.send_data(message_to_send)
|
||||
|
||||
if conn_type != "TCP-Server": # No leer respuesta en modo servidor
|
||||
if conn_type != "TCP-Server":
|
||||
response = self.connection_manager.read_response(timeout=0.1)
|
||||
if response and response.strip():
|
||||
Utils.log_message(self.comm_log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}")
|
||||
Utils.log_message(self.cyclic_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.comm_log_text,
|
||||
Utils.log_message(self.cyclic_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.comm_log_text, "TCP Server: Cliente desconectado. Esperando nueva conexión.")
|
||||
Utils.log_message(self.event_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
|
||||
except Exception as e:
|
||||
Utils.log_message(self.comm_log_text, f"Error en comunicación ({conn_type}): {e}")
|
||||
Utils.log_message(self.event_log_text, f"Error en comunicación ({conn_type}): {e}")
|
||||
self.frame.after(0, self.stop_simulation_error)
|
||||
break
|
||||
elif actual_error_type_sent == "Trama Faltante (Omitir Envío)" and log_prefix_for_send.startswith("Error Sim (Reemplazo Programado)"):
|
||||
# Loguear que se omitió una trama debido al reemplazo por "Trama Faltante"
|
||||
Utils.log_message(self.comm_log_text, f"{log_prefix_for_send}: Simulación de '{actual_error_type_sent}'{log_suffix_for_send}. No se envió trama.")
|
||||
Utils.log_message(self.cyclic_log_text, f"{log_prefix_for_send}: Simulación de '{actual_error_type_sent}'{log_suffix_for_send}. No se envió trama.")
|
||||
|
||||
self.simulation_step += 1
|
||||
time.sleep(sample_period)
|
||||
|
||||
except Exception as e: # Catches errors in parameter fetching or main loop logic
|
||||
except Exception as e:
|
||||
Utils.log_message(self.event_log_text, f"Error en simulación: {e}")
|
||||
if self.simulating: # Ensure stop is called only if an error occurs while simulating
|
||||
if self.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
|
||||
if self.simulating:
|
||||
messagebox.showerror("Error de Simulación", "Error durante la simulación. Simulación detenida.")
|
||||
self.stop_simulation() # Llama al método normal de parada
|
||||
self.stop_simulation()
|
||||
|
||||
def generate_erroneous_message_logic(self, error_type, adam_address, base_ma_value):
|
||||
"""Genera la trama (bytes) según el tipo de error."""
|
||||
message_bytes = None
|
||||
log_message_suffix = ""
|
||||
|
||||
|
@ -740,14 +636,14 @@ class SimulatorTab:
|
|||
elif error_type == "Longitud Errónea (Aleatoria)":
|
||||
base_msg_bytes, _ = ProtocolHandler.create_adam_message_from_ma(adam_address, base_ma_value)
|
||||
if len(base_msg_bytes) > 1:
|
||||
if random.choice([True, False]): # Acortar
|
||||
if random.choice([True, False]):
|
||||
cut_len = random.randint(1, max(1, len(base_msg_bytes) // 2))
|
||||
message_bytes = base_msg_bytes[:-cut_len]
|
||||
log_message_suffix = f" (longitud acortada en {cut_len} bytes)"
|
||||
else: # Alargar
|
||||
add_len = random.randint(1, 5) # Aumentado un poco el largo posible
|
||||
else:
|
||||
add_len = random.randint(1, 5)
|
||||
garbage = bytes([random.randint(32, 126) for _ in range(add_len)])
|
||||
message_bytes = base_msg_bytes + garbage # Podría ser al final o en medio
|
||||
message_bytes = base_msg_bytes + garbage
|
||||
log_message_suffix = f" (longitud aumentada en {add_len} bytes)"
|
||||
else:
|
||||
message_bytes, _ = ProtocolHandler.create_adam_message_with_bad_checksum(adam_address, base_ma_value)
|
||||
|
@ -755,65 +651,54 @@ class SimulatorTab:
|
|||
elif error_type == "Trama Faltante (Omitir Envío)":
|
||||
log_message_suffix = " (trama omitida)"
|
||||
return None, log_message_suffix
|
||||
elif error_type == "Ninguno": # Enviar trama normal
|
||||
elif error_type == "Ninguno":
|
||||
message_bytes, _ = ProtocolHandler.create_adam_message_from_ma(adam_address, base_ma_value)
|
||||
log_message_suffix = " (trama normal)"
|
||||
else:
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim: Tipo de error '{error_type}' desconocido.")
|
||||
Utils.log_message(self.event_log_text, f"Error Sim: Tipo de error '{error_type}' desconocido.")
|
||||
return None, f" (tipo de error '{error_type}' desconocido)"
|
||||
|
||||
return message_bytes, log_message_suffix
|
||||
|
||||
def send_selected_error_manually(self):
|
||||
"""Manejador del botón 'Enviar Trama Errónea'."""
|
||||
if not (self.shared_config['connection_type_var'].get() == "TCP-Server" and self.simulating):
|
||||
messagebox.showwarning("No Activo", "La simulación de errores manuales requiere modo TCP-Server y simulación activa.")
|
||||
return
|
||||
|
||||
if not self.connection_manager.is_client_connected():
|
||||
Utils.log_message(self.comm_log_text, "Error Sim: No hay cliente conectado para enviar trama errónea.")
|
||||
# messagebox.showinfo("Sin Cliente", "No hay cliente conectado para enviar la trama errónea.")
|
||||
# return # Permitir enviar aunque no haya cliente, el log lo indicará
|
||||
Utils.log_message(self.event_log_text, "Error Sim: No hay cliente conectado para enviar trama errónea.")
|
||||
|
||||
error_type = self.error_type_var.get()
|
||||
adam_address, base_ma_value = self.get_current_error_sim_parameters()
|
||||
|
||||
message_bytes, log_suffix_from_gen = self.generate_erroneous_message_logic(error_type, adam_address, base_ma_value)
|
||||
|
||||
if self.replace_normal_with_error_var.get():
|
||||
# Programar para reemplazo en el siguiente ciclo de simulación
|
||||
self.error_details_for_replacement = (message_bytes, log_suffix_from_gen, error_type)
|
||||
self.next_frame_is_error_event.set()
|
||||
if error_type == "Trama Faltante (Omitir Envío)":
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual: Programada OMISIÓN de trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual: Programada OMISIÓN de trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
elif message_bytes:
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual: Programada trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
else: # Error en generación o tipo desconocido
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual: No se pudo programar trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual: Programada trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
else:
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual: No se pudo programar trama '{error_type}'{log_suffix_from_gen} para reemplazo.")
|
||||
else:
|
||||
# Enviar inmediatamente como trama adicional
|
||||
if message_bytes:
|
||||
try:
|
||||
self.connection_manager.send_data(message_bytes)
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual (Adicional): Trama '{error_type}'{log_suffix_from_gen} -> {ProtocolHandler.format_for_display(message_bytes, hex_non_printable=True)}")
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual (Adicional): Trama '{error_type}'{log_suffix_from_gen} -> {ProtocolHandler.format_for_display(message_bytes, hex_non_printable=True)}")
|
||||
except Exception as e:
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual (Adicional): Fallo al enviar trama: {e}")
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual (Adicional): Fallo al enviar trama: {e}")
|
||||
elif error_type == "Trama Faltante (Omitir Envío)":
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Manual (Adicional): Simulación de '{error_type}'{log_suffix_from_gen}. No se envió trama adicional.")
|
||||
# else: Ya logueado por generate_erroneous_message_logic si message_bytes es None y no es "Trama Faltante"
|
||||
Utils.log_message(self.event_log_text, f"Error Sim Manual (Adicional): Simulación de '{error_type}'{log_suffix_from_gen}. No se envió trama adicional.")
|
||||
|
||||
def toggle_random_errors(self):
|
||||
"""Activa o desactiva el envío de errores aleatorios."""
|
||||
# self.random_error_var.get() refleja el nuevo estado del checkbox debido al clic del usuario
|
||||
|
||||
can_actually_start_random_errors = (self.shared_config['connection_type_var'].get() == "TCP-Server" and self.simulating)
|
||||
|
||||
if self.random_error_var.get(): # Si el usuario intenta activar los errores aleatorios
|
||||
if self.random_error_var.get():
|
||||
if not can_actually_start_random_errors:
|
||||
Utils.log_message(self.event_log_text, "Error Sim: Errores aleatorios solo en TCP-Server con simulación activa.")
|
||||
self.random_error_var.set(False) # Forzar a False ya que las condiciones no se cumplen
|
||||
# El timer no se iniciará. update_error_controls_state() al final se encargará.
|
||||
else: # Las condiciones se cumplen, iniciar el timer si no está ya activo
|
||||
self.random_error_var.set(False)
|
||||
else:
|
||||
try:
|
||||
interval_val = float(self.random_error_interval_var.get())
|
||||
if interval_val <= 0:
|
||||
|
@ -827,22 +712,18 @@ class SimulatorTab:
|
|||
self.update_error_controls_state()
|
||||
return
|
||||
|
||||
# Las condiciones se cumplen, iniciar el timer si no está ya activo
|
||||
if self.random_error_timer is None or not self.random_error_timer.is_alive():
|
||||
self.random_error_timer_stop_event.clear()
|
||||
self.random_error_timer = threading.Thread(target=self._random_error_loop, args=(interval_val,), daemon=True)
|
||||
self.random_error_timer.start()
|
||||
else: # Si el usuario intenta desactivar los errores aleatorios (el checkbox ahora está desmarcado)
|
||||
else:
|
||||
if self.random_error_timer and self.random_error_timer.is_alive():
|
||||
Utils.log_message(self.event_log_text, "Error Sim: Deteniendo envío de errores aleatorios.")
|
||||
self.random_error_timer_stop_event.set()
|
||||
# No es necesario join aquí, se hará en stop_simulation o al cerrar.
|
||||
|
||||
# Actualizar siempre el estado de los controles al final, basado en el estado final de self.random_error_var
|
||||
self.update_error_controls_state()
|
||||
|
||||
def _random_error_loop(self, initial_interval_s):
|
||||
"""Bucle del hilo que envía errores aleatorios."""
|
||||
possible_error_types = [val for val in self.error_type_combo['values'] if val != "Ninguno"]
|
||||
if not possible_error_types: return
|
||||
|
||||
|
@ -851,7 +732,7 @@ class SimulatorTab:
|
|||
|
||||
while not self.random_error_timer_stop_event.is_set():
|
||||
if not (self.shared_config['connection_type_var'].get() == "TCP-Server" and self.simulating and self.connection_manager.is_client_connected()):
|
||||
self.random_error_timer_stop_event.wait(1.0) # Esperar si no hay cliente o no está activo
|
||||
self.random_error_timer_stop_event.wait(1.0)
|
||||
continue
|
||||
|
||||
selected_random_error = random.choice(possible_error_types)
|
||||
|
@ -859,36 +740,31 @@ class SimulatorTab:
|
|||
message_bytes, log_suffix = self.generate_erroneous_message_logic(selected_random_error, adam_address, base_ma_value)
|
||||
|
||||
if self.replace_normal_with_error_var.get():
|
||||
# Programar el error para que reemplace la siguiente trama normal
|
||||
self.error_details_for_replacement = (message_bytes, log_suffix, selected_random_error)
|
||||
self.next_frame_is_error_event.set()
|
||||
# El log de este envío se hará en run_simulation cuando efectivamente se envíe/omita
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Aleatorio: Programada trama '{selected_random_error}'{log_suffix} para reemplazo.")
|
||||
Utils.log_message(self.cyclic_log_text, f"Error Sim Aleatorio: Programada trama '{selected_random_error}'{log_suffix} para reemplazo.")
|
||||
else:
|
||||
# Enviar el error inmediatamente, además de las tramas normales
|
||||
if message_bytes:
|
||||
try:
|
||||
self.connection_manager.send_data(message_bytes)
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Aleatorio (Adicional): Trama '{selected_random_error}'{log_suffix} -> {ProtocolHandler.format_for_display(message_bytes, hex_non_printable=True)}")
|
||||
Utils.log_message(self.cyclic_log_text, f"Error Sim Aleatorio (Adicional): Trama '{selected_random_error}'{log_suffix} -> {ProtocolHandler.format_for_display(message_bytes, hex_non_printable=True)}")
|
||||
except Exception as e:
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Aleatorio (Adicional): Fallo al enviar: {e}")
|
||||
Utils.log_message(self.cyclic_log_text, f"Error Sim Aleatorio (Adicional): Fallo al enviar: {e}")
|
||||
elif selected_random_error == "Trama Faltante (Omitir Envío)":
|
||||
Utils.log_message(self.comm_log_text, f"Error Sim Aleatorio (Adicional): Simulación de '{selected_random_error}'{log_suffix}. No se envió trama adicional.")
|
||||
Utils.log_message(self.cyclic_log_text, f"Error Sim Aleatorio (Adicional): Simulación de '{selected_random_error}'{log_suffix}. No se envió trama adicional.")
|
||||
|
||||
# Permitir que el intervalo se actualice dinámicamente
|
||||
try:
|
||||
new_interval = float(self.random_error_interval_var.get())
|
||||
if new_interval > 0 and new_interval != current_interval:
|
||||
current_interval = new_interval
|
||||
Utils.log_message(self.event_log_text, f"Error Sim: Intervalo de errores aleatorios actualizado a {current_interval:.2f}s.")
|
||||
except ValueError:
|
||||
pass # Mantener el intervalo actual si el nuevo valor es inválido
|
||||
pass
|
||||
|
||||
self.random_error_timer_stop_event.wait(timeout=current_interval)
|
||||
Utils.log_message(self.event_log_text, "Error Sim: Hilo de errores aleatorios detenido.")
|
||||
|
||||
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)
|
||||
|
@ -898,37 +774,30 @@ class SimulatorTab:
|
|||
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() # noqa: F841
|
||||
self.start_time = time.time()
|
||||
Utils.log_message(self.event_log_text, "Gráfico del simulador limpiado.")
|
||||
|
||||
if hasattr(self, 'graph_update_callback'):
|
||||
self.graph_update_callback()
|
||||
|
||||
def clear_comm_log(self):
|
||||
"""Limpia el log de comunicación del simulador."""
|
||||
Utils.clear_log_widget(self.comm_log_text)
|
||||
Utils.log_message(self.event_log_text, "Log de comunicación del simulador limpiado.")
|
||||
def clear_cyclic_log(self):
|
||||
Utils.clear_log_widget(self.cyclic_log_text)
|
||||
Utils.log_message(self.event_log_text, "Log cíclico del simulador limpiado.")
|
||||
|
||||
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)
|
||||
|
||||
# self.update_error_controls_state() # El estado de los controles de error depende también de self.simulating
|
||||
|
||||
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(),
|
||||
|
@ -940,7 +809,6 @@ class SimulatorTab:
|
|||
}
|
||||
|
||||
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'))
|
||||
|
@ -953,14 +821,11 @@ class SimulatorTab:
|
|||
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
|
||||
self.update_error_controls_state() # Actualizar estado de controles de error al cargar config
|
||||
self.on_function_type_change()
|
||||
self.update_error_controls_state()
|
||||
|
||||
def on_app_close(self):
|
||||
"""Llamado cuando la aplicación se está cerrando para limpiar recursos."""
|
||||
if self.simulating:
|
||||
self.stop_simulation() # Asegura que todo se detenga y limpie correctamente
|
||||
self.stop_simulation()
|
||||
|
|
Loading…
Reference in New Issue