-
파이썬으로 작성한 SERIAL 통신 프로그램PYTHON(파이썬)/파이썬 활용 2024. 10. 31. 07:27728x90반응형
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 sysimport serialimport serial.tools.list_portsfrom 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 readself.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 dataif 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.DontUseNativeDialogfile_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.DontUseNativeDialogfile_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 보내기 (17) 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#) (1) 2024.10.31 - GUI 초기화 (initUI):