Add TCP-Server support and enhance connection management
- Implement TCP-Server connection type in ConfigManager and ConnectionManager. - Update MaselliApp to handle TCP-Server mode, including UI adjustments. - Modify NetComTab to prevent operation in TCP-Server mode. - Enhance SimulatorTab to manage client connections and data sending for TCP-Server. - Update trace_tab.py to ensure compatibility with TCP-Server. - Improve logging functionality in utils.py to limit log lines.
This commit is contained in:
parent
86669fc94c
commit
a28f02b4c9
|
@ -90,6 +90,12 @@ class ConfigManager:
|
|||
'port': config.get('com_port', 'COM3'),
|
||||
'baud': int(config.get('baud_rate', '115200'))
|
||||
}
|
||||
elif conn_type == "TCP-Server":
|
||||
# Para TCP-Server, la IP es implícitamente '0.0.0.0' (escuchar en todas las interfaces)
|
||||
# Solo necesitamos el puerto para el bind.
|
||||
return {
|
||||
'port': int(config.get('port', '8899')) # Usa el mismo campo de puerto
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'ip': config.get('ip_address', '192.168.1.100'),
|
||||
|
@ -142,7 +148,7 @@ class ConfigManager:
|
|||
except ValueError:
|
||||
errors.append("La velocidad de baudios debe ser un número entero")
|
||||
|
||||
# Validar configuración TCP/UDP
|
||||
# Validar configuración TCP/UDP/TCP-Server
|
||||
else:
|
||||
try:
|
||||
port = int(config.get('port', '502'))
|
||||
|
|
|
@ -6,15 +6,34 @@ import serial
|
|||
import socket
|
||||
import time
|
||||
|
||||
class ConnectionManagerError(Exception):
|
||||
"""Clase base para excepciones de ConnectionManager."""
|
||||
pass
|
||||
|
||||
class ClientDisconnectedError(ConnectionManagerError):
|
||||
"""Excepción personalizada para cuando un cliente TCP se desconecta."""
|
||||
pass
|
||||
|
||||
class NoClientConnectedError(ConnectionManagerError):
|
||||
"""Excepción personalizada para cuando se intenta enviar datos sin un cliente TCP conectado."""
|
||||
pass
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
self.connection = None # Para Serial (Serial obj), TCP Client (socket), UDP (socket)
|
||||
self.server_socket = None # Para TCP Server (listening socket)
|
||||
self.client_socket = None # Para TCP Server (accepted client connection)
|
||||
self.client_address = None # Para TCP Server (address of accepted client)
|
||||
self.connection_type = None
|
||||
self.dest_address = None # Para UDP
|
||||
|
||||
|
||||
self.ClientDisconnectedError = ClientDisconnectedError
|
||||
self.NoClientConnectedError = NoClientConnectedError
|
||||
|
||||
def open_connection(self, conn_type, conn_params):
|
||||
"""Abre una conexión según el tipo especificado"""
|
||||
try:
|
||||
listening_info = None # Información sobre dónde está escuchando el servidor
|
||||
if conn_type == "Serial":
|
||||
self.connection = serial.Serial(
|
||||
port=conn_params['port'],
|
||||
|
@ -42,12 +61,49 @@ class ConnectionManager:
|
|||
self.dest_address = (conn_params['ip'], conn_params['port'])
|
||||
self.connection = sock
|
||||
self.connection_type = "UDP"
|
||||
|
||||
elif conn_type == "TCP-Server":
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
# conn_params para TCP-Server solo debería tener 'port'
|
||||
# listen_ip_param es la IP que se usará para bind (e.g., '0.0.0.0' o una IP específica)
|
||||
listen_ip_param = conn_params.get('ip', '0.0.0.0')
|
||||
listen_port = conn_params['port']
|
||||
|
||||
actual_listen_description = ""
|
||||
if listen_ip_param == '0.0.0.0':
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
ip_list = socket.gethostbyname_ex(hostname)[2] # Direcciones IPv4
|
||||
|
||||
non_loopback_ips = [ip for ip in ip_list if not ip.startswith("127.")]
|
||||
|
||||
display_ips = non_loopback_ips if non_loopback_ips else ip_list
|
||||
|
||||
if display_ips:
|
||||
actual_listen_description = f"en IPs: {', '.join(display_ips)} en puerto {listen_port}"
|
||||
else:
|
||||
actual_listen_description = f"en todas las interfaces (0.0.0.0) puerto {listen_port} (no se pudieron determinar IPs específicas)"
|
||||
except socket.gaierror:
|
||||
actual_listen_description = f"en todas las interfaces (0.0.0.0) puerto {listen_port} (error al resolver hostname para IPs específicas)"
|
||||
except Exception as e_get_ip:
|
||||
print(f"Advertencia: Error obteniendo IPs locales: {e_get_ip}")
|
||||
actual_listen_description = f"en todas las interfaces (0.0.0.0) puerto {listen_port} (error obteniendo IPs locales)"
|
||||
else: # Se especificó una IP para escuchar
|
||||
actual_listen_description = f"en IP: {listen_ip_param}:{listen_port}"
|
||||
|
||||
return self.connection
|
||||
self.server_socket.bind((listen_ip_param, listen_port))
|
||||
self.server_socket.listen(1) # Escuchar hasta 1 conexión en cola
|
||||
self.connection_type = "TCP-Server"
|
||||
self.connection = self.server_socket # self.connection apunta al socket principal
|
||||
|
||||
listening_info = f"TCP Server escuchando {actual_listen_description}"
|
||||
print(listening_info) # Log para la consola
|
||||
|
||||
return self.connection, listening_info
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Error al abrir conexión {conn_type}: {e}")
|
||||
|
||||
def close_connection(self):
|
||||
"""Cierra la conexión actual"""
|
||||
try:
|
||||
|
@ -57,11 +113,24 @@ class ConnectionManager:
|
|||
elif self.connection_type in ["TCP", "UDP"]:
|
||||
if self.connection:
|
||||
self.connection.close()
|
||||
elif self.connection_type == "TCP-Server":
|
||||
if self.client_socket:
|
||||
try: self.client_socket.close()
|
||||
except Exception as e_client: print(f"Error cerrando client_socket: {e_client}")
|
||||
self.client_socket = None
|
||||
self.client_address = None
|
||||
if self.server_socket: # server_socket es self.connection en este modo
|
||||
try: self.server_socket.close()
|
||||
except Exception as e_server: print(f"Error cerrando server_socket: {e_server}")
|
||||
self.server_socket = None
|
||||
self.connection = None # Asegurar que self.connection también se limpie
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error al cerrar conexión: {e}")
|
||||
finally:
|
||||
self.connection = None
|
||||
self.connection_type = None
|
||||
# No limpiar server_socket, client_socket aquí, ya se hizo arriba si era TCP-Server
|
||||
self.dest_address = None
|
||||
|
||||
def send_data(self, data_bytes):
|
||||
|
@ -94,8 +163,25 @@ class ConnectionManager:
|
|||
self.connection.send(data_to_send)
|
||||
elif self.connection_type == "UDP":
|
||||
self.connection.sendto(data_to_send, self.dest_address)
|
||||
elif self.connection_type == "TCP-Server":
|
||||
if self.client_socket:
|
||||
try:
|
||||
self.client_socket.sendall(data_to_send) # sendall es más robusto
|
||||
except (socket.error, BrokenPipeError, ConnectionResetError) as e_send:
|
||||
print(f"TCP Server: Cliente desconectado durante el envío: {e_send}")
|
||||
self.reset_client_connection() # Limpiar el socket del cliente
|
||||
raise self.ClientDisconnectedError(f"Cliente desconectado: {e_send}") from e_send
|
||||
else:
|
||||
# Opción: ser silencioso si no hay cliente, o lanzar NoClientConnectedError
|
||||
# print(f"TCP Server: No hay cliente conectado, datos no enviados: {data_to_send!r}")
|
||||
pass # No enviar si no hay cliente, la simulación puede continuar
|
||||
except self.ClientDisconnectedError: # Permitir que esta pase tal cual
|
||||
raise
|
||||
except self.NoClientConnectedError: # Permitir que esta pase tal cual
|
||||
raise
|
||||
except Exception as e:
|
||||
raise Exception(f"Error al enviar datos: {e}")
|
||||
# Solo envolver otras excepciones no manejadas específicamente
|
||||
raise Exception(f"Error al enviar datos ({self.connection_type}): {e}") from e
|
||||
|
||||
def read_response(self, timeout=0.5):
|
||||
"""Intenta leer una respuesta del dispositivo"""
|
||||
|
@ -140,7 +226,25 @@ class ConnectionManager:
|
|||
response = response.decode('ascii', errors='ignore')
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
elif self.connection_type == "TCP-Server":
|
||||
if self.client_socket:
|
||||
self.client_socket.settimeout(timeout)
|
||||
try:
|
||||
response_bytes = self.client_socket.recv(1024)
|
||||
if response_bytes:
|
||||
response = response_bytes.decode('ascii', errors='ignore')
|
||||
else: # Cliente cerró conexión
|
||||
self.reset_client_connection()
|
||||
print("TCP Server: Cliente cerró conexión durante lectura.")
|
||||
except socket.timeout:
|
||||
pass # Sin datos en timeout
|
||||
except (socket.error, ConnectionResetError) as e_read:
|
||||
print(f"TCP Server: Error leyendo de cliente o cliente desconectado: {e_read}")
|
||||
self.reset_client_connection()
|
||||
else:
|
||||
# Sin cliente conectado, no hay respuesta posible
|
||||
pass
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
|
@ -174,7 +278,21 @@ class ConnectionManager:
|
|||
# data is already bytes
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
elif self.connection_type == "TCP-Server":
|
||||
if self.client_socket:
|
||||
self.client_socket.settimeout(0.01) # Timeout muy corto para no bloquear
|
||||
try:
|
||||
data = self.client_socket.recv(1024) # Retorna bytes
|
||||
if not data: # Cliente cerró conexión
|
||||
self.reset_client_connection()
|
||||
print("TCP Server: Cliente cerró conexión (lectura no bloqueante).")
|
||||
return None # Indicar que la conexión se cerró
|
||||
except socket.timeout:
|
||||
pass # Sin datos disponibles
|
||||
except (socket.error, ConnectionResetError) as e_read_nb:
|
||||
print(f"TCP Server: Error leyendo (no bloqueante) o cliente desconectado: {e_read_nb}")
|
||||
self.reset_client_connection()
|
||||
data = None # Error
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
|
@ -185,5 +303,43 @@ class ConnectionManager:
|
|||
"""Verifica si hay una conexión activa"""
|
||||
if self.connection_type == "Serial":
|
||||
return self.connection and self.connection.is_open
|
||||
elif self.connection_type == "TCP-Server":
|
||||
# "Conectado" significa que el servidor está escuchando.
|
||||
# Para enviar datos, is_client_connected() es más relevante.
|
||||
return self.server_socket is not None
|
||||
else:
|
||||
return self.connection is not None
|
||||
|
||||
def accept_client(self, timeout=None):
|
||||
"""Acepta una conexión de cliente (solo para modo TCP-Server)."""
|
||||
if self.connection_type != "TCP-Server" or not self.server_socket:
|
||||
return False
|
||||
if self.client_socket: # Ya hay un cliente conectado
|
||||
return True
|
||||
|
||||
original_timeout = self.server_socket.gettimeout()
|
||||
self.server_socket.settimeout(timeout)
|
||||
try:
|
||||
self.client_socket, self.client_address = self.server_socket.accept()
|
||||
self.client_socket.settimeout(None) # Volver a modo bloqueante para send/recv en client_socket
|
||||
print(f"TCP Server: Cliente conectado desde {self.client_address}")
|
||||
self.server_socket.settimeout(original_timeout)
|
||||
return True
|
||||
except socket.timeout:
|
||||
self.server_socket.settimeout(original_timeout)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"TCP Server: Error aceptando cliente: {e}")
|
||||
self.server_socket.settimeout(original_timeout)
|
||||
return False
|
||||
|
||||
def is_client_connected(self): # Específico para TCP-Server
|
||||
return self.connection_type == "TCP-Server" and self.client_socket is not None
|
||||
|
||||
def reset_client_connection(self): # Específico para TCP-Server
|
||||
if self.client_socket:
|
||||
try: self.client_socket.close()
|
||||
except Exception: pass
|
||||
self.client_socket = None
|
||||
self.client_address = None
|
||||
print("TCP Server: Conexión con cliente reseteada.")
|
||||
|
|
|
@ -61,7 +61,7 @@ class MaselliApp:
|
|||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(0, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1) # Notebook
|
||||
|
||||
# Frame de configuración compartida
|
||||
self.create_shared_config_frame(main_frame)
|
||||
|
@ -102,14 +102,14 @@ class MaselliApp:
|
|||
self.connection_type_var = tk.StringVar()
|
||||
self.connection_type_combo = ttk.Combobox(
|
||||
config_frame, textvariable=self.connection_type_var,
|
||||
values=["Serial", "TCP", "UDP"], state="readonly", width=10
|
||||
values=["Serial", "TCP", "UDP", "TCP-Server"], state="readonly", width=10
|
||||
)
|
||||
self.connection_type_combo.grid(row=0, column=1, padx=5, pady=5)
|
||||
self.connection_type_combo.bind("<<ComboboxSelected>>", self.on_connection_type_change)
|
||||
|
||||
# Frame para Serial
|
||||
self.serial_frame = ttk.Frame(config_frame)
|
||||
self.serial_frame.grid(row=0, column=2, columnspan=4, padx=5, pady=5, sticky="ew")
|
||||
self.serial_frame.grid(row=0, column=2, columnspan=6, padx=5, pady=5, sticky="ew") # Ajustado columnspan
|
||||
|
||||
ttk.Label(self.serial_frame, text="Puerto:").grid(row=0, column=0, padx=5, sticky="w")
|
||||
self.com_port_var = tk.StringVar()
|
||||
|
@ -123,18 +123,26 @@ class MaselliApp:
|
|||
|
||||
# Frame para Ethernet
|
||||
self.ethernet_frame = ttk.Frame(config_frame)
|
||||
self.ethernet_frame.grid(row=0, column=2, columnspan=4, padx=5, pady=5, sticky="ew")
|
||||
self.ethernet_frame.grid(row=0, column=2, columnspan=6, padx=5, pady=5, sticky="ew") # Aumentado columnspan
|
||||
self.ethernet_frame.grid_remove()
|
||||
|
||||
ttk.Label(self.ethernet_frame, text="IP:").grid(row=0, column=0, padx=5, sticky="w")
|
||||
self.ip_address_label_widget = ttk.Label(self.ethernet_frame, text="IP:")
|
||||
self.ip_address_label_widget.grid(row=0, column=0, padx=5, sticky="w")
|
||||
self.ip_address_var = tk.StringVar()
|
||||
self.ip_address_entry = ttk.Entry(self.ethernet_frame, textvariable=self.ip_address_var, width=15)
|
||||
self.ip_address_entry.grid(row=0, column=1, padx=5)
|
||||
|
||||
ttk.Label(self.ethernet_frame, text="Puerto:").grid(row=0, column=2, padx=5, sticky="w")
|
||||
self.port_label_widget = ttk.Label(self.ethernet_frame, text="Puerto:")
|
||||
self.port_label_widget.grid(row=0, column=2, padx=5, sticky="w")
|
||||
self.port_var = tk.StringVar()
|
||||
self.port_entry = ttk.Entry(self.ethernet_frame, textvariable=self.port_var, width=8)
|
||||
self.port_entry.grid(row=0, column=3, padx=5)
|
||||
self.port_entry.grid(row=0, column=3, padx=(0,5), pady=5) # Ajustado padx
|
||||
|
||||
# Label para mostrar el cliente conectado en modo TCP-Server
|
||||
self.client_connected_label_widget = ttk.Label(self.ethernet_frame, text="Cliente Conectado:")
|
||||
# Se mostrará/ocultará en on_connection_type_change
|
||||
self.client_connected_var = tk.StringVar(value="Ninguno")
|
||||
self.client_connected_display = ttk.Label(self.ethernet_frame, textvariable=self.client_connected_var, width=25)
|
||||
|
||||
# Parámetros de mapeo
|
||||
ttk.Label(config_frame, text="Min Brix [4mA]:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
|
||||
|
@ -162,6 +170,7 @@ class MaselliApp:
|
|||
'port_var': self.port_var,
|
||||
'min_brix_map_var': self.min_brix_map_var,
|
||||
'max_brix_map_var': self.max_brix_map_var,
|
||||
'client_connected_var': self.client_connected_var, # Para actualizar desde el simulador
|
||||
'shared_widgets': [
|
||||
self.connection_type_combo,
|
||||
self.com_port_entry,
|
||||
|
@ -169,7 +178,8 @@ class MaselliApp:
|
|||
self.ip_address_entry,
|
||||
self.port_entry,
|
||||
self.min_brix_map_entry,
|
||||
self.max_brix_map_entry
|
||||
self.max_brix_map_entry,
|
||||
# self.client_connected_display # No deshabilitar el display, solo su contenido
|
||||
]
|
||||
})
|
||||
|
||||
|
@ -274,16 +284,71 @@ class MaselliApp:
|
|||
def on_connection_type_change(self, event=None):
|
||||
"""Maneja el cambio de tipo de conexión"""
|
||||
conn_type = self.connection_type_var.get()
|
||||
is_server_mode = (conn_type == "TCP-Server")
|
||||
|
||||
if conn_type == "Serial":
|
||||
self.ethernet_frame.grid_remove()
|
||||
self.serial_frame.grid()
|
||||
else:
|
||||
# No es necesario manipular los widgets dentro de ethernet_frame si está oculto
|
||||
self.client_connected_label_widget.grid_remove()
|
||||
self.client_connected_display.grid_remove()
|
||||
self.client_connected_var.set("Ninguno")
|
||||
elif conn_type == "TCP-Server":
|
||||
self.serial_frame.grid_remove()
|
||||
self.ethernet_frame.grid()
|
||||
|
||||
self.ip_address_label_widget.grid_remove() # Ocultar etiqueta IP
|
||||
self.ip_address_entry.config(state=tk.DISABLED) # IP no se usa para el servidor
|
||||
self.ip_address_entry.grid_remove() # Ocultar campo IP
|
||||
|
||||
self.port_label_widget.grid(row=0, column=2, padx=5, sticky="w") # Asegurar que la etiqueta Puerto esté visible
|
||||
self.port_entry.config(state=tk.NORMAL) # Puerto es para escuchar
|
||||
self.port_entry.grid(row=0, column=3, padx=(0,5), pady=5) # Asegurar que el campo Puerto esté visible
|
||||
|
||||
self.client_connected_label_widget.grid(row=0, column=4, padx=(10,2), pady=5, sticky="w")
|
||||
self.client_connected_display.grid(row=0, column=5, padx=(0,5), pady=5, sticky="w")
|
||||
else: # TCP, UDP
|
||||
self.serial_frame.grid_remove()
|
||||
self.ethernet_frame.grid()
|
||||
|
||||
self.ip_address_label_widget.grid(row=0, column=0, padx=5, sticky="w") # Asegurar que la etiqueta IP esté visible
|
||||
self.ip_address_entry.config(state=tk.NORMAL)
|
||||
self.ip_address_entry.grid(row=0, column=1, padx=5) # Asegurar que el campo IP esté visible
|
||||
|
||||
self.port_label_widget.grid(row=0, column=2, padx=5, sticky="w") # Asegurar que la etiqueta Puerto esté visible
|
||||
self.port_entry.config(state=tk.NORMAL)
|
||||
self.port_entry.grid(row=0, column=3, padx=(0,5), pady=5) # Asegurar que el campo Puerto esté visible
|
||||
|
||||
self.client_connected_label_widget.grid_remove()
|
||||
self.client_connected_display.grid_remove()
|
||||
self.client_connected_var.set("Ninguno")
|
||||
|
||||
# Actualizar info en NetCom
|
||||
if hasattr(self, 'netcom_tab'):
|
||||
self.netcom_tab.update_net_info()
|
||||
|
||||
# Habilitar/deshabilitar botones Start en otras pestañas según compatibilidad
|
||||
if hasattr(self, 'simulator_tab'):
|
||||
# El simulador maneja TCP-Server, su lógica de botón es interna
|
||||
pass
|
||||
|
||||
if hasattr(self, 'trace_tab'):
|
||||
if is_server_mode:
|
||||
self.trace_tab.start_button.config(state=tk.DISABLED)
|
||||
if self.trace_tab.tracing: # Si estaba trazando y el modo cambió
|
||||
self.trace_tab.stop_trace()
|
||||
messagebox.showinfo("Trace Detenido", "El modo Trace se detuvo porque el tipo de conexión cambió a TCP-Server, que no es compatible.")
|
||||
elif not self.trace_tab.tracing : # Habilitar solo si no está trazando
|
||||
self.trace_tab.start_button.config(state=tk.NORMAL)
|
||||
|
||||
if hasattr(self, 'netcom_tab'):
|
||||
if is_server_mode:
|
||||
self.netcom_tab.start_button.config(state=tk.DISABLED)
|
||||
if self.netcom_tab.bridging: # Si estaba en modo bridge
|
||||
self.netcom_tab.stop_bridge()
|
||||
messagebox.showinfo("NetCom Detenido", "El modo NetCom se detuvo porque el tipo de conexión cambió a TCP-Server, que no es compatible.")
|
||||
elif not self.netcom_tab.bridging: # Habilitar solo si no está en modo bridge
|
||||
self.netcom_tab.start_button.config(state=tk.NORMAL)
|
||||
|
||||
def save_config(self):
|
||||
"""Guarda la configuración actual"""
|
||||
|
@ -337,6 +402,8 @@ class MaselliApp:
|
|||
|
||||
# Actualizar vista
|
||||
self.on_connection_type_change()
|
||||
if self.connection_type_var.get() != "TCP-Server":
|
||||
self.client_connected_var.set("Ninguno")
|
||||
|
||||
def on_closing(self):
|
||||
"""Maneja el cierre de la aplicación"""
|
||||
|
@ -352,3 +419,4 @@ class MaselliApp:
|
|||
|
||||
# Cerrar ventana
|
||||
self.root.destroy()
|
||||
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"connection_type": "TCP",
|
||||
"connection_type": "TCP-Server",
|
||||
"com_port": "COM8",
|
||||
"baud_rate": "115200",
|
||||
"ip_address": "10.1.33.18",
|
||||
"port": "8899",
|
||||
"min_brix_map": "0",
|
||||
"max_brix_map": "80",
|
||||
"max_brix_map": "60",
|
||||
"adam_address": "01",
|
||||
"function_type": "Manual",
|
||||
"cycle_time": "3",
|
||||
"function_type": "Sinusoidal",
|
||||
"cycle_time": "15",
|
||||
"samples_per_cycle": "100",
|
||||
"manual_input_type": "Brix",
|
||||
"manual_value": "0.00",
|
||||
|
|
|
@ -224,6 +224,12 @@ class NetComTab:
|
|||
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()
|
||||
|
@ -251,7 +257,8 @@ class NetComTab:
|
|||
|
||||
# Abrir conexión COM física
|
||||
try:
|
||||
self.com_connection.open_connection("Serial", {
|
||||
# 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()),
|
||||
|
@ -288,8 +295,12 @@ class NetComTab:
|
|||
# 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()}")
|
||||
# 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}")
|
||||
|
|
|
@ -347,36 +347,61 @@ class SimulatorTab:
|
|||
conn_type = current_config_values['connection_type']
|
||||
conn_params = self.shared_config['config_manager'].get_connection_params(current_config_values)
|
||||
|
||||
temp_conn = ConnectionManager()
|
||||
try:
|
||||
temp_conn.open_connection(conn_type, conn_params)
|
||||
Utils.log_message(self.log_text, f"Conexión {conn_type} abierta temporalmente.")
|
||||
Utils.log_message(self.log_text, f"Enviando Manual: {ProtocolHandler.format_for_display(message)}")
|
||||
if conn_type == "TCP-Server":
|
||||
if not self.connection_manager.is_client_connected(): # Verificar el connection_manager de la pestaña
|
||||
Utils.log_message(self.log_text, "Envío Manual (TCP Server): Ningún cliente conectado.")
|
||||
messagebox.showinfo("TCP Server", "Ningún cliente conectado para enviar datos manualmente.")
|
||||
return
|
||||
|
||||
temp_conn.send_data(message)
|
||||
# No necesitamos 'listening_details' aquí porque la conexión ya está establecida
|
||||
# y el log de inicio ya se hizo. Solo usamos la conexión existente.
|
||||
# La llamada a open_connection no ocurre aquí para TCP-Server en modo manual.
|
||||
|
||||
response = temp_conn.read_response(timeout=0.5)
|
||||
if response and response.strip():
|
||||
Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}")
|
||||
try:
|
||||
# message ya es bytes
|
||||
self.connection_manager.send_data(message)
|
||||
Utils.log_message(self.log_text, f"Enviando Manual (TCP Server): {ProtocolHandler.format_for_display(message)}")
|
||||
# No se espera respuesta en modo servidor para el simulador
|
||||
except self.connection_manager.ClientDisconnectedError:
|
||||
Utils.log_message(self.log_text, "Envío Manual (TCP Server): Cliente desconectado durante el envío.")
|
||||
messagebox.showerror("TCP Server Error", "El cliente se desconectó durante el envío manual.")
|
||||
except Exception as e_manual_server:
|
||||
Utils.log_message(self.log_text, f"Error al enviar manualmente (TCP Server): {e_manual_server}")
|
||||
messagebox.showerror("Error", str(e_manual_server))
|
||||
return # Terminar aquí para envío manual en TCP-Server
|
||||
|
||||
# Lógica existente para otros tipos de conexión (Serial, TCP Client, UDP)
|
||||
else:
|
||||
temp_conn = ConnectionManager()
|
||||
try:
|
||||
# open_connection ahora devuelve (connection_object, listening_info)
|
||||
_, _ = temp_conn.open_connection(conn_type, conn_params) # Ignoramos listening_info para conexión temporal
|
||||
Utils.log_message(self.log_text, f"Conexión {conn_type} abierta temporalmente.")
|
||||
Utils.log_message(self.log_text, f"Enviando Manual: {ProtocolHandler.format_for_display(message)}")
|
||||
|
||||
parsed = ProtocolHandler.parse_adam_message(response)
|
||||
if parsed:
|
||||
brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map)
|
||||
Utils.log_message(self.log_text,
|
||||
f" -> Addr: {parsed['address']}, "
|
||||
f"mA: {parsed['ma']:.3f}, "
|
||||
f"Brix: {brix_resp:.3f}")
|
||||
|
||||
except Exception as e:
|
||||
Utils.log_message(self.log_text, f"Error al enviar: {e}")
|
||||
messagebox.showerror("Error", str(e))
|
||||
finally:
|
||||
temp_conn.close_connection()
|
||||
Utils.log_message(self.log_text, "Conexión cerrada.")
|
||||
temp_conn.send_data(message) # message ya es bytes
|
||||
|
||||
response = temp_conn.read_response(timeout=0.5)
|
||||
if response and response.strip():
|
||||
Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}")
|
||||
|
||||
parsed = ProtocolHandler.parse_adam_message(response)
|
||||
if parsed:
|
||||
brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map)
|
||||
Utils.log_message(self.log_text,
|
||||
f" -> Addr: {parsed['address']}, "
|
||||
f"mA: {parsed['ma']:.3f}, "
|
||||
f"Brix: {brix_resp:.3f}")
|
||||
except Exception as e:
|
||||
Utils.log_message(self.log_text, f"Error al enviar: {e}")
|
||||
messagebox.showerror("Error", str(e))
|
||||
finally:
|
||||
temp_conn.close_connection()
|
||||
Utils.log_message(self.log_text, "Conexión cerrada.")
|
||||
|
||||
except (ValueError, KeyError, TypeError) as e:
|
||||
messagebox.showerror("Error", f"Valores inválidos en la configuración o entrada: {e}")
|
||||
|
||||
|
||||
def start_simulation(self):
|
||||
"""Inicia la simulación continua"""
|
||||
if self.simulating:
|
||||
|
@ -418,8 +443,14 @@ class SimulatorTab:
|
|||
conn_type = current_config_values['connection_type']
|
||||
conn_params = self.shared_config['config_manager'].get_connection_params(current_config_values)
|
||||
|
||||
self.connection_manager.open_connection(conn_type, conn_params)
|
||||
Utils.log_message(self.log_text, f"Conexión {conn_type} abierta para simulación.")
|
||||
# open_connection ahora devuelve (connection_object, listening_info)
|
||||
# El connection_object se guarda internamente en self.connection_manager
|
||||
_, listening_details = self.connection_manager.open_connection(conn_type, conn_params)
|
||||
|
||||
if conn_type == "TCP-Server":
|
||||
Utils.log_message(self.log_text, f"{listening_details} para simulación.")
|
||||
elif conn_type != "TCP-Server": # Para otros tipos, el mensaje genérico
|
||||
Utils.log_message(self.log_text, f"Conexión {conn_type} abierta para simulación.")
|
||||
except Exception as e:
|
||||
messagebox.showerror("Error de Conexión", str(e))
|
||||
return
|
||||
|
@ -430,6 +461,8 @@ class SimulatorTab:
|
|||
self.start_button.config(state=tk.DISABLED)
|
||||
self.stop_button.config(state=tk.NORMAL)
|
||||
self._set_entries_state(tk.DISABLED)
|
||||
if conn_type == "TCP-Server":
|
||||
self.shared_config['client_connected_var'].set("Esperando...")
|
||||
|
||||
self.simulation_thread = threading.Thread(target=self.run_simulation, daemon=True)
|
||||
self.simulation_thread.start()
|
||||
|
@ -452,6 +485,8 @@ class SimulatorTab:
|
|||
self.stop_button.config(state=tk.DISABLED)
|
||||
self._set_entries_state(tk.NORMAL)
|
||||
self.on_function_type_change() # Re-evaluar estado de controles manuales
|
||||
if self.connection_manager.connection_type == "TCP-Server": # Limpiar info del cliente
|
||||
self.shared_config['client_connected_var'].set("Ninguno")
|
||||
|
||||
Utils.log_message(self.log_text, "Simulación detenida.")
|
||||
self.current_brix_var.set("---")
|
||||
|
@ -467,7 +502,17 @@ 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
|
||||
|
||||
# Obtener la configuración actual para el log del puerto en TCP-Server
|
||||
current_config_values = {
|
||||
'connection_type': self.shared_config['connection_type_var'].get(),
|
||||
'com_port': self.shared_config['com_port_var'].get(),
|
||||
'baud_rate': self.shared_config['baud_rate_var'].get(),
|
||||
'ip_address': self.shared_config['ip_address_var'].get(),
|
||||
'port': self.shared_config['port_var'].get(),
|
||||
}
|
||||
|
||||
sample_period = cycle_time / samples_per_cycle
|
||||
|
||||
while self.simulating:
|
||||
|
@ -494,23 +539,45 @@ class SimulatorTab:
|
|||
|
||||
self.frame.after(0, lambda b=current_brix, m=ma_value: self.add_data_point(b, m))
|
||||
|
||||
Utils.log_message(self.log_text, f"Enviando: {ProtocolHandler.format_for_display(message)}")
|
||||
|
||||
try:
|
||||
self.connection_manager.send_data(message)
|
||||
response = self.connection_manager.read_response(timeout=0.1)
|
||||
if response and response.strip():
|
||||
Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}")
|
||||
parsed = ProtocolHandler.parse_adam_message(response)
|
||||
if parsed:
|
||||
brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map)
|
||||
Utils.log_message(self.log_text,
|
||||
f" -> Addr: {parsed['address']}, "
|
||||
f"mA: {parsed['ma']:.3f}, "
|
||||
f"Brix: {brix_resp:.3f}")
|
||||
if conn_type == "TCP-Server":
|
||||
if not self.connection_manager.is_client_connected():
|
||||
# Loguear solo si el estado cambia o periódicamente para evitar spam
|
||||
if not hasattr(self, '_waiting_for_client_logged') or not self._waiting_for_client_logged:
|
||||
port_to_log = self.shared_config['config_manager'].get_connection_params(current_config_values)['port']
|
||||
Utils.log_message(self.log_text, f"TCP Server: Esperando cliente en puerto {port_to_log}...")
|
||||
self._waiting_for_client_logged = True
|
||||
|
||||
if self.connection_manager.accept_client(timeout=0.05): # Intento corto no bloqueante
|
||||
Utils.log_message(self.log_text, f"TCP Server: Cliente conectado desde {self.connection_manager.client_address}")
|
||||
client_info = f"{self.connection_manager.client_address[0]}:{self.connection_manager.client_address[1]}"
|
||||
self.shared_config['client_connected_var'].set(client_info)
|
||||
self._waiting_for_client_logged = False # Resetear flag de log
|
||||
elif not self.connection_manager.is_client_connected() and \
|
||||
self.shared_config['client_connected_var'].get() != "Esperando...":
|
||||
self.shared_config['client_connected_var'].set("Esperando...")
|
||||
|
||||
Utils.log_message(self.log_text, f"Enviando: {ProtocolHandler.format_for_display(message)}")
|
||||
self.connection_manager.send_data(message)
|
||||
|
||||
if conn_type != "TCP-Server": # No leer respuesta en modo servidor
|
||||
response = self.connection_manager.read_response(timeout=0.1)
|
||||
if response and response.strip():
|
||||
Utils.log_message(self.log_text, f"Respuesta: {ProtocolHandler.format_for_display(response)}")
|
||||
parsed = ProtocolHandler.parse_adam_message(response)
|
||||
if parsed:
|
||||
brix_resp = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix_map, max_brix_map)
|
||||
Utils.log_message(self.log_text,
|
||||
f" -> Addr: {parsed['address']}, "
|
||||
f"mA: {parsed['ma']:.3f}, "
|
||||
f"Brix: {brix_resp:.3f}")
|
||||
except self.connection_manager.ClientDisconnectedError:
|
||||
Utils.log_message(self.log_text, "TCP Server: Cliente desconectado. Esperando nueva conexión.")
|
||||
if conn_type == "TCP-Server":
|
||||
self.shared_config['client_connected_var'].set("Esperando...")
|
||||
self._waiting_for_client_logged = False # Permitir que se loguee "esperando" de nuevo
|
||||
except Exception as e:
|
||||
Utils.log_message(self.log_text, f"Error en comunicación: {e}")
|
||||
Utils.log_message(self.log_text, f"Error en comunicación ({conn_type}): {e}")
|
||||
self.frame.after(0, self.stop_simulation_error) # Schedule GUI update from main thread
|
||||
break
|
||||
|
||||
|
|
|
@ -9,6 +9,13 @@ import time
|
|||
import csv
|
||||
from collections import deque
|
||||
from datetime import datetime
|
||||
import sys # Add sys import
|
||||
import os # Add os import
|
||||
|
||||
# If this script is run directly, add the parent directory to sys.path
|
||||
# to allow imports of modules like protocol_handler, connection_manager, utils
|
||||
if __name__ == "__main__" and __package__ is None:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from protocol_handler import ProtocolHandler
|
||||
from connection_manager import ConnectionManager
|
||||
|
@ -120,6 +127,12 @@ class TraceTab:
|
|||
if self.tracing:
|
||||
messagebox.showwarning("Advertencia", "El trace ya está en curso.")
|
||||
return
|
||||
|
||||
# Verificar si el tipo de conexión global es compatible
|
||||
global_conn_type = self.shared_config['connection_type_var'].get()
|
||||
if global_conn_type == "TCP-Server":
|
||||
messagebox.showerror("Modo No Compatible", "El modo Trace no es compatible cuando el tipo de conexión global es TCP-Server.")
|
||||
return
|
||||
|
||||
# Crear archivo CSV
|
||||
csv_filename = Utils.create_csv_filename("maselli_trace")
|
||||
|
@ -201,7 +214,7 @@ class TraceTab:
|
|||
|
||||
def run_trace(self):
|
||||
"""Thread principal para recepción de datos"""
|
||||
buffer = ""
|
||||
buffer = bytearray() # Cambiar buffer a bytearray
|
||||
|
||||
while self.tracing:
|
||||
try:
|
||||
|
@ -209,32 +222,43 @@ class TraceTab:
|
|||
data = self.connection_manager.read_data_non_blocking()
|
||||
|
||||
if data:
|
||||
buffer += data
|
||||
buffer.extend(data) # Usar extend para bytearray
|
||||
|
||||
# Buscar mensajes completos
|
||||
while '\r' in buffer or '\n' in buffer or len(buffer) >= 10:
|
||||
# Encontrar el primer terminador
|
||||
# Las condiciones de búsqueda ahora deben usar bytes
|
||||
while b'\r' in buffer or b'\n' in buffer or len(buffer) >= 10: # Encontrar el primer terminador
|
||||
end_idx = -1
|
||||
for i, char in enumerate(buffer):
|
||||
if char in ['\r', '\n']:
|
||||
# Iterar sobre los valores de byte
|
||||
for i, byte_val in enumerate(buffer):
|
||||
if byte_val == ord(b'\r') or byte_val == ord(b'\n'):
|
||||
end_idx = i + 1
|
||||
break
|
||||
|
||||
# Si no hay terminador pero el buffer es largo, buscar mensaje completo
|
||||
if end_idx == -1 and len(buffer) >= 10:
|
||||
# Verificar si hay un mensaje ADAM completo
|
||||
if buffer[0] == '#' or (len(buffer) >= 10 and buffer[2:8].replace('.', '').isdigit()):
|
||||
end_idx = 10 # Longitud mínima de un mensaje ADAM
|
||||
if len(buffer) > 10 and buffer[10] in ['\r', '\n']:
|
||||
end_idx = 11
|
||||
# Heurística: si empieza con '#' o parece un valor ADAM
|
||||
# Decodificar solo la parte necesaria para la heurística
|
||||
is_adam_like = False
|
||||
try:
|
||||
temp_str_for_check = buffer[:10].decode('ascii', errors='ignore')
|
||||
if temp_str_for_check.startswith('#') or \
|
||||
(len(temp_str_for_check) >= 8 and temp_str_for_check[2:8].replace('.', '').isdigit()):
|
||||
is_adam_like = True
|
||||
except: pass
|
||||
|
||||
if is_adam_like:
|
||||
end_idx = 10 # Longitud de un mensaje ADAM sin terminador explícito
|
||||
if len(buffer) > 10 and (buffer[10] == ord(b'\r') or buffer[10] == ord(b'\n')):
|
||||
end_idx = 11
|
||||
|
||||
if end_idx > 0:
|
||||
message = buffer[:end_idx]
|
||||
message_bytes = bytes(buffer[:end_idx]) # Extraer como bytes
|
||||
buffer = buffer[end_idx:]
|
||||
|
||||
# Procesar mensaje si tiene contenido
|
||||
if message.strip():
|
||||
self._process_message(message)
|
||||
message_str = message_bytes.decode('ascii', errors='ignore') # Decodificar a string
|
||||
if message_str.strip(): # Procesar si la cadena decodificada tiene contenido
|
||||
self._process_message(message_str)
|
||||
else:
|
||||
break
|
||||
|
||||
|
|
22
utils.py
22
utils.py
|
@ -6,16 +6,28 @@ import tkinter as tk
|
|||
from datetime import datetime
|
||||
import os
|
||||
|
||||
MAX_LOG_LINES = 100 # Número máximo de líneas en el log
|
||||
|
||||
class Utils:
|
||||
@staticmethod
|
||||
def log_message(log_widget, message):
|
||||
"""Escribe un mensaje con timestamp en el widget de log especificado"""
|
||||
"""Escribe un mensaje con timestamp en el widget de log especificado, limitando el número de líneas."""
|
||||
if log_widget:
|
||||
log_widget.configure(state=tk.NORMAL)
|
||||
timestamp = datetime.now().strftime('%H:%M:%S')
|
||||
log_widget.insert(tk.END, f"[{timestamp}] {message}\n")
|
||||
|
||||
# Limitar el número de líneas
|
||||
num_lines = int(log_widget.index('end-1c').split('.')[0])
|
||||
if num_lines > MAX_LOG_LINES:
|
||||
lines_to_delete = num_lines - MAX_LOG_LINES
|
||||
# Sumamos 1.0 porque delete va hasta el índice anterior al segundo parámetro
|
||||
log_widget.delete('1.0', f"{lines_to_delete + 1}.0")
|
||||
|
||||
log_widget.see(tk.END)
|
||||
log_widget.configure(state=tk.DISABLED)
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def load_icon(root):
|
||||
|
@ -81,3 +93,11 @@ class Utils:
|
|||
for container in data_containers:
|
||||
if hasattr(container, 'clear'):
|
||||
container.clear()
|
||||
|
||||
@staticmethod
|
||||
def clear_log_widget(log_widget):
|
||||
"""Limpia el contenido del widget de log especificado."""
|
||||
if log_widget:
|
||||
log_widget.configure(state=tk.NORMAL)
|
||||
log_widget.delete('1.0', tk.END)
|
||||
log_widget.configure(state=tk.DISABLED)
|
||||
|
|
Loading…
Reference in New Issue