Enhance NetCom configuration options and improve data handling
- Added support for additional NetCom parameters: data bits, parity, stop bits, and flow control options (RTS/CTS, DSR/DTR, XON/XOFF). - Updated configuration loading and saving to include new parameters. - Improved data handling in NetComTab for better byte management and logging. - Refactored message parsing and sending to accommodate new data types and formats.
This commit is contained in:
parent
e1c9199cb5
commit
1266fa705d
|
@ -18,11 +18,19 @@ class ConfigManager:
|
||||||
'function_type': 'Sinusoidal',
|
'function_type': 'Sinusoidal',
|
||||||
'min_brix_map': '0',
|
'min_brix_map': '0',
|
||||||
'max_brix_map': '80',
|
'max_brix_map': '80',
|
||||||
'cycle_time': '0.5', # Cambiado de 'period' a 'cycle_time' para tiempo de ciclo completo
|
'cycle_time': '0.5',
|
||||||
'manual_brix': '10.0',
|
'manual_input_type': 'Brix', # Nuevo: 'Brix', 'mA', 'Voltaje'
|
||||||
|
'manual_value': '10.0', # Nuevo: valor correspondiente al manual_input_type
|
||||||
# Configuración para NetCom
|
# Configuración para NetCom
|
||||||
'netcom_com_port': 'COM3',
|
'netcom_com_port': 'COM3',
|
||||||
'netcom_baud_rate': '115200'
|
'netcom_baud_rate': '115200',
|
||||||
|
'netcom_bytesize': 8, # Data bits (5, 6, 7, 8)
|
||||||
|
'netcom_parity': 'N', # Parity ('N', 'E', 'O', 'M', 'S')
|
||||||
|
'netcom_stopbits': 1, # Stop bits (1, 1.5, 2)
|
||||||
|
'netcom_rtscts': False, # Hardware flow control RTS/CTS
|
||||||
|
'netcom_dsrdtr': False, # Hardware flow control DSR/DTR
|
||||||
|
'netcom_xonxoff': False, # Software flow control XON/XOFF
|
||||||
|
'netcom_bridge_delay': 0.001 # Delay in seconds for the bridge polling loop
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_config(self, config_data):
|
def save_config(self, config_data):
|
||||||
|
@ -54,6 +62,12 @@ class ConfigManager:
|
||||||
config['cycle_time'] = config['period']
|
config['cycle_time'] = config['period']
|
||||||
del config['period']
|
del config['period']
|
||||||
|
|
||||||
|
# Migrar 'manual_brix' a 'manual_input_type' y 'manual_value'
|
||||||
|
if 'manual_brix' in config and 'manual_value' not in config:
|
||||||
|
config['manual_value'] = config['manual_brix']
|
||||||
|
config['manual_input_type'] = config.get('manual_input_type', 'Brix') # Asumir Brix si no existe
|
||||||
|
del config['manual_brix']
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -108,6 +122,13 @@ class ConfigManager:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
errors.append("El tiempo de ciclo debe ser un número válido")
|
errors.append("El tiempo de ciclo debe ser un número válido")
|
||||||
|
|
||||||
|
# Validar valor manual
|
||||||
|
try:
|
||||||
|
manual_value = float(config.get('manual_value', '0'))
|
||||||
|
# Aquí se podrían añadir validaciones de rango según el manual_input_type
|
||||||
|
except ValueError:
|
||||||
|
errors.append("El valor manual debe ser un número válido")
|
||||||
|
|
||||||
# Validar puerto serie
|
# Validar puerto serie
|
||||||
if config.get('connection_type') == 'Serial':
|
if config.get('connection_type') == 'Serial':
|
||||||
com_port = config.get('com_port', '')
|
com_port = config.get('com_port', '')
|
||||||
|
|
|
@ -17,9 +17,15 @@ class ConnectionManager:
|
||||||
try:
|
try:
|
||||||
if conn_type == "Serial":
|
if conn_type == "Serial":
|
||||||
self.connection = serial.Serial(
|
self.connection = serial.Serial(
|
||||||
conn_params['port'],
|
port=conn_params['port'],
|
||||||
conn_params['baud'],
|
baudrate=conn_params['baudrate'], # Standard pyserial parameter name
|
||||||
timeout=1
|
timeout=conn_params.get('timeout', 1),
|
||||||
|
bytesize=conn_params.get('bytesize', serial.EIGHTBITS), # Use provided or default
|
||||||
|
parity=conn_params.get('parity', serial.PARITY_NONE), # Use provided or default
|
||||||
|
stopbits=conn_params.get('stopbits', serial.STOPBITS_ONE), # Use provided or default
|
||||||
|
xonxoff=conn_params.get('xonxoff', False),
|
||||||
|
rtscts=conn_params.get('rtscts', False),
|
||||||
|
dsrdtr=conn_params.get('dsrdtr', False)
|
||||||
)
|
)
|
||||||
self.connection_type = "Serial"
|
self.connection_type = "Serial"
|
||||||
|
|
||||||
|
@ -58,21 +64,39 @@ class ConnectionManager:
|
||||||
self.connection_type = None
|
self.connection_type = None
|
||||||
self.dest_address = None
|
self.dest_address = None
|
||||||
|
|
||||||
def send_data(self, data):
|
def send_data(self, data_bytes):
|
||||||
"""Envía datos por la conexión actual"""
|
"""Envía datos por la conexión actual"""
|
||||||
if not self.connection:
|
if not self.connection:
|
||||||
raise Exception("No hay conexión activa")
|
raise Exception("No hay conexión activa")
|
||||||
|
|
||||||
|
data_to_send = None
|
||||||
|
if isinstance(data_bytes, str):
|
||||||
|
# Esto no debería suceder si el llamador (NetComTab, SimulatorTab) funciona como se espera.
|
||||||
|
# Loguear una advertencia e intentar codificar como último recurso.
|
||||||
|
print(f"ADVERTENCIA: ConnectionManager.send_data recibió str, se esperaba bytes. Intentando codificar a ASCII. Datos: {data_bytes!r}")
|
||||||
|
try:
|
||||||
|
data_to_send = data_bytes.encode('ascii')
|
||||||
|
except UnicodeEncodeError as uee:
|
||||||
|
print(f"ERROR CRÍTICO: No se pudo codificar la cadena (str) a ASCII antes de enviar: {uee}. Datos: {data_bytes!r}")
|
||||||
|
# Elevar una excepción clara porque no se puede continuar si la codificación falla.
|
||||||
|
raise Exception(f"Error al enviar datos: la cadena no pudo ser codificada a ASCII: {uee}") from uee
|
||||||
|
elif isinstance(data_bytes, (bytes, bytearray)):
|
||||||
|
data_to_send = data_bytes # Ya es bytes o bytearray (que .write/.send aceptan)
|
||||||
|
else:
|
||||||
|
# Si no es ni str ni bytes/bytearray, es un error de tipo fundamental.
|
||||||
|
print(f"ERROR CRÍTICO: ConnectionManager.send_data recibió un tipo inesperado: {type(data_bytes)}. Se esperaba bytes. Datos: {data_bytes!r}")
|
||||||
|
raise TypeError(f"Error al enviar datos: se esperaba un objeto tipo bytes, pero se recibió {type(data_bytes)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.connection_type == "Serial":
|
if self.connection_type == "Serial":
|
||||||
self.connection.write(data.encode('ascii'))
|
self.connection.write(data_to_send)
|
||||||
elif self.connection_type == "TCP":
|
elif self.connection_type == "TCP":
|
||||||
self.connection.send(data.encode('ascii'))
|
self.connection.send(data_to_send)
|
||||||
elif self.connection_type == "UDP":
|
elif self.connection_type == "UDP":
|
||||||
self.connection.sendto(data.encode('ascii'), self.dest_address)
|
self.connection.sendto(data_to_send, self.dest_address)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Error al enviar datos: {e}")
|
raise Exception(f"Error al enviar datos: {e}")
|
||||||
|
|
||||||
def read_response(self, timeout=0.5):
|
def read_response(self, timeout=0.5):
|
||||||
"""Intenta leer una respuesta del dispositivo"""
|
"""Intenta leer una respuesta del dispositivo"""
|
||||||
if not self.connection:
|
if not self.connection:
|
||||||
|
@ -132,12 +156,12 @@ class ConnectionManager:
|
||||||
data = None
|
data = None
|
||||||
if self.connection_type == "Serial":
|
if self.connection_type == "Serial":
|
||||||
if self.connection.in_waiting > 0:
|
if self.connection.in_waiting > 0:
|
||||||
data = self.connection.read(self.connection.in_waiting).decode('ascii', errors='ignore')
|
data = self.connection.read(self.connection.in_waiting) # Returns bytes
|
||||||
|
|
||||||
elif self.connection_type == "TCP":
|
elif self.connection_type == "TCP":
|
||||||
self.connection.settimeout(0.1)
|
self.connection.settimeout(0.1)
|
||||||
try:
|
try:
|
||||||
data = self.connection.recv(1024).decode('ascii', errors='ignore')
|
data = self.connection.recv(1024) # Returns bytes
|
||||||
if not data: # Conexión cerrada
|
if not data: # Conexión cerrada
|
||||||
return None
|
return None
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
|
@ -147,7 +171,7 @@ class ConnectionManager:
|
||||||
self.connection.settimeout(0.1)
|
self.connection.settimeout(0.1)
|
||||||
try:
|
try:
|
||||||
data, addr = self.connection.recvfrom(1024)
|
data, addr = self.connection.recvfrom(1024)
|
||||||
data = data.decode('ascii', errors='ignore')
|
# data is already bytes
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,6 @@
|
||||||
"cycle_time": "10.0",
|
"cycle_time": "10.0",
|
||||||
"samples_per_cycle": "100",
|
"samples_per_cycle": "100",
|
||||||
"manual_brix": "10.0",
|
"manual_brix": "10.0",
|
||||||
"netcom_com_port": "COM3",
|
"netcom_com_port": "COM9",
|
||||||
"netcom_baud_rate": "115200"
|
"netcom_baud_rate": "9600"
|
||||||
}
|
}
|
|
@ -45,16 +45,35 @@ class ProtocolHandler:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_adam_message(adam_address, brix_value, min_brix_map, max_brix_map):
|
def create_adam_message(adam_address, brix_value, min_brix_map, max_brix_map):
|
||||||
"""Crea un mensaje completo ADAM a partir de un valor Brix"""
|
"""Crea un mensaje completo ADAM (como bytes) a partir de un valor Brix"""
|
||||||
ma_val = ProtocolHandler.scale_to_ma(brix_value, min_brix_map, max_brix_map)
|
ma_val = ProtocolHandler.scale_to_ma(brix_value, min_brix_map, max_brix_map)
|
||||||
ma_str = ProtocolHandler.format_ma_value(ma_val)
|
ma_str = ProtocolHandler.format_ma_value(ma_val)
|
||||||
|
|
||||||
message_part = f"#{adam_address}{ma_str}"
|
message_part = f"#{adam_address}{ma_str}"
|
||||||
checksum = ProtocolHandler.calculate_checksum(message_part)
|
checksum = ProtocolHandler.calculate_checksum(message_part)
|
||||||
full_message = f"{message_part}{checksum}\r"
|
full_message_str = f"{message_part}{checksum}\r"
|
||||||
|
|
||||||
return full_message, ma_val
|
return full_message_str.encode('ascii'), ma_val
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ma_to_voltage(ma_value):
|
||||||
|
"""Convierte valor mA a Voltaje (0-10V). 0mA -> 0V, 20mA -> 10V."""
|
||||||
|
# Escala lineal: Voltage = (ma_value / 20mA) * 10V
|
||||||
|
voltage = (ma_value / 20.0) * 10.0
|
||||||
|
return max(0.0, min(10.0, voltage)) # Asegurar que esté en el rango 0-10V
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def voltage_to_ma(voltage_value):
|
||||||
|
"""Convierte valor Voltaje (0-10V) a mA. 0V -> 0mA, 10V -> 20mA."""
|
||||||
|
# Escala lineal: mA = (voltage_value / 10V) * 20mA
|
||||||
|
ma = (voltage_value / 10.0) * 20.0
|
||||||
|
return max(0.0, min(20.0, ma)) # Asegurar que esté en el rango 0-20mA
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_voltage_display(voltage_value):
|
||||||
|
"""Formatea un valor de Voltaje para mostrar."""
|
||||||
|
return f"{voltage_value:.2f} V"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_adam_message(data):
|
def parse_adam_message(data):
|
||||||
"""
|
"""
|
||||||
|
@ -116,6 +135,25 @@ class ProtocolHandler:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_for_display(message):
|
def format_for_display(message, hex_non_printable=False):
|
||||||
"""Formatea un mensaje para mostrar en el log (reemplaza caracteres no imprimibles)"""
|
"""Formatea un mensaje (bytes o str) para mostrar en el log"""
|
||||||
return message.replace('\r', '<CR>').replace('\n', '<LF>').replace('\t', '<TAB>')
|
if isinstance(message, bytes):
|
||||||
|
if hex_non_printable:
|
||||||
|
parts = []
|
||||||
|
for byte_val in message:
|
||||||
|
# Caracteres ASCII imprimibles (32 a 126) se dejan como están.
|
||||||
|
# Otros se convierten a \xHH.
|
||||||
|
if 32 <= byte_val <= 126:
|
||||||
|
parts.append(chr(byte_val))
|
||||||
|
else:
|
||||||
|
parts.append(f'\\x{byte_val:02x}')
|
||||||
|
message_str = "".join(parts)
|
||||||
|
else:
|
||||||
|
message_str = message.decode('ascii', errors='replace') # 'replace' muestra para no decodificables
|
||||||
|
else: # Asumir que es str
|
||||||
|
message_str = str(message)
|
||||||
|
|
||||||
|
# Si no es formato hexadecimal, reemplazar caracteres de control comunes por representaciones legibles.
|
||||||
|
if not hex_non_printable:
|
||||||
|
message_str = message_str.replace('\r', '<CR>').replace('\n', '<LF>').replace('\t', '<TAB>')
|
||||||
|
return message_str
|
||||||
|
|
|
@ -42,11 +42,49 @@ class NetComTab:
|
||||||
self.com_port_var = tk.StringVar(value=self.shared_config.get('netcom_com_port', 'COM3'))
|
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 = 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")
|
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")
|
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_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 = 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")
|
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
|
||||||
info_frame = ttk.LabelFrame(self.frame, text="Información de Conexión")
|
info_frame = ttk.LabelFrame(self.frame, text="Información de Conexión")
|
||||||
|
@ -194,11 +232,18 @@ class NetComTab:
|
||||||
try:
|
try:
|
||||||
com_port = self.com_port_var.get()
|
com_port = self.com_port_var.get()
|
||||||
baud_rate = int(self.baud_rate_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'):
|
if not com_port.upper().startswith('COM'):
|
||||||
raise ValueError("Puerto COM inválido")
|
raise ValueError("Puerto COM inválido")
|
||||||
if baud_rate <= 0:
|
if baud_rate <= 0:
|
||||||
raise ValueError("Baud rate debe ser mayor que 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:
|
except ValueError as e:
|
||||||
messagebox.showerror("Error", f"Configuración inválida: {e}")
|
messagebox.showerror("Error", f"Configuración inválida: {e}")
|
||||||
|
@ -208,9 +253,22 @@ class NetComTab:
|
||||||
try:
|
try:
|
||||||
self.com_connection.open_connection("Serial", {
|
self.com_connection.open_connection("Serial", {
|
||||||
'port': com_port,
|
'port': com_port,
|
||||||
'baud': baud_rate
|
'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()
|
||||||
})
|
})
|
||||||
self.log_message(f"Puerto COM abierto: {com_port} @ {baud_rate} bps")
|
|
||||||
|
# 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:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"No se pudo abrir puerto COM: {e}")
|
messagebox.showerror("Error", f"No se pudo abrir puerto COM: {e}")
|
||||||
return
|
return
|
||||||
|
@ -282,8 +340,9 @@ class NetComTab:
|
||||||
|
|
||||||
def run_bridge(self):
|
def run_bridge(self):
|
||||||
"""Thread principal del bridge"""
|
"""Thread principal del bridge"""
|
||||||
com_buffer = ""
|
com_buffer = bytearray()
|
||||||
net_buffer = ""
|
# 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:
|
while self.bridging:
|
||||||
try:
|
try:
|
||||||
|
@ -291,21 +350,25 @@ class NetComTab:
|
||||||
com_data = self.com_connection.read_data_non_blocking()
|
com_data = self.com_connection.read_data_non_blocking()
|
||||||
if com_data:
|
if com_data:
|
||||||
com_buffer += com_data
|
com_buffer += com_data
|
||||||
|
|
||||||
# Buscar mensajes completos para logging
|
# Buscar mensajes completos para logging
|
||||||
while '\r' in com_buffer or '\n' in com_buffer or len(com_buffer) >= 10:
|
# Adaptar la condición para bytearray
|
||||||
|
while self._find_message_end_conditions(com_buffer):
|
||||||
end_idx = self._find_message_end(com_buffer)
|
end_idx = self._find_message_end(com_buffer)
|
||||||
if end_idx > 0:
|
if end_idx > 0:
|
||||||
message = com_buffer[:end_idx]
|
message_bytes = bytes(com_buffer[:end_idx])
|
||||||
com_buffer = com_buffer[end_idx:]
|
com_buffer = com_buffer[end_idx:]
|
||||||
|
|
||||||
# Log y parseo
|
# Log y parseo
|
||||||
display_msg = ProtocolHandler.format_for_display(message)
|
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")
|
self.log_message(f"Data: {display_msg}", "com_to_net")
|
||||||
|
|
||||||
# Intentar parsear si está habilitado
|
# Intentar parsear si está habilitado
|
||||||
if self.show_parsed_var.get():
|
if self.show_parsed_var.get():
|
||||||
parsed = ProtocolHandler.parse_adam_message(message)
|
# 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:
|
if parsed:
|
||||||
# Obtener valores de mapeo
|
# Obtener valores de mapeo
|
||||||
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
||||||
|
@ -322,7 +385,7 @@ class NetComTab:
|
||||||
|
|
||||||
# Reenviar a la red
|
# Reenviar a la red
|
||||||
try:
|
try:
|
||||||
self.net_connection.send_data(message)
|
self.net_connection.send_data(message_bytes)
|
||||||
self.com_to_net_count += 1
|
self.com_to_net_count += 1
|
||||||
self.update_stats()
|
self.update_stats()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -335,47 +398,42 @@ class NetComTab:
|
||||||
# Leer de la red
|
# Leer de la red
|
||||||
net_data = self.net_connection.read_data_non_blocking()
|
net_data = self.net_connection.read_data_non_blocking()
|
||||||
if net_data:
|
if net_data:
|
||||||
net_buffer += net_data
|
# Los datos de la red (net_data) son bytes.
|
||||||
|
# Se reenvían directamente al puerto COM.
|
||||||
|
|
||||||
# Buscar mensajes completos para logging
|
# Log y parseo (opcional, sobre los datos recibidos directamente)
|
||||||
while '\r' in net_buffer or '\n' in net_buffer or len(net_buffer) >= 10:
|
use_hex_format_for_raw = self.show_parsed_var.get()
|
||||||
end_idx = self._find_message_end(net_buffer)
|
display_msg = ProtocolHandler.format_for_display(net_data, hex_non_printable=use_hex_format_for_raw)
|
||||||
if end_idx > 0:
|
self.log_message(f"Data: {display_msg}", "net_to_com")
|
||||||
message = net_buffer[:end_idx]
|
|
||||||
net_buffer = net_buffer[end_idx:]
|
# 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)
|
||||||
|
|
||||||
# Log y parseo
|
self.log_message(
|
||||||
display_msg = ProtocolHandler.format_for_display(message)
|
f"ADAM (datos red) - Addr: {parsed['address']}, "
|
||||||
self.log_message(f"Data: {display_msg}", "net_to_com")
|
f"mA: {parsed['ma']:.3f}, "
|
||||||
|
f"Brix: {brix_value:.3f}, "
|
||||||
# Intentar parsear si está habilitado
|
f"Checksum: {'OK' if parsed.get('checksum_valid', True) else 'ERROR'}",
|
||||||
if self.show_parsed_var.get():
|
"parsed"
|
||||||
parsed = ProtocolHandler.parse_adam_message(message)
|
)
|
||||||
if parsed:
|
|
||||||
# Obtener valores de mapeo
|
# Reenviar al COM
|
||||||
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
try:
|
||||||
max_brix = float(self.shared_config['max_brix_map_var'].get())
|
self.com_connection.send_data(net_data) # Enviar los bytes tal cual se recibieron
|
||||||
brix_value = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix, max_brix)
|
self.net_to_com_count += len(net_data) # Contar bytes en lugar de "mensajes"
|
||||||
|
self.update_stats()
|
||||||
self.log_message(
|
except Exception as e:
|
||||||
f"ADAM - Addr: {parsed['address']}, "
|
self.log_message(f"Error enviando a COM: {e}", "error")
|
||||||
f"mA: {parsed['ma']:.3f}, "
|
self.error_count += 1
|
||||||
f"Brix: {brix_value:.3f}, "
|
self.update_stats()
|
||||||
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:
|
except Exception as e:
|
||||||
if self.bridging:
|
if self.bridging:
|
||||||
|
@ -386,26 +444,52 @@ class NetComTab:
|
||||||
|
|
||||||
# Pequeña pausa para no consumir demasiado CPU
|
# Pequeña pausa para no consumir demasiado CPU
|
||||||
if not com_data and not net_data:
|
if not com_data and not net_data:
|
||||||
time.sleep(0.001)
|
time.sleep(current_delay)
|
||||||
|
|
||||||
# Asegurar que el estado se actualice
|
# Asegurar que el estado se actualice
|
||||||
if not self.bridging:
|
if not self.bridging:
|
||||||
self.frame.after(0, self._ensure_stopped_state)
|
self.frame.after(0, self._ensure_stopped_state)
|
||||||
|
|
||||||
def _find_message_end(self, buffer):
|
def _find_message_end_conditions(self, buffer_bytes: bytearray):
|
||||||
"""Encuentra el final de un mensaje en el buffer"""
|
"""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
|
# Buscar terminadores
|
||||||
for i, char in enumerate(buffer):
|
for i, byte_val in enumerate(buffer_bytes):
|
||||||
if char in ['\r', '\n']:
|
if byte_val == ord(b'\r') or byte_val == ord(b'\n'):
|
||||||
return i + 1
|
return i + 1
|
||||||
|
|
||||||
# Si no hay terminador pero el buffer es largo, buscar mensaje ADAM completo
|
# Si no hay terminador pero el buffer es largo, buscar mensaje ADAM completo
|
||||||
if len(buffer) >= 10:
|
# Esta parte es una heurística para mensajes tipo ADAM que podrían no tener terminador
|
||||||
if buffer[0] == '#' or (buffer[2:8].replace('.', '').replace(' ', '').replace('-', '').isdigit()):
|
# y debe usarse con cuidado para no cortar mensajes prematuramente.
|
||||||
# Parece un mensaje ADAM
|
if len(buffer_bytes) >= 10:
|
||||||
if len(buffer) > 10 and buffer[10] in ['\r', '\n']:
|
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
|
return 11
|
||||||
else:
|
else:
|
||||||
|
# Asumir un mensaje de 10 bytes (ej: #AAXX.XXXCC)
|
||||||
return 10
|
return 10
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
|
@ -413,7 +497,11 @@ class NetComTab:
|
||||||
def update_stats(self):
|
def update_stats(self):
|
||||||
"""Actualiza las estadísticas en la GUI"""
|
"""Actualiza las estadísticas en la GUI"""
|
||||||
self.com_to_net_var.set(str(self.com_to_net_count))
|
self.com_to_net_var.set(str(self.com_to_net_count))
|
||||||
self.net_to_com_var.set(str(self.net_to_com_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))
|
self.errors_var.set(str(self.error_count))
|
||||||
|
|
||||||
def clear_log(self):
|
def clear_log(self):
|
||||||
|
@ -427,6 +515,13 @@ class NetComTab:
|
||||||
"""Habilita/deshabilita los controles durante el bridge"""
|
"""Habilita/deshabilita los controles durante el bridge"""
|
||||||
self.com_port_entry.config(state=state)
|
self.com_port_entry.config(state=state)
|
||||||
self.baud_rate_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
|
# También deshabilitar controles compartidos
|
||||||
if 'shared_widgets' in self.shared_config:
|
if 'shared_widgets' in self.shared_config:
|
||||||
|
@ -444,10 +539,16 @@ class NetComTab:
|
||||||
"""Obtiene la configuración actual del NetCom"""
|
"""Obtiene la configuración actual del NetCom"""
|
||||||
return {
|
return {
|
||||||
'netcom_com_port': self.com_port_var.get(),
|
'netcom_com_port': self.com_port_var.get(),
|
||||||
'netcom_baud_rate': self.baud_rate_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):
|
def set_config(self, config):
|
||||||
"""Establece la configuración del NetCom"""
|
"""Establece la configuración del NetCom"""
|
||||||
self.com_port_var.set(config.get('netcom_com_port', 'COM3'))
|
self.com_port_var.set(config.get('netcom_com_port', 'COM3'))
|
||||||
self.baud_rate_var.set(config.get('netcom_baud_rate', '115200'))
|
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))
|
||||||
|
|
Loading…
Reference in New Issue