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',
|
||||
'min_brix_map': '0',
|
||||
'max_brix_map': '80',
|
||||
'cycle_time': '0.5', # Cambiado de 'period' a 'cycle_time' para tiempo de ciclo completo
|
||||
'manual_brix': '10.0',
|
||||
'cycle_time': '0.5',
|
||||
'manual_input_type': 'Brix', # Nuevo: 'Brix', 'mA', 'Voltaje'
|
||||
'manual_value': '10.0', # Nuevo: valor correspondiente al manual_input_type
|
||||
# Configuración para NetCom
|
||||
'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):
|
||||
|
@ -54,6 +62,12 @@ class ConfigManager:
|
|||
config['cycle_time'] = 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
|
||||
|
||||
except Exception as e:
|
||||
|
@ -108,6 +122,13 @@ class ConfigManager:
|
|||
except ValueError:
|
||||
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
|
||||
if config.get('connection_type') == 'Serial':
|
||||
com_port = config.get('com_port', '')
|
||||
|
|
|
@ -17,9 +17,15 @@ class ConnectionManager:
|
|||
try:
|
||||
if conn_type == "Serial":
|
||||
self.connection = serial.Serial(
|
||||
conn_params['port'],
|
||||
conn_params['baud'],
|
||||
timeout=1
|
||||
port=conn_params['port'],
|
||||
baudrate=conn_params['baudrate'], # Standard pyserial parameter name
|
||||
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"
|
||||
|
||||
|
@ -58,21 +64,39 @@ class ConnectionManager:
|
|||
self.connection_type = None
|
||||
self.dest_address = None
|
||||
|
||||
def send_data(self, data):
|
||||
def send_data(self, data_bytes):
|
||||
"""Envía datos por la conexión actual"""
|
||||
if not self.connection:
|
||||
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:
|
||||
if self.connection_type == "Serial":
|
||||
self.connection.write(data.encode('ascii'))
|
||||
self.connection.write(data_to_send)
|
||||
elif self.connection_type == "TCP":
|
||||
self.connection.send(data.encode('ascii'))
|
||||
self.connection.send(data_to_send)
|
||||
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:
|
||||
raise Exception(f"Error al enviar datos: {e}")
|
||||
|
||||
|
||||
def read_response(self, timeout=0.5):
|
||||
"""Intenta leer una respuesta del dispositivo"""
|
||||
if not self.connection:
|
||||
|
@ -132,12 +156,12 @@ class ConnectionManager:
|
|||
data = None
|
||||
if self.connection_type == "Serial":
|
||||
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":
|
||||
self.connection.settimeout(0.1)
|
||||
try:
|
||||
data = self.connection.recv(1024).decode('ascii', errors='ignore')
|
||||
data = self.connection.recv(1024) # Returns bytes
|
||||
if not data: # Conexión cerrada
|
||||
return None
|
||||
except socket.timeout:
|
||||
|
@ -147,7 +171,7 @@ class ConnectionManager:
|
|||
self.connection.settimeout(0.1)
|
||||
try:
|
||||
data, addr = self.connection.recvfrom(1024)
|
||||
data = data.decode('ascii', errors='ignore')
|
||||
# data is already bytes
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"cycle_time": "10.0",
|
||||
"samples_per_cycle": "100",
|
||||
"manual_brix": "10.0",
|
||||
"netcom_com_port": "COM3",
|
||||
"netcom_baud_rate": "115200"
|
||||
"netcom_com_port": "COM9",
|
||||
"netcom_baud_rate": "9600"
|
||||
}
|
|
@ -45,16 +45,35 @@ class ProtocolHandler:
|
|||
|
||||
@staticmethod
|
||||
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_str = ProtocolHandler.format_ma_value(ma_val)
|
||||
|
||||
message_part = f"#{adam_address}{ma_str}"
|
||||
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
|
||||
def parse_adam_message(data):
|
||||
"""
|
||||
|
@ -116,6 +135,25 @@ class ProtocolHandler:
|
|||
return None
|
||||
|
||||
@staticmethod
|
||||
def format_for_display(message):
|
||||
"""Formatea un mensaje para mostrar en el log (reemplaza caracteres no imprimibles)"""
|
||||
return message.replace('\r', '<CR>').replace('\n', '<LF>').replace('\t', '<TAB>')
|
||||
def format_for_display(message, hex_non_printable=False):
|
||||
"""Formatea un mensaje (bytes o str) para mostrar en el log"""
|
||||
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_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")
|
||||
|
@ -194,11 +232,18 @@ class NetComTab:
|
|||
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}")
|
||||
|
@ -208,9 +253,22 @@ class NetComTab:
|
|||
try:
|
||||
self.com_connection.open_connection("Serial", {
|
||||
'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:
|
||||
messagebox.showerror("Error", f"No se pudo abrir puerto COM: {e}")
|
||||
return
|
||||
|
@ -282,8 +340,9 @@ class NetComTab:
|
|||
|
||||
def run_bridge(self):
|
||||
"""Thread principal del bridge"""
|
||||
com_buffer = ""
|
||||
net_buffer = ""
|
||||
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:
|
||||
|
@ -291,21 +350,25 @@ class NetComTab:
|
|||
com_data = self.com_connection.read_data_non_blocking()
|
||||
if com_data:
|
||||
com_buffer += com_data
|
||||
|
||||
|
||||
# Buscar mensajes completos para logging
|
||||
while '\r' in com_buffer or '\n' in com_buffer or len(com_buffer) >= 10:
|
||||
# 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 = com_buffer[:end_idx]
|
||||
message_bytes = bytes(com_buffer[:end_idx])
|
||||
com_buffer = com_buffer[end_idx:]
|
||||
|
||||
# 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")
|
||||
|
||||
# Intentar parsear si está habilitado
|
||||
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:
|
||||
# Obtener valores de mapeo
|
||||
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
||||
|
@ -322,7 +385,7 @@ class NetComTab:
|
|||
|
||||
# Reenviar a la red
|
||||
try:
|
||||
self.net_connection.send_data(message)
|
||||
self.net_connection.send_data(message_bytes)
|
||||
self.com_to_net_count += 1
|
||||
self.update_stats()
|
||||
except Exception as e:
|
||||
|
@ -335,47 +398,42 @@ class NetComTab:
|
|||
# Leer de la red
|
||||
net_data = self.net_connection.read_data_non_blocking()
|
||||
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
|
||||
while '\r' in net_buffer or '\n' in net_buffer or len(net_buffer) >= 10:
|
||||
end_idx = self._find_message_end(net_buffer)
|
||||
if end_idx > 0:
|
||||
message = net_buffer[:end_idx]
|
||||
net_buffer = net_buffer[end_idx:]
|
||||
# Log y parseo (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)
|
||||
|
||||
# Log y parseo
|
||||
display_msg = ProtocolHandler.format_for_display(message)
|
||||
self.log_message(f"Data: {display_msg}", "net_to_com")
|
||||
|
||||
# Intentar parsear si está habilitado
|
||||
if self.show_parsed_var.get():
|
||||
parsed = ProtocolHandler.parse_adam_message(message)
|
||||
if parsed:
|
||||
# Obtener valores de mapeo
|
||||
min_brix = float(self.shared_config['min_brix_map_var'].get())
|
||||
max_brix = float(self.shared_config['max_brix_map_var'].get())
|
||||
brix_value = ProtocolHandler.ma_to_brix(parsed['ma'], min_brix, max_brix)
|
||||
|
||||
self.log_message(
|
||||
f"ADAM - Addr: {parsed['address']}, "
|
||||
f"mA: {parsed['ma']:.3f}, "
|
||||
f"Brix: {brix_value:.3f}, "
|
||||
f"Checksum: {'OK' if parsed.get('checksum_valid', True) else 'ERROR'}",
|
||||
"parsed"
|
||||
)
|
||||
|
||||
# Reenviar al COM
|
||||
try:
|
||||
self.com_connection.send_data(message)
|
||||
self.net_to_com_count += 1
|
||||
self.update_stats()
|
||||
except Exception as e:
|
||||
self.log_message(f"Error enviando a COM: {e}", "error")
|
||||
self.error_count += 1
|
||||
self.update_stats()
|
||||
else:
|
||||
break
|
||||
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:
|
||||
|
@ -386,26 +444,52 @@ class NetComTab:
|
|||
|
||||
# Pequeña pausa para no consumir demasiado CPU
|
||||
if not com_data and not net_data:
|
||||
time.sleep(0.001)
|
||||
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(self, buffer):
|
||||
"""Encuentra el final de un mensaje en el buffer"""
|
||||
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, char in enumerate(buffer):
|
||||
if char in ['\r', '\n']:
|
||||
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
|
||||
if len(buffer) >= 10:
|
||||
if buffer[0] == '#' or (buffer[2:8].replace('.', '').replace(' ', '').replace('-', '').isdigit()):
|
||||
# Parece un mensaje ADAM
|
||||
if len(buffer) > 10 and buffer[10] in ['\r', '\n']:
|
||||
# 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
|
||||
|
@ -413,7 +497,11 @@ class NetComTab:
|
|||
def update_stats(self):
|
||||
"""Actualiza las estadísticas en la GUI"""
|
||||
self.com_to_net_var.set(str(self.com_to_net_count))
|
||||
self.net_to_com_var.set(str(self.net_to_com_count))
|
||||
# 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):
|
||||
|
@ -427,6 +515,13 @@ class NetComTab:
|
|||
"""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:
|
||||
|
@ -444,10 +539,16 @@ class NetComTab:
|
|||
"""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_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))
|
||||
|
|
Loading…
Reference in New Issue