🔧 How to Use
- Install dependencies:
bashCopyEditpip install pyqt5 pyserial
- Save this script as
serial_monitor_gui.py
and run it.
🐍 Full Python Script
pythonCopyEditimport sys
import serial
import serial.tools.list_ports
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QTextEdit, QLineEdit, QPushButton,
QHBoxLayout, QLabel, QComboBox, QCheckBox
)
from PyQt5.QtCore import QThread, pyqtSignal, QTimer
from datetime import datetime
class SerialReader(QThread):
data_received = pyqtSignal(str)
def __init__(self, serial_port):
super().__init__()
self.ser = serial_port
self.running = True
def run(self):
while self.running and self.ser.is_open:
try:
if self.ser.in_waiting:
line = self.ser.readline().decode('utf-8', errors='ignore').strip()
self.data_received.emit(line)
except Exception as e:
self.data_received.emit(f"[Error] {e}")
break
def stop(self):
self.running = False
if self.ser and self.ser.is_open:
self.ser.close()
class SerialMonitor(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt5 Serial Monitor")
self.resize(700, 500)
self.ser = None
self.serial_thread = None
self.log_file = open("serial_log.txt", "a")
self.init_ui()
self.auto_reconnect_timer = QTimer()
self.auto_reconnect_timer.timeout.connect(self.try_reconnect)
self.auto_reconnect_timer.start(5000)
def init_ui(self):
layout = QVBoxLayout()
# Port and baud rate
port_layout = QHBoxLayout()
self.port_combo = QComboBox()
self.refresh_ports()
self.baud_combo = QComboBox()
self.baud_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
self.baud_combo.setCurrentText("9600")
self.connect_btn = QPushButton("Connect")
self.connect_btn.clicked.connect(self.toggle_connection)
self.auto_scroll_cb = QCheckBox("Auto-scroll")
self.auto_scroll_cb.setChecked(True)
port_layout.addWidget(QLabel("Port:"))
port_layout.addWidget(self.port_combo)
port_layout.addWidget(QLabel("Baud:"))
port_layout.addWidget(self.baud_combo)
port_layout.addWidget(self.connect_btn)
port_layout.addWidget(self.auto_scroll_cb)
layout.addLayout(port_layout)
# Serial output area
self.output_area = QTextEdit()
self.output_area.setReadOnly(True)
layout.addWidget(self.output_area)
# Input field
self.input_field = QLineEdit()
self.input_field.setPlaceholderText("Enter command and press Enter")
self.input_field.returnPressed.connect(self.send_data)
layout.addWidget(self.input_field)
self.setLayout(layout)
def refresh_ports(self):
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
def toggle_connection(self):
if self.ser and self.ser.is_open:
self.disconnect_serial()
else:
self.connect_serial()
def connect_serial(self):
port = self.port_combo.currentText()
baud = int(self.baud_combo.currentText())
try:
self.ser = serial.Serial(port, baud, timeout=1)
self.output_area.append(f"[Connected] to {port} at {baud} baud")
self.serial_thread = SerialReader(self.ser)
self.serial_thread.data_received.connect(self.display_data)
self.serial_thread.start()
self.connect_btn.setText("Disconnect")
except serial.SerialException as e:
self.output_area.append(f"[Error] {e}")
def disconnect_serial(self):
if self.serial_thread:
self.serial_thread.stop()
self.serial_thread.wait()
if self.ser and self.ser.is_open:
self.ser.close()
self.ser = None
self.connect_btn.setText("Connect")
self.output_area.append("[Disconnected]")
def try_reconnect(self):
if not self.ser or not self.ser.is_open:
self.refresh_ports()
self.connect_serial()
def send_data(self):
if self.ser and self.ser.is_open:
data = self.input_field.text()
if data:
self.ser.write((data + "\n").encode('utf-8'))
self.display_data(f"> {data}")
self.input_field.clear()
def display_data(self, line):
timestamped_line = f"[{datetime.now().strftime('%H:%M:%S')}] {line}"
self.output_area.append(timestamped_line)
self.log_file.write(timestamped_line + "\n")
self.log_file.flush()
if self.auto_scroll_cb.isChecked():
self.output_area.moveCursor(self.output_area.textCursor().End)
def closeEvent(self, event):
self.disconnect_serial()
self.log_file.close()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SerialMonitor()
window.show()
sys.exit(app.exec_())