454 lines
20 KiB
Python
454 lines
20 KiB
Python
"""
|
|
Tab NetCom - Gateway/Bridge entre puerto COM físico y conexión TCP/UDP
|
|
"""
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk, scrolledtext, messagebox
|
|
import threading
|
|
import time
|
|
from datetime import datetime
|
|
|
|
from connection_manager import ConnectionManager
|
|
from protocol_handler import ProtocolHandler
|
|
from utils import Utils
|
|
|
|
class NetComTab:
|
|
def __init__(self, parent_frame, shared_config):
|
|
self.frame = parent_frame
|
|
self.shared_config = shared_config
|
|
|
|
# Estado del gateway
|
|
self.bridging = False
|
|
self.bridge_thread = None
|
|
|
|
# Conexiones
|
|
self.com_connection = ConnectionManager()
|
|
self.net_connection = ConnectionManager()
|
|
|
|
# Estadísticas
|
|
self.com_to_net_count = 0
|
|
self.net_to_com_count = 0
|
|
self.error_count = 0
|
|
|
|
self.create_widgets()
|
|
|
|
def create_widgets(self):
|
|
"""Crea los widgets del tab NetCom"""
|
|
# Frame de configuración COM física
|
|
com_config_frame = ttk.LabelFrame(self.frame, text="Configuración Puerto COM Físico")
|
|
com_config_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=5, sticky="ew")
|
|
|
|
ttk.Label(com_config_frame, text="Puerto COM:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
|
self.com_port_var = tk.StringVar(value=self.shared_config.get('netcom_com_port', 'COM3'))
|
|
self.com_port_entry = ttk.Entry(com_config_frame, textvariable=self.com_port_var, width=10)
|
|
self.com_port_entry.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
|
|
|
|
ttk.Label(com_config_frame, text="Baud Rate:").grid(row=0, column=2, padx=5, pady=5, sticky="w")
|
|
self.baud_rate_var = tk.StringVar(value=self.shared_config.get('netcom_baud_rate', '115200'))
|
|
self.baud_rate_entry = ttk.Entry(com_config_frame, textvariable=self.baud_rate_var, width=10)
|
|
self.baud_rate_entry.grid(row=0, column=3, padx=5, pady=5, sticky="ew")
|
|
|
|
# Info frame
|
|
info_frame = ttk.LabelFrame(self.frame, text="Información de Conexión")
|
|
info_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=5, sticky="ew")
|
|
|
|
ttk.Label(info_frame, text="Conexión de Red:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
|
self.net_info_var = tk.StringVar(value="No configurada")
|
|
ttk.Label(info_frame, textvariable=self.net_info_var, font=("Courier", 10)).grid(row=0, column=1, padx=5, pady=5, sticky="w")
|
|
|
|
ttk.Label(info_frame, text="Estado:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
|
|
self.status_var = tk.StringVar(value="Desconectado")
|
|
self.status_label = ttk.Label(info_frame, textvariable=self.status_var, font=("Courier", 10, "bold"))
|
|
self.status_label.grid(row=1, column=1, padx=5, pady=5, sticky="w")
|
|
|
|
# Control Frame
|
|
control_frame = ttk.LabelFrame(self.frame, text="Control Gateway")
|
|
control_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew")
|
|
|
|
self.start_button = ttk.Button(control_frame, text="Iniciar Gateway", command=self.start_bridge)
|
|
self.start_button.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.stop_button = ttk.Button(control_frame, text="Detener Gateway", command=self.stop_bridge, state=tk.DISABLED)
|
|
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
|
|
self.clear_log_button = ttk.Button(control_frame, text="Limpiar Log", command=self.clear_log)
|
|
self.clear_log_button.pack(side=tk.LEFT, padx=5)
|
|
|
|
# Statistics Frame
|
|
stats_frame = ttk.LabelFrame(self.frame, text="Estadísticas")
|
|
stats_frame.grid(row=2, column=1, padx=10, pady=5, sticky="ew")
|
|
|
|
ttk.Label(stats_frame, text="COM → NET:").grid(row=0, column=0, padx=5, pady=2, sticky="w")
|
|
self.com_to_net_var = tk.StringVar(value="0")
|
|
ttk.Label(stats_frame, textvariable=self.com_to_net_var).grid(row=0, column=1, padx=5, pady=2, sticky="w")
|
|
|
|
ttk.Label(stats_frame, text="NET → COM:").grid(row=0, column=2, padx=5, pady=2, sticky="w")
|
|
self.net_to_com_var = tk.StringVar(value="0")
|
|
ttk.Label(stats_frame, textvariable=self.net_to_com_var).grid(row=0, column=3, padx=5, pady=2, sticky="w")
|
|
|
|
ttk.Label(stats_frame, text="Errores:").grid(row=1, column=0, padx=5, pady=2, sticky="w")
|
|
self.errors_var = tk.StringVar(value="0")
|
|
ttk.Label(stats_frame, textvariable=self.errors_var, foreground="red").grid(row=1, column=1, padx=5, pady=2, sticky="w")
|
|
|
|
# Log Frame con filtros
|
|
log_control_frame = ttk.Frame(self.frame)
|
|
log_control_frame.grid(row=3, column=0, columnspan=2, padx=10, pady=(10,0), sticky="ew")
|
|
|
|
ttk.Label(log_control_frame, text="Filtros:").pack(side=tk.LEFT, padx=5)
|
|
|
|
self.show_com_to_net_var = tk.BooleanVar(value=True)
|
|
ttk.Checkbutton(log_control_frame, text="COM→NET",
|
|
variable=self.show_com_to_net_var).pack(side=tk.LEFT, padx=5)
|
|
|
|
self.show_net_to_com_var = tk.BooleanVar(value=True)
|
|
ttk.Checkbutton(log_control_frame, text="NET→COM",
|
|
variable=self.show_net_to_com_var).pack(side=tk.LEFT, padx=5)
|
|
|
|
self.show_parsed_var = tk.BooleanVar(value=True)
|
|
ttk.Checkbutton(log_control_frame, text="Mostrar datos parseados",
|
|
variable=self.show_parsed_var).pack(side=tk.LEFT, padx=5)
|
|
|
|
# Log Frame
|
|
log_frame = ttk.LabelFrame(self.frame, text="Log de Gateway (Sniffer)")
|
|
log_frame.grid(row=4, column=0, columnspan=2, padx=10, pady=5, sticky="nsew")
|
|
|
|
self.log_text = scrolledtext.ScrolledText(log_frame, height=20, width=80, wrap=tk.WORD, state=tk.DISABLED)
|
|
self.log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
|
|
|
|
# Configurar tags para colores
|
|
self.log_text.tag_config("com_to_net", foreground="blue")
|
|
self.log_text.tag_config("net_to_com", foreground="green")
|
|
self.log_text.tag_config("error", foreground="red")
|
|
self.log_text.tag_config("info", foreground="black")
|
|
self.log_text.tag_config("parsed", foreground="purple")
|
|
|
|
# Configurar pesos
|
|
self.frame.columnconfigure(0, weight=1)
|
|
self.frame.columnconfigure(1, weight=1)
|
|
self.frame.rowconfigure(4, weight=1)
|
|
|
|
# Actualizar info de red
|
|
self.update_net_info()
|
|
|
|
def update_net_info(self):
|
|
"""Actualiza la información de la conexión de red configurada"""
|
|
conn_type = self.shared_config['connection_type_var'].get()
|
|
if conn_type == "Serial":
|
|
port = self.shared_config['com_port_var'].get()
|
|
baud = self.shared_config['baud_rate_var'].get()
|
|
self.net_info_var.set(f"Serial: {port} @ {baud} bps")
|
|
elif conn_type == "TCP":
|
|
ip = self.shared_config['ip_address_var'].get()
|
|
port = self.shared_config['port_var'].get()
|
|
self.net_info_var.set(f"TCP: {ip}:{port}")
|
|
elif conn_type == "UDP":
|
|
ip = self.shared_config['ip_address_var'].get()
|
|
port = self.shared_config['port_var'].get()
|
|
self.net_info_var.set(f"UDP: {ip}:{port}")
|
|
|
|
def log_message(self, message, tag="info", force=False):
|
|
"""Log con formato especial para el sniffer"""
|
|
# Verificar filtros
|
|
if not force:
|
|
if tag == "com_to_net" and not self.show_com_to_net_var.get():
|
|
return
|
|
if tag == "net_to_com" and not self.show_net_to_com_var.get():
|
|
return
|
|
|
|
self.log_text.configure(state=tk.NORMAL)
|
|
timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3]
|
|
|
|
# Agregar prefijo según la dirección
|
|
if tag == "com_to_net":
|
|
prefix = "[COM→NET]"
|
|
elif tag == "net_to_com":
|
|
prefix = "[NET→COM]"
|
|
elif tag == "error":
|
|
prefix = "[ERROR] "
|
|
elif tag == "parsed":
|
|
prefix = "[PARSED] "
|
|
else:
|
|
prefix = "[INFO] "
|
|
|
|
full_message = f"[{timestamp}] {prefix} {message}\n"
|
|
|
|
# Insertar con color
|
|
start_index = self.log_text.index(tk.END)
|
|
self.log_text.insert(tk.END, full_message)
|
|
end_index = self.log_text.index(tk.END)
|
|
self.log_text.tag_add(tag, start_index, end_index)
|
|
|
|
self.log_text.see(tk.END)
|
|
self.log_text.configure(state=tk.DISABLED)
|
|
|
|
def start_bridge(self):
|
|
"""Inicia el gateway/bridge"""
|
|
if self.bridging:
|
|
messagebox.showwarning("Advertencia", "El gateway ya está activo.")
|
|
return
|
|
|
|
# Actualizar info de red
|
|
self.update_net_info()
|
|
|
|
# Validar configuración
|
|
try:
|
|
com_port = self.com_port_var.get()
|
|
baud_rate = int(self.baud_rate_var.get())
|
|
|
|
if not com_port.upper().startswith('COM'):
|
|
raise ValueError("Puerto COM inválido")
|
|
if baud_rate <= 0:
|
|
raise ValueError("Baud rate debe ser mayor que 0")
|
|
|
|
except ValueError as e:
|
|
messagebox.showerror("Error", f"Configuración inválida: {e}")
|
|
return
|
|
|
|
# Abrir conexión COM física
|
|
try:
|
|
self.com_connection.open_connection("Serial", {
|
|
'port': com_port,
|
|
'baud': baud_rate
|
|
})
|
|
self.log_message(f"Puerto COM abierto: {com_port} @ {baud_rate} bps")
|
|
except Exception as e:
|
|
messagebox.showerror("Error", f"No se pudo abrir puerto COM: {e}")
|
|
return
|
|
|
|
# Abrir conexión de red
|
|
try:
|
|
# For the network side of the bridge, use shared connection settings
|
|
net_conn_type_actual = self.shared_config['connection_type_var'].get()
|
|
|
|
current_shared_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(),
|
|
}
|
|
# The first argument to get_connection_params is the dictionary it will read from.
|
|
net_conn_params = self.shared_config['config_manager'].get_connection_params(current_shared_config_values)
|
|
|
|
self.net_connection.open_connection(net_conn_type_actual, net_conn_params)
|
|
self.log_message(f"Conexión {net_conn_type_actual} abierta: {self.net_info_var.get()}")
|
|
except Exception as e:
|
|
self.com_connection.close_connection()
|
|
messagebox.showerror("Error", f"No se pudo abrir conexión de red: {e}")
|
|
return
|
|
|
|
# Resetear estadísticas
|
|
self.com_to_net_count = 0
|
|
self.net_to_com_count = 0
|
|
self.error_count = 0
|
|
self.update_stats()
|
|
|
|
# Iniciar bridge
|
|
self.bridging = True
|
|
self.status_var.set("Conectado")
|
|
self.status_label.config(foreground="green")
|
|
self.start_button.config(state=tk.DISABLED)
|
|
self.stop_button.config(state=tk.NORMAL)
|
|
self._set_entries_state(tk.DISABLED)
|
|
|
|
self.bridge_thread = threading.Thread(target=self.run_bridge, daemon=True)
|
|
self.bridge_thread.start()
|
|
|
|
self.log_message("Gateway iniciado - Modo bridge activo")
|
|
|
|
def stop_bridge(self):
|
|
"""Detiene el gateway/bridge"""
|
|
if not self.bridging:
|
|
return
|
|
|
|
self.bridging = False
|
|
|
|
# Esperar a que termine el thread
|
|
if self.bridge_thread and self.bridge_thread.is_alive():
|
|
self.bridge_thread.join(timeout=2.0)
|
|
|
|
# Cerrar conexiones
|
|
self.com_connection.close_connection()
|
|
self.net_connection.close_connection()
|
|
|
|
self.status_var.set("Desconectado")
|
|
self.status_label.config(foreground="black")
|
|
self.start_button.config(state=tk.NORMAL)
|
|
self.stop_button.config(state=tk.DISABLED)
|
|
self._set_entries_state(tk.NORMAL)
|
|
|
|
self.log_message("Gateway detenido")
|
|
self.log_message(f"Total transferencias - COM→NET: {self.com_to_net_count}, NET→COM: {self.net_to_com_count}, Errores: {self.error_count}")
|
|
|
|
def run_bridge(self):
|
|
"""Thread principal del bridge"""
|
|
com_buffer = ""
|
|
net_buffer = ""
|
|
|
|
while self.bridging:
|
|
try:
|
|
# Leer del COM físico
|
|
com_data = self.com_connection.read_data_non_blocking()
|
|
if com_data:
|
|
com_buffer += com_data
|
|
|
|
# Buscar mensajes completos para logging
|
|
while '\r' in com_buffer or '\n' in com_buffer or len(com_buffer) >= 10:
|
|
end_idx = self._find_message_end(com_buffer)
|
|
if end_idx > 0:
|
|
message = com_buffer[:end_idx]
|
|
com_buffer = com_buffer[end_idx:]
|
|
|
|
# Log y parseo
|
|
display_msg = ProtocolHandler.format_for_display(message)
|
|
self.log_message(f"Data: {display_msg}", "com_to_net")
|
|
|
|
# Intentar parsear si está habilitado
|
|
if self.show_parsed_var.get():
|
|
parsed = ProtocolHandler.parse_adam_message(message)
|
|
if parsed:
|
|
# Obtener valores de mapeo
|
|
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
|
max_brix = float(self.shared_config['max_brix_map_var'].get())
|
|
brix_value = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix, max_brix)
|
|
|
|
self.log_message(
|
|
f"ADAM - Addr: {parsed['address']}, "
|
|
f"mA: {parsed['ma']:.3f}, "
|
|
f"Brix: {brix_value:.3f}, "
|
|
f"Checksum: {'OK' if parsed.get('checksum_valid', True) else 'ERROR'}",
|
|
"parsed"
|
|
)
|
|
|
|
# Reenviar a la red
|
|
try:
|
|
self.net_connection.send_data(message)
|
|
self.com_to_net_count += 1
|
|
self.update_stats()
|
|
except Exception as e:
|
|
self.log_message(f"Error enviando a red: {e}", "error")
|
|
self.error_count += 1
|
|
self.update_stats()
|
|
else:
|
|
break
|
|
|
|
# Leer de la red
|
|
net_data = self.net_connection.read_data_non_blocking()
|
|
if net_data:
|
|
net_buffer += net_data
|
|
|
|
# Buscar mensajes completos para logging
|
|
while '\r' in net_buffer or '\n' in net_buffer or len(net_buffer) >= 10:
|
|
end_idx = self._find_message_end(net_buffer)
|
|
if end_idx > 0:
|
|
message = net_buffer[:end_idx]
|
|
net_buffer = net_buffer[end_idx:]
|
|
|
|
# Log y parseo
|
|
display_msg = ProtocolHandler.format_for_display(message)
|
|
self.log_message(f"Data: {display_msg}", "net_to_com")
|
|
|
|
# Intentar parsear si está habilitado
|
|
if self.show_parsed_var.get():
|
|
parsed = ProtocolHandler.parse_adam_message(message)
|
|
if parsed:
|
|
# Obtener valores de mapeo
|
|
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
|
max_brix = float(self.shared_config['max_brix_map_var'].get())
|
|
brix_value = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix, max_brix)
|
|
|
|
self.log_message(
|
|
f"ADAM - Addr: {parsed['address']}, "
|
|
f"mA: {parsed['ma']:.3f}, "
|
|
f"Brix: {brix_value:.3f}, "
|
|
f"Checksum: {'OK' if parsed.get('checksum_valid', True) else 'ERROR'}",
|
|
"parsed"
|
|
)
|
|
|
|
# Reenviar al COM
|
|
try:
|
|
self.com_connection.send_data(message)
|
|
self.net_to_com_count += 1
|
|
self.update_stats()
|
|
except Exception as e:
|
|
self.log_message(f"Error enviando a COM: {e}", "error")
|
|
self.error_count += 1
|
|
self.update_stats()
|
|
else:
|
|
break
|
|
|
|
except Exception as e:
|
|
if self.bridging:
|
|
self.log_message(f"Error en bridge: {e}", "error")
|
|
self.error_count += 1
|
|
self.update_stats()
|
|
break
|
|
|
|
# Pequeña pausa para no consumir demasiado CPU
|
|
if not com_data and not net_data:
|
|
time.sleep(0.001)
|
|
|
|
# Asegurar que el estado se actualice
|
|
if not self.bridging:
|
|
self.frame.after(0, self._ensure_stopped_state)
|
|
|
|
def _find_message_end(self, buffer):
|
|
"""Encuentra el final de un mensaje en el buffer"""
|
|
# Buscar terminadores
|
|
for i, char in enumerate(buffer):
|
|
if char in ['\r', '\n']:
|
|
return i + 1
|
|
|
|
# Si no hay terminador pero el buffer es largo, buscar mensaje ADAM completo
|
|
if len(buffer) >= 10:
|
|
if buffer[0] == '#' or (buffer[2:8].replace('.', '').replace(' ', '').replace('-', '').isdigit()):
|
|
# Parece un mensaje ADAM
|
|
if len(buffer) > 10 and buffer[10] in ['\r', '\n']:
|
|
return 11
|
|
else:
|
|
return 10
|
|
|
|
return -1
|
|
|
|
def update_stats(self):
|
|
"""Actualiza las estadísticas en la GUI"""
|
|
self.com_to_net_var.set(str(self.com_to_net_count))
|
|
self.net_to_com_var.set(str(self.net_to_com_count))
|
|
self.errors_var.set(str(self.error_count))
|
|
|
|
def clear_log(self):
|
|
"""Limpia el log"""
|
|
self.log_text.configure(state=tk.NORMAL)
|
|
self.log_text.delete(1.0, tk.END)
|
|
self.log_text.configure(state=tk.DISABLED)
|
|
self.log_message("Log limpiado", force=True)
|
|
|
|
def _set_entries_state(self, state):
|
|
"""Habilita/deshabilita los controles durante el bridge"""
|
|
self.com_port_entry.config(state=state)
|
|
self.baud_rate_entry.config(state=state)
|
|
|
|
# También deshabilitar controles compartidos
|
|
if 'shared_widgets' in self.shared_config:
|
|
Utils.set_widgets_state(self.shared_config['shared_widgets'], state)
|
|
|
|
def _ensure_stopped_state(self):
|
|
"""Asegura que la GUI refleje el estado detenido"""
|
|
self.status_var.set("Desconectado")
|
|
self.status_label.config(foreground="black")
|
|
self.start_button.config(state=tk.NORMAL)
|
|
self.stop_button.config(state=tk.DISABLED)
|
|
self._set_entries_state(tk.NORMAL)
|
|
|
|
def get_config(self):
|
|
"""Obtiene la configuración actual del NetCom"""
|
|
return {
|
|
'netcom_com_port': self.com_port_var.get(),
|
|
'netcom_baud_rate': self.baud_rate_var.get()
|
|
}
|
|
|
|
def set_config(self, config):
|
|
"""Establece la configuración del NetCom"""
|
|
self.com_port_var.set(config.get('netcom_com_port', 'COM3'))
|
|
self.baud_rate_var.set(config.get('netcom_baud_rate', '115200'))
|