Te entiendo doc. Por una necesidad personal, solo eso. Esta hecho en python y es la base por si a alguien le sirve y quiera algo personalizado. Intentaré dejar el código. Saludos
# import serial
import serial.tools.list_ports
import threading
import tkinter as tk
from tkinter import ttk
import sys
import os
ser = None
hilo = None
def listar_puertos():
return [port.device for port in serial.tools.list_ports.comports()]
def actualizar_puertos():
combo_puerto['values'] = listar_puertos()
combo_puerto.set(listar_puertos()[0] if listar_puertos() else "")
def crear_tooltip(widget, texto):
tooltip = tk.Toplevel(widget)
tooltip.withdraw()
tooltip.overrideredirect(True)
tooltip_label = tk.Label(tooltip, text=texto, background="lightyellow", relief="solid", borderwidth=1,
font=("Helvetica", 9))
tooltip_label.pack()
def mostrar_tooltip(event):
x = event.x_root + 10
y = event.y_root + 10
tooltip.geometry(f"+{x}+{y}")
tooltip.deiconify()
def ocultar_tooltip(event):
tooltip.withdraw()
widget.bind("<Enter>", mostrar_tooltip)
widget.bind("<Leave>", ocultar_tooltip)
def conectar():
global ser, hilo
puerto = combo_puerto.get()
# velocidad = "115200"
velocidad = int(combo_velocidad.get())
try:
ser = serial.Serial(puerto, velocidad, timeout=1)
estado.set(f"⚫ Conectado a {puerto}")
# Cambiar color a VERDE
etiqueta_estado.config(fg="green")
hilo = threading.Thread(target=leer_serial, daemon=True)
hilo.start()
except serial.SerialException as e:
estado.set(f"Error: {e}")
# Cambiar color a ROJO para errores
etiqueta_estado.config(fg="red")
def desconectar():
global ser, hilo
if ser and ser.is_open:
try:
ser.close()
except:
pass # Ignorar errores al cerrar
estado.set("⚫ Desconectado")
# Cambiar color a ROJO
etiqueta_estado.config(fg="red")
ser = None
def leer_serial():
while ser and ser.is_open:
try:
if ser.in_waiting:
datos = ser.read(ser.in_waiting).decode(errors='ignore')
if len(datos) > 6:
# Mensaje largo: insertamos en bloque para velocidad
texto.insert(tk.END, datos)
texto.see(tk.END)
texto.update_idletasks()
else:
# Mensaje corto: insertamos carácter por carácter
for i, char in enumerate(datos):
texto.insert(tk.END, char)
if i % 5 == 0:
texto.update_idletasks()
texto.see(tk.END)
time.sleep(0.01) # 💤 pausa mínima de 10ms
except Exception as e:
if "ClearCommError" in str(e) or "OSError(9" in str(e):
break
elif "device has been disconnected" in str(e).lower():
break
else:
print(f"Error en lectura serial: {e}")
break
def enviar(event=None):
global ajuste_linea_var
if ser and ser.is_open:
mensaje = entrada.get()
ajuste = ajuste_linea_var.get()
if ajuste == "Nueva línea":
mensaje += '\n'
elif ajuste == "Retorno de carro":
mensaje += '\r'
elif ajuste == "Ambos NL & CR":
mensaje += '\r\n'
ser.write(mensaje.encode())
entrada.delete(0, tk.END)
def limpiar():
texto.delete('1.0', tk.END)
def crear_menu_contextual(widget):
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="Copiar", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="Pegar", command=lambda: widget.event_generate("<<Paste>>"))
menu.add_command(label="Cortar", command=lambda: widget.event_generate("<<Cut>>"))
widget.bind("<Button-3>", lambda event: menu.tk_popup(event.x_root, event.y_root))
def centrar_ventana(ventana, ancho=820, alto=440):
ventana.update_idletasks()
x = (ventana.winfo_screenwidth() // 2) - (ancho // 2)
y = (ventana.winfo_screenheight() // 2) - (alto // 2)
ventana.geometry(f"{ancho}x{alto}+{x}+{y}")
def iniciar_erebus():
global ventana, texto, combo_puerto, combo_velocidad, entrada, estado
global ajuste_linea_var, etiqueta_estado
global ajuste_linea_var
ventana = tk.Tk()
ventana.option_add("*Font", "Helvetica 12") # Luego se aplica el estilo global
centrar_ventana(ventana, ancho=820, alto=440)
ventana.title("Monitor Serial alternativo by Trucco_AR")
# Carga del ícono
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.dirname(os.path.abspath(__file__))
icon_path = os.path.join(base_path, "inocente.ico")
try:
ventana.iconbitmap(icon_path)
print(f"✅ Ícono cargado desde: {icon_path}")
except Exception as e:
print(f"⚠️ No se pudo cargar el ícono: {e}")
ventana.columnconfigure(0, weight=1)
ventana.rowconfigure(1, weight=1)
# Marco superior
marco_config = tk.Frame(ventana)
marco_config.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
tk.Label(marco_config, text="Puerto:").pack(side=tk.LEFT)
combo_puerto = ttk.Combobox(marco_config, values=listar_puertos(), width=10)
combo_puerto.pack(side=tk.LEFT, padx=5)
combo_puerto.set(listar_puertos()[0] if listar_puertos() else "")
boton_actualizar = tk.Button(marco_config, text="🔄", command=actualizar_puertos)
boton_actualizar.pack(side=tk.LEFT, padx=5)
crear_tooltip(boton_actualizar, "Actualizar lista de puertos")
tk.Label(marco_config, text="Velocidad:").pack(side=tk.LEFT)
combo_velocidad = ttk.Combobox(marco_config, values=["9600", "19200", "38400", "57600", "115200"], width=8)
combo_velocidad.pack(side=tk.LEFT, padx=5)
combo_velocidad.set("115200")
tk.Button(marco_config, text="Conectar", command=conectar).pack(side=tk.LEFT, padx=5)
tk.Button(marco_config, text="Desconectar", command=desconectar).pack(side=tk.LEFT, padx=5)
estado = tk.StringVar()
estado.set("⚫ Desconectado")
etiqueta_estado = tk.Label(marco_config, textvariable=estado, fg="red") # Inicia en rojo
etiqueta_estado.pack(side=tk.LEFT, padx=10)
# Área de texto
texto = tk.Text(ventana)
texto.grid(row=1, column=0, sticky="nsew", padx=5)
crear_menu_contextual(texto)
# Marco inferior
marco_inferior = tk.Frame(ventana)
marco_inferior.grid(row=2, column=0, sticky="ew", padx=5, pady=5)
entrada = tk.Entry(marco_inferior)
entrada.grid(row=0, column=0, sticky="ew")
entrada.bind("<Return>", enviar)
marco_inferior.columnconfigure(0, weight=1)
crear_menu_contextual(entrada)
tk.Button(marco_inferior, text="Enviar", command=enviar).grid(row=0, column=1, padx=5)
tk.Button(marco_inferior, text="Limpiar", command=limpiar).grid(row=0, column=2, padx=5)
# Opciones de ajuste de línea
opciones_ajuste = ["Sin ajuste de línea", "Nueva línea", "Retorno de carro", "Ambos NL & CR"]
ajuste_linea_var = tk.StringVar(value="Sin ajuste de línea")
# Combobox de ajuste de línea
combo_ajuste_linea = ttk.Combobox(marco_inferior, textvariable=ajuste_linea_var, values=opciones_ajuste, state="readonly", width=20)
combo_ajuste_linea.grid(row=0, column=3, padx=5)
ventana.mainloop()
iniciar_erebus()