Monitor serial alternativo

Hola! Me llamo Daniel. Por una necesidad personal que no viene al caso, necesitaba un monitor serial establecido en una velocidad y sin ajuste de línea, pero como alguna vez yo he buscado algo alternativo, decidí hacerlo lo mas funcional al integrado con el IDE y luego quitar las partes que no necesito. Pensé que el código completo le podría servir a alguien y aquí lo estoy consultado.

Ustedes dirán si se acepta y como debería subirlo.

Esta es una captura de pantalla del monitor y puedo subir el código Python y/o el ejecutable .exe. Saludos

1 Like

¿Una alternativa al monitor serie del IDE? Hay docenas, empezando por el simple "Putty" o incluso "MobaXterm". Simplemente no abras el puerto serie del IDE y usa el externo. Recuerda desconectarlo del puerto COM cuando necesites cargar código.
Si creaste este monitor serie, está bien, por supuesto, pero no estoy seguro de si lo integraste en el IDE en lugar del existente (¿cómo lo hiciste?), o si preguntas cómo hacerlo (no sé la respuesta).

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()
1 Like

Para las personas que no tienen python instalado o sepan compilarlo dejo un link de [Descarga.] No necesita instalación y se puede ejecutar desde una unidad USB.

Si no corresponde postear esto, lo pueden borrar, sin mas. Saludos