RS-232 시리얼 통신을 위한 GUI 애플리케이션을 PySide6와 Python을 사용하여 구현한 것입니다. 주요 기능과 구조는 다음과 같습니다.
주요 구성 요소
- GUI 초기화 (initUI):
- QGroupBox, QComboBox, QPushButton, QLineEdit 등을 사용하여 UI를 구성합니다.
- 시리얼 포트 설정 및 연결, 수신 데이터 확인, 데이터 전송 등의 인터페이스를 제공합니다.
- 스타일 시트(setStyleSheet)를 사용하여 전체적인 UI의 색상, 폰트, 버튼 등의 디자인을 설정합니다.
- 시리얼 포트 설정:
- 사용 가능한 시리얼 포트를 자동으로 검색하고, 사용자가 포트, 보드레이트, 데이터 비트, 패리티, 정지 비트 등의 설정을 선택할 수 있도록 합니다.
- 연결 및 해제 버튼을 통해 시리얼 포트를 제어합니다.
- 시리얼 포트 연결 및 데이터 송수신:
- connect_serial(): 사용자가 선택한 시리얼 포트와 설정 값으로 포트를 연결합니다.
- disconnect_serial(): 연결된 포트를 해제합니다.
- read_serial_data(): 주기적으로 데이터를 비동기적으로 읽어 들여 화면에 표시합니다. QTimer를 사용하여 일정 시간 간격으로 데이터를 확인합니다.
- send_data_with_control(): 전송할 데이터를 입력받고, 선택된 제어 문자(STX, ETX 등)를 추가하여 전송합니다.
- 수신 데이터 표시 및 관리:
- 수신된 데이터는 ASCII와 HEX 형식으로 표시됩니다.
- QTextEdit를 사용해 수신 데이터를 표시하며, 사용자는 Clear, Save, Load 버튼을 통해 데이터를 관리할 수 있습니다.
- 데이터를 텍스트 파일로 저장하거나 불러올 수 있는 기능도 제공됩니다.
- 제어 문자 선택 및 데이터 전송:
- 사용자가 전송할 데이터를 입력하고, 그 앞과 뒤에 추가할 제어 문자를 선택할 수 있도록 인터페이스를 구성하였습니다.
- 제어 문자로 STX, ETX, ACK, NAK 등 여러 가지가 있으며, 사용자 선택에 따라 전송 메시지에 포함됩니다.
import sys
import serial
import serial.tools.list_ports
from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QComboBox, QMessageBox, QFormLayout, QGroupBox, QHBoxLayout, QFileDialog
)
from PySide6.QtCore import QTimer
class SerialApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.serial_port = None
def initUI(self):
# 스타일 시트 적용
self.setStyleSheet("""
QWidget {
background-color: #f0f0f0;
font-family: Arial, sans-serif;
font-size: 14px;
}
QGroupBox {
background-color: #ffffff;
border: 2px solid #c0c0c0;
border-radius: 5px;
margin-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 0 3px;
background-color: #e0e0e0;
color: #333333;
font-weight: bold;
}
QLabel {
color: #333333;
}
QComboBox, QLineEdit {
border: 1px solid #c0c0c0;
border-radius: 3px;
padding: 2px 4px;
}
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
padding: 5px 10px;
}
QPushButton:hover {
background-color: #45a049;
}
QTextEdit {
border: 1px solid #c0c0c0;
border-radius: 5px;
padding: 5px;
background-color: #ffffff;
color: #333333;
}
QPushButton#clearButton {
background-color: #f44336;
}
QPushButton#clearButton:hover {
background-color: #d32f2f;
}
QPushButton#saveButton, QPushButton#loadButton {
background-color: #2196F3;
}
QPushButton#saveButton:hover, QPushButton#loadButton:hover {
background-color: #1976D2;
}
""")
main_layout = QVBoxLayout()
# 시리얼 설정 그룹박스
serial_groupbox = QGroupBox("Serial Port Settings")
form_layout = QFormLayout()
# 포트 선택
self.port_combobox = QComboBox()
self.update_ports()
form_layout.addRow("Select Port:", self.port_combobox)
# Baudrate 설정
self.baudrate_combobox = QComboBox()
self.baudrate_combobox.addItems(["9600", "19200", "38400", "57600", "115200"])
form_layout.addRow("Select Baudrate:", self.baudrate_combobox)
# Data Bits 설정
self.databits_combobox = QComboBox()
self.databits_combobox.addItems(["5", "6", "7", "8"])
self.databits_combobox.setCurrentText("8")
form_layout.addRow("Select Data Bits:", self.databits_combobox)
# Parity 설정
self.parity_combobox = QComboBox()
self.parity_combobox.addItems(["None", "Even", "Odd", "Mark", "Space"])
self.parity_combobox.setCurrentText("None")
form_layout.addRow("Select Parity:", self.parity_combobox)
# Stop Bits 설정
self.stopbits_combobox = QComboBox()
self.stopbits_combobox.addItems(["1", "1.5", "2"])
self.stopbits_combobox.setCurrentText("1")
form_layout.addRow("Select Stop Bits:", self.stopbits_combobox)
# 연결 및 연결 해제 버튼 레이아웃
button_layout = QHBoxLayout()
self.connect_button = QPushButton("Connect")
self.connect_button.clicked.connect(self.connect_serial)
button_layout.addWidget(self.connect_button)
self.disconnect_button = QPushButton("Disconnect")
self.disconnect_button.clicked.connect(self.disconnect_serial)
button_layout.addWidget(self.disconnect_button)
self.disconnect_button.setEnabled(False) # 초기에는 비활성화
form_layout.addRow(button_layout)
# 포트 상태 표시 라벨
self.status_label = QLabel("Port Status: Disconnected")
form_layout.addRow("Status:", self.status_label)
serial_groupbox.setLayout(form_layout)
main_layout.addWidget(serial_groupbox)
# 수신 데이터 표시
self.data_label = QLabel("Received Data:")
main_layout.addWidget(self.data_label)
self.received_data_text = QTextEdit()
self.received_data_text.setReadOnly(True)
self.received_data_text.setMinimumHeight(200) # 수신 데이터 창을 더 크게 설정
main_layout.addWidget(self.received_data_text)
# 데이터 창 지우기, 저장, 불러오기 버튼 레이아웃
data_control_layout = QHBoxLayout()
clear_button = QPushButton("Clear")
clear_button.setObjectName("clearButton")
clear_button.clicked.connect(self.clear_received_data)
data_control_layout.addWidget(clear_button)
save_button = QPushButton("Save")
save_button.setObjectName("saveButton")
save_button.clicked.connect(self.save_received_data)
data_control_layout.addWidget(save_button)
load_button = QPushButton("Load")
load_button.setObjectName("loadButton")
load_button.clicked.connect(self.load_data_from_file)
data_control_layout.addWidget(load_button)
main_layout.addLayout(data_control_layout)
# 전송 데이터 입력
self.input_label = QLabel("Send Data:")
main_layout.addWidget(self.input_label)
self.input_line_edit = QLineEdit()
main_layout.addWidget(self.input_line_edit)
# 제어문자 선택 레이아웃 (앞과 뒤)
control_layout = QHBoxLayout()
self.control_combobox_start = QComboBox()
self.control_combobox_start.addItems(["None", "STX", "ETX", "ACK", "NAK", "EOT"])
control_layout.addWidget(QLabel("Start Control:"))
control_layout.addWidget(self.control_combobox_start)
self.control_combobox_end = QComboBox()
self.control_combobox_end.addItems(["None", "STX", "ETX", "ACK", "NAK", "EOT"])
control_layout.addWidget(QLabel("End Control:"))
control_layout.addWidget(self.control_combobox_end)
main_layout.addLayout(control_layout)
# 전송 버튼
self.send_button = QPushButton("Send")
self.send_button.clicked.connect(self.send_data_with_control)
self.send_button.setEnabled(False) # 초기에는 비활성화
main_layout.addWidget(self.send_button)
self.setLayout(main_layout)
self.setWindowTitle('RS-232 Serial Communication')
self.setGeometry(300, 300, 500, 600)
def update_ports(self):
ports = serial.tools.list_ports.comports()
self.port_combobox.clear()
for port in ports:
self.port_combobox.addItem(port.device)
def connect_serial(self):
port = self.port_combobox.currentText()
baudrate = int(self.baudrate_combobox.currentText())
databits = int(self.databits_combobox.currentText())
parity_dict = {
"None": serial.PARITY_NONE,
"Even": serial.PARITY_EVEN,
"Odd": serial.PARITY_ODD,
"Mark": serial.PARITY_MARK,
"Space": serial.PARITY_SPACE,
}
parity = parity_dict[self.parity_combobox.currentText()]
stopbits_dict = {
"1": serial.STOPBITS_ONE,
"1.5": serial.STOPBITS_ONE_POINT_FIVE,
"2": serial.STOPBITS_TWO,
}
stopbits = stopbits_dict[self.stopbits_combobox.currentText()]
try:
self.serial_port = serial.Serial(
port=port,
baudrate=baudrate,
bytesize=databits,
parity=parity,
stopbits=stopbits,
timeout=1
)
self.serial_port.flush()
self.status_label.setText(f"Port Status: Connected to {port} at {baudrate} baud.")
self.connect_button.setEnabled(False)
self.disconnect_button.setEnabled(True)
self.send_button.setEnabled(True)
QMessageBox.information(self, "Success", f"Connected to {port} at {baudrate} baud.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to connect: {str(e)}")
self.serial_port.timeout = 0.1 # Short timeout for non-blocking read
self.read_serial_data()
def disconnect_serial(self):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.status_label.setText("Port Status: Disconnected")
self.connect_button.setEnabled(True)
self.disconnect_button.setEnabled(False)
self.send_button.setEnabled(False)
QMessageBox.information(self, "Disconnected", "Serial port disconnected successfully.")
def read_serial_data(self):
if self.serial_port and self.serial_port.is_open:
try:
data = self.serial_port.read(1024)
if data:
ascii_data = data.decode(errors='ignore')
hex_data = ' '.join(f'{x:02x}' for x in data)
self.received_data_text.append(f"ASCII: {ascii_data}\nHEX: {hex_data}\n")
except Exception as e:
QMessageBox.critical(self, "Error", f"Error reading from serial port: {str(e)}")
# Keep checking for data
if self.serial_port and self.serial_port.is_open:
QTimer.singleShot(100, self.read_serial_data)
def send_data_with_control(self):
if self.serial_port and self.serial_port.is_open:
data = self.input_line_edit.text()
control_char_dict = {
"None": b'',
"STX": b'\x02',
"ETX": b'\x03',
"ACK": b'\x06',
"NAK": b'\x15',
"EOT": b'\x04',
}
start_control = control_char_dict[self.control_combobox_start.currentText()]
end_control = control_char_dict[self.control_combobox_end.currentText()]
full_message = start_control + data.encode() + end_control
try:
self.serial_port.write(full_message)
self.received_data_text.append(f"Sent: {self.control_combobox_start.currentText()} {data} {self.control_combobox_end.currentText()}\n")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to send data: {str(e)}")
else:
QMessageBox.warning(self, "Warning", "Not connected to any serial port.")
def clear_received_data(self):
self.received_data_text.clear()
def save_received_data(self):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
file_path, _ = QFileDialog.getSaveFileName(self, "Save Received Data", "", "Text Files (*.txt);;All Files (*)", options=options)
if file_path:
if not file_path.endswith('.txt'):
file_path += '.txt' # .txt 확장자를 자동으로 추가
try:
with open(file_path, 'w') as file:
file.write(self.received_data_text.toPlainText())
QMessageBox.information(self, "Saved", f"Data successfully saved to {file_path}.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to save data: {str(e)}")
def load_data_from_file(self):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
file_path, _ = QFileDialog.getOpenFileName(self, "Load Data", "", "Text Files (*.txt);;All Files (*)", options=options)
if file_path:
try:
with open(file_path, 'r') as file:
data = file.read()
self.received_data_text.setPlainText(data)
QMessageBox.information(self, "Loaded", f"Data successfully loaded from {file_path}.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to load data: {str(e)}")
def closeEvent(self, event):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SerialApp()
ex.show()
sys.exit(app.exec())
이 프로그램은 직관적인 GUI와 PySide6를 사용하여 시리얼 통신을 쉽게 제어할 수 있도록 설계되었습니다. 여러 포트를 관리하고 데이터를 송수신하며, 이를 텍스트 파일로 저장하거나 불러오는 기능도 있어 사용성 측면에서 좋습니다. 몇 가지 잠재적인 성능 문제와 리소스 관리 측면에서의 개선이 가능하며, 사용자 입력 검증과 더 세밀한 오류 처리가 필요할 수 있습니다.

실행 화면으로 PORT 및 PROTOCOL 선택하고 Connect 버튼을 누르고 Send Data에 입력하고 Send를
누르면 PORT로 데이터가 전송되고 데이터가 수신되면 Received Data에 표시된다.
728x90
반응형
'PYTHON(파이썬) > 파이썬 활용' 카테고리의 다른 글
| 첨부파일을 포함한 e-mail 보내기 (18) | 2024.11.14 |
|---|---|
| python으로 간단하게 e-mail 보내기 (2) | 2024.11.13 |
| 네이버에서 PYTHON으로 삼성전자 주가 가져오기 (5) | 2024.11.01 |
| MCPROTOCOL을 이용한 미쓰비시 PLC 통신 (2) | 2024.11.01 |
| PC에 연결된 SERIAL PORT 찾기 (파이썬 그리고 C#) (2) | 2024.10.31 |