566 lines
28 KiB
Python
566 lines
28 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")
|
|
|
|
# Data bits, Parity, Stop bits
|
|
ttk.Label(com_config_frame, text="Data Bits:").grid(row=0, column=4, padx=5, pady=5, sticky="w")
|
|
self.bytesize_var = tk.StringVar(value=str(self.shared_config.get('netcom_bytesize', 8)))
|
|
self.bytesize_combo = ttk.Combobox(com_config_frame, textvariable=self.bytesize_var,
|
|
values=["5", "6", "7", "8"], state="readonly", width=5)
|
|
self.bytesize_combo.grid(row=0, column=5, padx=5, pady=5, sticky="ew")
|
|
|
|
ttk.Label(com_config_frame, text="Parity:").grid(row=0, column=6, padx=5, pady=5, sticky="w")
|
|
self.parity_var = tk.StringVar(value=self.shared_config.get('netcom_parity', 'N'))
|
|
self.parity_combo = ttk.Combobox(com_config_frame, textvariable=self.parity_var,
|
|
values=["N", "E", "O", "M", "S"], state="readonly", width=5) # N: None, E: Even, O: Odd, M: Mark, S: Space
|
|
self.parity_combo.grid(row=0, column=7, padx=5, pady=5, sticky="ew")
|
|
|
|
ttk.Label(com_config_frame, text="Stop Bits:").grid(row=0, column=8, padx=5, pady=5, sticky="w")
|
|
self.stopbits_var = tk.StringVar(value=str(self.shared_config.get('netcom_stopbits', 1)))
|
|
self.stopbits_combo = ttk.Combobox(com_config_frame, textvariable=self.stopbits_var,
|
|
values=["1", "1.5", "2"], state="readonly", width=5)
|
|
self.stopbits_combo.grid(row=0, column=9, padx=5, pady=5, sticky="ew")
|
|
|
|
|
|
|
|
# Flow control options
|
|
self.rtscts_var = tk.BooleanVar(value=self.shared_config.get('netcom_rtscts', False))
|
|
self.rtscts_check = ttk.Checkbutton(com_config_frame, text="RTS/CTS", variable=self.rtscts_var)
|
|
self.rtscts_check.grid(row=1, column=0, padx=5, pady=5, sticky="w")
|
|
self.dsrdtr_var = tk.BooleanVar(value=self.shared_config.get('netcom_dsrdtr', False))
|
|
self.dsrdtr_check = ttk.Checkbutton(com_config_frame, text="DSR/DTR", variable=self.dsrdtr_var)
|
|
self.dsrdtr_check.grid(row=1, column=1, padx=5, pady=5, sticky="w")
|
|
self.xonxoff_var = tk.BooleanVar(value=self.shared_config.get('netcom_xonxoff', False))
|
|
self.xonxoff_check = ttk.Checkbutton(com_config_frame, text="XON/XOFF", variable=self.xonxoff_var)
|
|
self.xonxoff_check.grid(row=1, column=2, padx=5, pady=5, sticky="w")
|
|
|
|
# Bridge delay
|
|
ttk.Label(com_config_frame, text="Retardo Bridge (s):").grid(row=1, column=3, padx=5, pady=5, sticky="w")
|
|
self.bridge_delay_var = tk.StringVar(value=str(self.shared_config.get('netcom_bridge_delay', 0.001)))
|
|
self.bridge_delay_entry = ttk.Entry(com_config_frame, textvariable=self.bridge_delay_var, width=8)
|
|
self.bridge_delay_entry.grid(row=1, column=4, 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
|
|
|
|
# Verificar si el tipo de conexión global es compatible con el lado de red de NetCom
|
|
global_conn_type_for_network_side = self.shared_config['connection_type_var'].get()
|
|
if global_conn_type_for_network_side == "TCP-Server":
|
|
messagebox.showerror("Modo No Compatible", "El lado de red de NetCom no puede operar en modo TCP-Server (configuración global). Seleccione TCP, UDP o Serial para la conexión de red.")
|
|
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())
|
|
bridge_delay_str = self.bridge_delay_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")
|
|
try:
|
|
self.current_bridge_delay = float(bridge_delay_str)
|
|
if self.current_bridge_delay < 0:
|
|
raise ValueError("El retardo del bridge no puede ser negativo.")
|
|
except ValueError:
|
|
raise ValueError("Retardo del bridge inválido. Debe ser un número (ej: 0.001).")
|
|
|
|
except ValueError as e:
|
|
messagebox.showerror("Error", f"Configuración inválida: {e}")
|
|
return
|
|
|
|
# Abrir conexión COM física
|
|
try:
|
|
# open_connection ahora devuelve (connection_object, listening_info)
|
|
_, _ = self.com_connection.open_connection("Serial", { # Ignoramos listening_info para Serial
|
|
'port': com_port,
|
|
'baudrate': baud_rate,
|
|
'bytesize': int(self.bytesize_var.get()),
|
|
'parity': self.parity_var.get(),
|
|
'stopbits': float(self.stopbits_var.get()),
|
|
'rtscts': self.rtscts_var.get(),
|
|
'dsrdtr': self.dsrdtr_var.get(),
|
|
'xonxoff': self.xonxoff_var.get()
|
|
})
|
|
|
|
# Log basic serial config
|
|
serial_config_log = f"{com_port} @ {baud_rate} bps, {self.bytesize_var.get()}{self.parity_var.get()}{self.stopbits_var.get()}"
|
|
fc_log = []
|
|
if self.rtscts_var.get(): fc_log.append("RTS/CTS")
|
|
if self.dsrdtr_var.get(): fc_log.append("DSR/DTR")
|
|
if self.xonxoff_var.get(): fc_log.append("XON/XOFF")
|
|
self.log_message(f"Puerto COM abierto: {serial_config_log}. Flow control: {', '.join(fc_log) if fc_log else 'Ninguno'}")
|
|
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)
|
|
|
|
# open_connection ahora devuelve (connection_object, listening_info)
|
|
_, net_listening_details = self.net_connection.open_connection(net_conn_type_actual, net_conn_params)
|
|
if net_conn_type_actual == "TCP-Server" and net_listening_details: # Aunque NetCom no usa TCP-Server globalmente
|
|
self.log_message(f"{net_listening_details}")
|
|
else:
|
|
self.log_message(f"Conexión de red ({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 = bytearray()
|
|
# net_buffer = bytearray() # Ya no se usa para el flujo NET->COM si pasamos los datos directamente
|
|
current_delay = self.current_bridge_delay
|
|
|
|
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
|
|
# Adaptar la condición para bytearray
|
|
while self._find_message_end_conditions(com_buffer):
|
|
end_idx = self._find_message_end(com_buffer)
|
|
if end_idx > 0:
|
|
message_bytes = bytes(com_buffer[:end_idx])
|
|
com_buffer = com_buffer[end_idx:]
|
|
|
|
# Log y parseo
|
|
use_hex_format_for_raw = self.show_parsed_var.get()
|
|
display_msg = ProtocolHandler.format_for_display(message_bytes, hex_non_printable=use_hex_format_for_raw)
|
|
self.log_message(f"Data: {display_msg}", "com_to_net")
|
|
|
|
# Intentar parsear si está habilitado
|
|
if self.show_parsed_var.get():
|
|
# Decodificar solo para el parseo
|
|
message_str_for_parse = message_bytes.decode('ascii', errors='ignore')
|
|
parsed = ProtocolHandler.parse_adam_message(message_str_for_parse)
|
|
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_bytes)
|
|
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:
|
|
# Los datos de la red (net_data) son bytes.
|
|
# Se reenvían directamente al puerto COM.
|
|
|
|
# Log y parseo (opcional, sobre los datos recibidos directamente)
|
|
use_hex_format_for_raw = self.show_parsed_var.get()
|
|
display_msg = ProtocolHandler.format_for_display(net_data, hex_non_printable=use_hex_format_for_raw)
|
|
self.log_message(f"Data: {display_msg}", "net_to_com")
|
|
|
|
# Intentar parsear si está habilitado (puede ser sobre fragmentos)
|
|
if self.show_parsed_var.get():
|
|
# Decodificar solo para el parseo
|
|
# Nota: parsear fragmentos puede no ser siempre significativo para protocolos como ADAM.
|
|
message_str_for_parse = net_data.decode('ascii', errors='ignore')
|
|
parsed = ProtocolHandler.parse_adam_message(message_str_for_parse)
|
|
if parsed:
|
|
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 (datos red) - 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(net_data) # Enviar los bytes tal cual se recibieron
|
|
self.net_to_com_count += len(net_data) # Contar bytes en lugar de "mensajes"
|
|
self.update_stats()
|
|
except Exception as e:
|
|
self.log_message(f"Error enviando a COM: {e}", "error")
|
|
self.error_count += 1
|
|
self.update_stats()
|
|
|
|
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(current_delay)
|
|
|
|
# Asegurar que el estado se actualice
|
|
if not self.bridging:
|
|
self.frame.after(0, self._ensure_stopped_state)
|
|
|
|
def _find_message_end_conditions(self, buffer_bytes: bytearray):
|
|
"""Verifica si hay condiciones para buscar el final de un mensaje."""
|
|
if not buffer_bytes:
|
|
return False
|
|
has_terminator = any(byte_val in (ord(b'\r'), ord(b'\n')) for byte_val in buffer_bytes)
|
|
return has_terminator or len(buffer_bytes) >= 10
|
|
|
|
def _find_message_end(self, buffer_bytes: bytearray):
|
|
"""Encuentra el final de un mensaje en el buffer de bytes."""
|
|
# Buscar terminadores
|
|
for i, byte_val in enumerate(buffer_bytes):
|
|
if byte_val == ord(b'\r') or byte_val == ord(b'\n'):
|
|
return i + 1
|
|
|
|
# Si no hay terminador pero el buffer es largo, buscar mensaje ADAM completo
|
|
# Esta parte es una heurística para mensajes tipo ADAM que podrían no tener terminador
|
|
# y debe usarse con cuidado para no cortar mensajes prematuramente.
|
|
if len(buffer_bytes) >= 10:
|
|
starts_with_hash = (buffer_bytes[0] == ord(b'#'))
|
|
|
|
is_adam_value_like = False
|
|
if len(buffer_bytes) >= 8: # Asegurar que el slice buffer_bytes[2:8] sea válido
|
|
try:
|
|
# Convertir la parte del valor a string para una verificación más sencilla
|
|
value_part_str = bytes(buffer_bytes[2:8]).decode('ascii')
|
|
# Formato ADAM es XX.XXX (6 caracteres)
|
|
if len(value_part_str) == 6 and value_part_str[2] == '.' and \
|
|
value_part_str[0:2].isdigit() and value_part_str[3:6].isdigit():
|
|
is_adam_value_like = True
|
|
except UnicodeDecodeError:
|
|
pass # No es ASCII, no es el formato ADAM esperado
|
|
|
|
if starts_with_hash or is_adam_value_like:
|
|
# Heurística: si parece ADAM y tiene al menos 10 bytes.
|
|
# Si después de 10 bytes hay un terminador, incluirlo.
|
|
if len(buffer_bytes) > 10 and \
|
|
(buffer_bytes[10] == ord(b'\r') or buffer_bytes[10] == ord(b'\n')):
|
|
return 11
|
|
else:
|
|
# Asumir un mensaje de 10 bytes (ej: #AAXX.XXXCC)
|
|
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))
|
|
# Si net_to_com_count ahora cuenta bytes, el label "NET → COM:" seguido de un número
|
|
# podría interpretarse como mensajes. Para mayor claridad, se podría cambiar el label
|
|
# o el formato del valor (ej. self.net_to_com_var.set(f"{self.net_to_com_count} bytes")).
|
|
# Por ahora, solo actualizamos el valor; el label no cambia.
|
|
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)
|
|
self.bytesize_combo.config(state=state)
|
|
self.parity_combo.config(state=state)
|
|
self.stopbits_combo.config(state=state)
|
|
self.rtscts_check.config(state=state)
|
|
self.dsrdtr_check.config(state=state)
|
|
self.xonxoff_check.config(state=state)
|
|
self.bridge_delay_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(),
|
|
'netcom_rtscts': self.rtscts_var.get(),
|
|
'netcom_dsrdtr': self.dsrdtr_var.get(),
|
|
'netcom_xonxoff': self.xonxoff_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'))
|
|
self.rtscts_var.set(config.get('netcom_rtscts', False))
|
|
self.dsrdtr_var.set(config.get('netcom_dsrdtr', False))
|
|
self.xonxoff_var.set(config.get('netcom_xonxoff', False))
|