[GPT활용] 챗GPT를 이용해 VScode 시리얼 통신 파이썬 프로그램 작성
0. 서론
안녕하세요, 전자공학 블로거 상민입니다. 정말 오랜만에 글을 작성해 보네요.
이번 글의 주제는 챗GPT를 이용해 VScode 시리얼 통신 파이썬 프로그램 작성 방법 입니다.
이 프로그램을 만드는 이유는 기존 아두이노 IDE에서 시리얼 모니터로 하는 일에 여러 부가적인 기능을 넣어 더욱 다양한 작업을 하기 위함입니다. 부가기능을 예를 들면 그래프 표시나 시리얼 출력값 저장 등의 옵션을 넣고 싶었습니다.
이번 글에서는 기본 뼈대 프로그램을 만드는 것을 목표로 합니다. 다음 글에서는 이 글에서 만든 프로그램에다가 챗GPT를 이용해 부가기능을 넣어 보겠습니다.
해당 내용에 대한 채팅 내역도 공유 드리겠습니다. 이것만 보셔도 많은 도움이 되리라 생각합니다.
https://chatgpt.com/share/66f1612a-1914-8000-8f63-e204ce6acef3
ChatGPT
A conversational AI system that listens, learns, and challenges
chatgpt.com
그러면 바로 본문으로 넘어가겠습니다.
1. 만들 프로그램 내용 정리하기
챗GPT는 던져주는 질문에 따라 나오는 답변 수준이 천차만별입니다. 주어진 정보가 자세하면 자세할수록 코드도 자세해지고 내가 원하는대로 코드가 나옵니다. 그래서 챗GPT에 질문을 하기 전에 내가 만들 프로그램의 아이디어를 글로 자세하게 서술하는게 중요합니다.
1) 내가 프로그램을 구동할 환경 정리
저는 VScode에서 해당 프로그램을 실행할것이고, ESP32보드와 시리얼 데이터를 주고받고자 합니다.
2) 프로그램의 내용을 서술하기
저는 간단한 그림을 그리려보고, 그에 따라서 하나씩 서술하는 방법으로 해봤습니다. 물론 그림을 안그리고 머릿속에서 간략하게 생각하면서 글을 작성하셔도 됩니다.
위 그림을 보면서 하나씩 서술해 보겠습니다.
1. 프로그램을 실행하면 새 창을 띄운다.
2. 새로 나온 창에서 시리얼 통신 포트와 baud rate 등 규격을 정한다.
3. 모든 규격을 설정한 후 연결 버튼을 누르면 시리얼 통신을 시작한다. 연결 상태를 표시한다.
4. 연결끊기 버튼을 눌러 연결을 끊을 수 있다.
5. 문자열을 적을 수 있는 공간이 있고 엔터키를 누르면 ESP32에 시리얼 통신으로 문자열을 보낸다.
6. ESP32에서 받은 문자열을 화면에 띄워준다.
3) 합쳐서 챗GPT에 물어보기
그 다음엔 위 1번과 2번을 합치면 됩니다.
최종 질문 내용
나는 vscode 환경에서 ESP32보드와 시리얼 데이터를 주고받는 프로그램을 만들고자 해. 자세한 내용은 다음과 같아.
1. 프로그램을 실행하면 새 창을 띄운다.
2. 새로 나온 창에서 시리얼 통신 포트와 baud rate 등 규격을 정한다.
3. 모든 규격을 설정한 후 연결 버튼을 누르면 시리얼 통신을 시작한다. 연결 상태를 표시한다.
4. 연결끊기 버튼을 눌러 연결을 끊을 수 있다.
5. 문자열을 적을 수 있는 공간이 있고 엔터키를 누르면 ESP32에 시리얼 통신으로 문자열을 보낸다.
6. ESP32에서 받은 문자열을 화면에 띄워준다.
1. 첫 질문 및 결과
챗GPT에 질문을 하고, 답변을 받았습니다. 답변 중 중간에 코드는 생략했습니다. 자세한 내용은 여기 채팅 내역 확인해 주세요.
채팅 내역 - https://chatgpt.com/share/66f1612a-1914-8000-8f63-e204ce6acef3
아래 첫 답변 코드를 복사해서 VScode 새 파일에다가 붙여넣고, 실행해 봅니다. 아래 더보기를 눌러주세요
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import threading
class SerialApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 Serial Communication")
# 시리얼 설정 변수
self.serial_port = None
self.is_connected = False
# 포트 설정
self.port_label = ttk.Label(root, text="Port:")
self.port_label.grid(row=0, column=0)
self.port_entry = ttk.Entry(root)
self.port_entry.grid(row=0, column=1)
self.baud_label = ttk.Label(root, text="Baud Rate:")
self.baud_label.grid(row=1, column=0)
self.baud_entry = ttk.Entry(root)
self.baud_entry.grid(row=1, column=1)
# 연결/해제 버튼
self.connect_button = ttk.Button(root, text="Connect", command=self.connect_serial)
self.connect_button.grid(row=2, column=0)
self.disconnect_button = ttk.Button(root, text="Disconnect", command=self.disconnect_serial)
self.disconnect_button.grid(row=2, column=1)
# 통신 상태
self.status_label = ttk.Label(root, text="Status: Disconnected")
self.status_label.grid(row=3, column=0, columnspan=2)
# 수신 메시지
self.recv_label = ttk.Label(root, text="Received:")
self.recv_label.grid(row=4, column=0)
self.recv_text = tk.Text(root, height=10, width=40)
self.recv_text.grid(row=5, column=0, columnspan=2)
# 전송 메시지
self.send_label = ttk.Label(root, text="Send:")
self.send_label.grid(row=6, column=0)
self.send_entry = ttk.Entry(root)
self.send_entry.grid(row=6, column=1)
self.send_button = ttk.Button(root, text="Send", command=self.send_message)
self.send_button.grid(row=7, column=0, columnspan=2)
def connect_serial(self):
port = self.port_entry.get()
baudrate = self.baud_entry.get()
try:
self.serial_port = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.status_label.config(text="Status: Connected")
threading.Thread(target=self.read_serial, daemon=True).start()
except Exception as e:
messagebox.showerror("Error", f"Failed to connect: {e}")
def disconnect_serial(self):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.is_connected = False
self.status_label.config(text="Status: Disconnected")
def send_message(self):
if self.is_connected:
message = self.send_entry.get()
self.serial_port.write(message.encode())
else:
messagebox.showwarning("Warning", "Not connected to a serial port")
def read_serial(self):
while self.is_connected:
if self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode('utf-8').strip()
self.recv_text.insert(tk.END, f"ESP32: {data}\n")
self.recv_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = SerialApp(root)
root.mainloop()
여기서 오류가 나오는 분들이 있을 것입니다. 대부분 필수패키지를 설치하지 않아서 그럴 건데요, 이것도 챗GPT에 물어보면 됩니다!
아주 자세히 알려주네요! 오류가 발생하신 분들은 이 내용을 따라해 주세요.
다시 프로그램을 실행해 줍니다.
프로그램 실행 결과, 새로운 창이 뜨면서 통신할 준비가 되었습니다. 제가 생각했던 것과 배치는 약간 다르고, 제가 자세하게 서술하지 못해서 입력 방식이라던지 아쉬운 부분이 많습니다. 그래도 첫 답변 치고는 만족할만한 결과가 나왔습니다. 다음 챕터에서 옵션들을 수정해 보도록 하고, 코드가 잘 작동하는 지 확인해 보겠습니다.
참고로 ESP32에 넣은 코드는 아래에 적어두었습니다. 단순히 시리얼 테스트하는 코드입니다. 아래 더보기를 눌러주세요
// Serial example
void setup() {
Serial.begin(115200);
Serial.println("Setup done\n\n");
}
void loop() {
if(Serial.available() > 0)
{
char sread = Serial.read();
if(sread == '1')
{
Serial.println("1 received.\n");
}
else if(sread == '2')
{
Serial.println("2 received.\n");
}
else if(sread != '\n')
{
Serial.print("Unexpected letter ");
Serial.print(sread);
Serial.println(" received.\n");
}
}
}
ESP32가 연결된 포트 번호를 port 오른쪽 빈칸에 넣고, Baud rate 오른쪽 빈칸에 설정한 baud rate를 입력합니다
(아주 번거롭네요)
그리고 Connect 버튼을 입력하면 Status가 Connected로 바뀝니다.
바로 시리얼 값이 날라오네요!
그리고 입력란에 1을 입력하고 Send 버튼을 누르면 ESP32에 문자를 보냅니다. 그리고 수신도 잘 하네요.
2. 옵션 수정
첫 질문에서 아쉬웠던 부분이 아주 많았는데요, 이를 챗GPT에 피드백해서 제가 원하는대로 수정해 보겠습니다.
일단, 창 크기부터 키워봅시다.
바로바로 잘 수정해 줍니다. 코드를 전부 복사해서 붙여넣고 실행하면 다음과 같습니다.
3번째 답변 코드입니다. 아래 더보기 눌러주세요
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import threading
class SerialApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 Serial Communication")
# 창 크기 설정 (가로 800, 세로 600으로 2배 키움)
self.root.geometry("800x600")
# 시리얼 설정 변수
self.serial_port = None
self.is_connected = False
# 포트 설정
self.port_label = ttk.Label(root, text="Port:")
self.port_label.grid(row=0, column=0)
self.port_entry = ttk.Entry(root)
self.port_entry.grid(row=0, column=1)
self.baud_label = ttk.Label(root, text="Baud Rate:")
self.baud_label.grid(row=1, column=0)
self.baud_entry = ttk.Entry(root)
self.baud_entry.grid(row=1, column=1)
# 연결/해제 버튼
self.connect_button = ttk.Button(root, text="Connect", command=self.connect_serial)
self.connect_button.grid(row=2, column=0)
self.disconnect_button = ttk.Button(root, text="Disconnect", command=self.disconnect_serial)
self.disconnect_button.grid(row=2, column=1)
# 통신 상태
self.status_label = ttk.Label(root, text="Status: Disconnected")
self.status_label.grid(row=3, column=0, columnspan=2)
# 수신 메시지
self.recv_label = ttk.Label(root, text="Received:")
self.recv_label.grid(row=4, column=0)
self.recv_text = tk.Text(root, height=20, width=80) # 텍스트 박스 크기 조정
self.recv_text.grid(row=5, column=0, columnspan=2)
# 전송 메시지
self.send_label = ttk.Label(root, text="Send:")
self.send_label.grid(row=6, column=0)
self.send_entry = ttk.Entry(root, width=60) # 엔트리 크기 조정
self.send_entry.grid(row=6, column=1)
self.send_button = ttk.Button(root, text="Send", command=self.send_message)
self.send_button.grid(row=7, column=0, columnspan=2)
def connect_serial(self):
port = self.port_entry.get()
baudrate = self.baud_entry.get()
try:
self.serial_port = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.status_label.config(text="Status: Connected")
threading.Thread(target=self.read_serial, daemon=True).start()
except Exception as e:
messagebox.showerror("Error", f"Failed to connect: {e}")
def disconnect_serial(self):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.is_connected = False
self.status_label.config(text="Status: Disconnected")
def send_message(self):
if self.is_connected:
message = self.send_entry.get()
self.serial_port.write(message.encode())
else:
messagebox.showwarning("Warning", "Not connected to a serial port")
def read_serial(self):
while self.is_connected:
if self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode('utf-8').strip()
self.recv_text.insert(tk.END, f"ESP32: {data}\n")
self.recv_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = SerialApp(root)
root.mainloop()
조금 애매하긴 한데, 나머지 다른 옵션도 수정해 보겠습니다.
먼저 포트설정에서 내가 현재 연결 가능한 포트를 드롭박스 리스트로 열어주고, 그 중 하나를 선택하도록 바꿔 보겠습니다.
4번째 답변 코드입니다. 아래 더보기 눌러주세요
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import threading
import serial.tools.list_ports
class SerialApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 Serial Communication")
# 창 크기 설정 (가로 800, 세로 600)
self.root.geometry("800x600")
# 시리얼 설정 변수
self.serial_port = None
self.is_connected = False
# 포트 리스트 가져오기
self.available_ports = self.get_serial_ports()
# 포트 설정 (드롭다운 메뉴)
self.port_label = ttk.Label(root, text="Port:")
self.port_label.grid(row=0, column=0)
self.port_combobox = ttk.Combobox(root, values=self.available_ports)
self.port_combobox.grid(row=0, column=1)
self.baud_label = ttk.Label(root, text="Baud Rate:")
self.baud_label.grid(row=1, column=0)
self.baud_entry = ttk.Entry(root)
self.baud_entry.grid(row=1, column=1)
# 연결/해제 버튼
self.connect_button = ttk.Button(root, text="Connect", command=self.connect_serial)
self.connect_button.grid(row=2, column=0)
self.disconnect_button = ttk.Button(root, text="Disconnect", command=self.disconnect_serial)
self.disconnect_button.grid(row=2, column=1)
# 통신 상태
self.status_label = ttk.Label(root, text="Status: Disconnected")
self.status_label.grid(row=3, column=0, columnspan=2)
# 수신 메시지
self.recv_label = ttk.Label(root, text="Received:")
self.recv_label.grid(row=4, column=0)
self.recv_text = tk.Text(root, height=20, width=80)
self.recv_text.grid(row=5, column=0, columnspan=2)
# 전송 메시지
self.send_label = ttk.Label(root, text="Send:")
self.send_label.grid(row=6, column=0)
self.send_entry = ttk.Entry(root, width=60)
self.send_entry.grid(row=6, column=1)
self.send_button = ttk.Button(root, text="Send", command=self.send_message)
self.send_button.grid(row=7, column=0, columnspan=2)
def get_serial_ports(self):
"""현재 사용 가능한 시리얼 포트를 반환하는 함수"""
ports = serial.tools.list_ports.comports()
return [port.device for port in ports]
def connect_serial(self):
port = self.port_combobox.get() # 사용자가 선택한 포트를 가져옴
baudrate = self.baud_entry.get()
if not port:
messagebox.showwarning("Warning", "Please select a serial port")
return
try:
self.serial_port = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.status_label.config(text="Status: Connected")
threading.Thread(target=self.read_serial, daemon=True).start()
except Exception as e:
messagebox.showerror("Error", f"Failed to connect: {e}")
def disconnect_serial(self):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.is_connected = False
self.status_label.config(text="Status: Disconnected")
def send_message(self):
if self.is_connected:
message = self.send_entry.get()
self.serial_port.write(message.encode())
else:
messagebox.showwarning("Warning", "Not connected to a serial port")
def read_serial(self):
while self.is_connected:
if self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode('utf-8').strip()
self.recv_text.insert(tk.END, f"ESP32: {data}\n")
self.recv_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = SerialApp(root)
root.mainloop()
실행 결과, 제가 생각한 그대로 잘 적용된 모습입니다.
이번엔 여러 수정사항을 한번에 보내보도록 하겠습니다. 질문 내용은 사진을 확인해 주세요.
5번째 답변 코드입니다. 아래 더보기 눌러주세요
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import threading
import serial.tools.list_ports
class SerialApp:
def __init__(self, root):
self.root = root
self.root.title("ESP32 Serial Communication")
# 창 크기 설정 (가로 800, 세로 600)
self.root.geometry("800x600")
# 시리얼 설정 변수
self.serial_port = None
self.is_connected = False
# 포트 리스트 가져오기
self.available_ports = self.get_serial_ports()
# 포트 설정 (드롭다운 메뉴)
self.port_label = ttk.Label(root, text="Port:")
self.port_label.grid(row=0, column=0)
self.port_combobox = ttk.Combobox(root, values=self.available_ports)
self.port_combobox.grid(row=0, column=1)
# Baud rate 드롭박스
self.baud_label = ttk.Label(root, text="Baud Rate:")
self.baud_label.grid(row=1, column=0)
self.baud_combobox = ttk.Combobox(root, values=["9600", "19200", "38400", "57600", "115200"])
self.baud_combobox.grid(row=1, column=1)
self.baud_combobox.current(0) # 기본값으로 9600 선택
# 연결/해제 버튼
self.connect_button = ttk.Button(root, text="Connect", command=self.connect_serial)
self.connect_button.grid(row=2, column=0)
self.disconnect_button = ttk.Button(root, text="Disconnect", command=self.disconnect_serial)
self.disconnect_button.grid(row=2, column=1)
# 통신 상태
self.status_label = ttk.Label(root, text="Status: Disconnected")
self.status_label.grid(row=3, column=0, columnspan=2)
# 수신 메시지
self.recv_label = ttk.Label(root, text="Received:")
self.recv_label.grid(row=4, column=0)
self.recv_text = tk.Text(root, height=20, width=80)
self.recv_text.grid(row=5, column=0, columnspan=2)
# 전송 메시지
self.send_label = ttk.Label(root, text="Send:")
self.send_label.grid(row=6, column=0)
self.send_entry = ttk.Entry(root, width=60)
self.send_entry.grid(row=6, column=1)
# 엔터키로 송신할 수 있도록 이벤트 바인딩
self.send_entry.bind("<Return>", self.enter_send)
self.send_button = ttk.Button(root, text="Send", command=self.send_message)
self.send_button.grid(row=7, column=0, columnspan=2)
def get_serial_ports(self):
"""현재 사용 가능한 시리얼 포트를 반환하는 함수"""
ports = serial.tools.list_ports.comports()
return [port.device for port in ports]
def connect_serial(self):
port = self.port_combobox.get() # 사용자가 선택한 포트를 가져옴
baudrate = self.baud_combobox.get() # 선택된 baud rate 가져옴
if not port:
messagebox.showwarning("Warning", "Please select a serial port")
return
try:
self.serial_port = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.status_label.config(text="Status: Connected")
threading.Thread(target=self.read_serial, daemon=True).start()
except Exception as e:
messagebox.showerror("Error", f"Failed to connect: {e}")
def disconnect_serial(self):
if self.serial_port and self.serial_port.is_open:
self.serial_port.close()
self.is_connected = False
self.status_label.config(text="Status: Disconnected")
def send_message(self):
if self.is_connected:
message = self.send_entry.get()
self.serial_port.write(message.encode())
self.send_entry.delete(0, tk.END) # 송신 후 입력창 비우기
else:
messagebox.showwarning("Warning", "Not connected to a serial port")
def enter_send(self, event):
"""엔터키를 눌렀을 때 메시지를 전송하는 함수"""
self.send_message()
def read_serial(self):
while self.is_connected:
if self.serial_port.in_waiting > 0:
data = self.serial_port.readline().decode('utf-8').strip()
self.recv_text.insert(tk.END, f"{data}\n") # 수신 내용만 표시
self.recv_text.see(tk.END)
if __name__ == "__main__":
root = tk.Tk()
app = SerialApp(root)
root.mainloop()
수정한 옵션이 모두 적용된 모습을 볼 수 있습니다.
그리고 크게 중요하지 않은 코드를 수정하고 코드를 생성하지 않는것도 가능합니다.
이렇게도 가능하니, 작성시간이 많이 걸리는게 불편할 때 이용해 보면 좋을 것 같습니다.
3. 결론
챗GPT에 제가 생각하는 내용을 자세히 적어주면 프로그램이 나오는 모습을 볼 수 있습니다. 하지만 여러 부분에서 아쉬운게 많았는데요, 그런 부분들도 자세히 적어서 수정해 달라고 하면 잘 수정해 주는 것도 볼 수 있었습니다.
이 예제 뿐만 아니라 다른 프로그램을 만들 때에도 이 예제와 비슷하게 챗GPT에 물어보고 수정하는 과정을 거친다면 여러분들도 얼마든지 쉽게 프로그램을 만들 수 있습니다.
다음 글에서는 이를 더욱 발전시켜서 어려운 부가기능을 추가해 보겠습니다.
감사합니다.