조금은 철 지난 이야기지만 ChatGPT로 프로그래밍이 가능한 세상입니다.
가능하긴 한데 어느 정도 수준까지 가능한지 궁금하기도 하고
사놓고 묵혀두고 있던 m5stack 기기가 있어 무작정 시도해 보기로 했습니다.
준비물 : ChatGPT, m5stack, 개발용PC
m5stack은 esp32 기반의 임베디드 개발 kit? 정도 되려나요?
컨트롤러 unit은 작은 용량의 배터리를 포함하고 독립적으로 프로그램을 돌리거나
다른 모듈과 결합해 원하는 기능을 구현할 수 있습니다.
기본적으로 touch screen과 가속도 센서 등을 포함하고 있고
m5stack 스토어에서 판매하는 다른 모듈을 결합해 기능을 손쉽게 확장할 수도 있습니다.
Arduino나 espressif를 이용해 c++ 프로그램으로 개발도 가능하지만
기본적으로 지원하는 micropython을 이용해 간단한 개발을 해보려고 방향을 잡았습니다.
업무적으로 진동을 측정할 일이 있어서 회사에 있는 진동계를 보던 중에
m5stack 기기가 생각나 이걸로 흉내 내 볼 수 있겠다는 생각이 들어 시도해 보기로 했습니다.
잡스럽게 코딩을 하긴 했지만, 어느 것 하나 똑 부러지게 잘하는 게 없는 수준이라
ChatGPT를 통해 어느 수준까지 가능한지 가늠해 보기도 나쁘지 않다는 생각이 들었네요.
개발 환경이라고 할 것도 없이 USB-C Cable을 통해 PC에 연결하고
http://flow.m5stack.com 에 접속하면 간단히 기기의 key 세팅을 통해 개발을 시작할 수 있습니다.
스크래치와 비슷하게 UIflow라고 하는 블록 기반의 코딩도 지원하지만 손에 잘 안 익는 것 같습니다.
그리고 이번에는 Chat GPT에게 모두 시킬 생각이니 micropython 탭을 선택해 줍니다.
프로그램이라고 부르기도 민망하지만
기본 내장된 가속도 센서를 이용해 진동을 측정하고 측정되는 진동값을 화면의 적당한 위치에 표시하고
로그를 쌓고 csv로 저장할 수 있는 프로그램을 작성하는 게 목표입니다.
여력이 된다면 배터리를 사용하는 기기니까 현재 배터리 수준까지 화면에 표시해 볼 생각으로 시작했습니다.
우선 다짜고짜 물어봤습니다.
(참고로 실제 되는지 먼저 시도해 보고 되는 걸 확인하고 블로그 글에 올리는 거라 처음 받은 코드랑
코드 부분 부분이 다르지만 작동은 한다는 점 이해 부탁 드립니다.)
from m5stack import lcd
import imu
# 가속도 센서 초기화
imu_sensor = imu.IMU()
# 화면 초기화
lcd.clear()
while True:
# 가속도 데이터 읽기
ax, ay, az = imu_sensor.acceleration
# 화면에 데이터 표시
lcd.setCursor(0, 0)
lcd.print("Acceleration Data:\n")
lcd.print("X: {:.2f} m/s^2\n".format(ax))
lcd.print("Y: {:.2f} m/s^2\n".format(ay))
lcd.print("Z: {:.2f} m/s^2\n".format(az))
이 상태에서 돌려보고 작동되는 것을 확인한 다음 추가 기능을 구현하기 위해
Chat GPT 프롬프트에 하나씩 요구 사항을 적기 시작했습니다.
실제 사용했던 프롬프트 텍스트를 참고 삼아 아래에 적어 둡니다.
1. 화면 우측 상단에 배터리 수준을 표시하는 코드를 추가해 줘
2. 화면 하단에 실시간으로 측정되고 있는 진동 수준을 그래프로 표현하는 코드를 추가해 줘
3. 측정된 진동값을 csv 파일로 로깅할 수 있는 코드를 추가해 줘
4. A 버튼을 누르면 로깅을 시작하고 다시 누르면 중지하는 코드를 추가해 줘
5. B 버튼을 누르면 그래프를 리프레쉬할 수 있는 코드를 추가해 줘
6. C 버튼을 누르면 SD 카드의 로그 목록을 보여주는 코드를 추가해 줘
한 번에 다 적어도 잘 알아먹을 겁니다.
위 프롬프트를 통해 최종적으로 코드를 획득하고
색상 등은 제가 간단히 수정해서 프로그램을 완성했습니다.
사용하지 않은 코드도 있지만 그냥 남겨두었습니다.
기기의 특성상 sd 카드 마운트가 잘 됐는지 확인하기 쉽지 않아
확인하는 코드를 넣어두기도 했고
레이블을 넣었다고 의도랑 달라서 주석처리 해 버린 부분도 있습니다.
어쨌든 m5stack에서 의도대로 작동되는 프로그램을 짜서 넣을 수 있었고
혼자 짜려고 시도하는 것보다 훨씬 빠르게 프로그래밍이 가능하다는 것을 알 수 있었습니다.
초짜 프로그래머들 설자리가 빠르게 사라질 것 같다는 생각과 함께
잘 쓰면 코딩 효율이 엄청 좋아질 것 같다는 생각도 드네요.
프로그램 짜서 돌리다가 에러메시지 뜨면 굳이 메시지가 뭔지 볼 필요 없이
ChatGPT에게 물어보면 그냥 해설을 다 해줍니다. 적절한 해결책과 함께 말이죠.
# 필요한 라이브러리를 임포트합니다.
from m5stack import *
from m5ui import *
from uiflow import *
import imu
import time
import os
setScreenColor(lcd.BLACK)
print(os.listdir('/sd'))
imu0 = imu.IMU()
# UI 컴포넌트를 초기화합니다.
label0 = M5TextBox(13, 54, "Text", lcd.FONT_Default, lcd.RED, rotate=0)
label1 = M5TextBox(13, 87, "Text", lcd.FONT_Default, lcd.GREEN, rotate=0)
label2 = M5TextBox(13, 120, "Text", lcd.FONT_Default, lcd.BLUE, rotate=0)
label3 = M5TextBox(13, 10, "Text", lcd.FONT_Default, 0xFFFFFF, rotate=0) # 시각을 표시할 레이블 추가
label4 = M5TextBox(135, 110, "", lcd.FONT_Default, 0xFF0000, rotate=0) # 로깅 상태를 표시할 레이블 추가, 위치와 폰트 크기 조정
label5 = M5TextBox(200, 10, "", lcd.FONT_Default, 0xFFFFFF, rotate=0) # 배터리 상태를 표시할 레이블 추가
label6 = M5TextBox(120, 50, "", lcd.FONT_Default, 0xFFFFFF, rotate=0) # 로그 파일 목록을 표시할 레이블 추가
label7 = M5TextBox(10, 155, "1G", lcd.FONT_Default,lcd.WHITE, rotate=0)
label8 = M5TextBox(10, 185, "0G", lcd.FONT_Default,lcd.WHITE, rotate=0)
label9 = M5TextBox(10, 215, "-1G",lcd.FONT_Default,lcd.WHITE, rotate=0)
accel_data = []
logging = False
log_visible = False # 로그 파일 목록의 상태를 저장하는 변수 추가
file_num = 0
last_save_time = 0 # 마지막 데이터 저장 시간을 추적하기 위한 변수
# 가속도 센서의 X, Y, Z 축 값을 저장할 리스트를 초기화합니다.
x_values, y_values, z_values = [], [], []
def update_graph(x_values, y_values, z_values):
#lcd.rect(0, 120, 320, 160, lcd.WHITE) # 그래프 영역 지우기
for i in range(1, len(x_values)):
lcd.line(30+(i-1), 220-x_values[i-1], 30+i, 220-x_values[i], lcd.RED)
lcd.line(30+(i-1), 220-y_values[i-1], 30+i, 220-y_values[i], lcd.GREEN)
lcd.line(30+(i-1), 220-z_values[i-1], 30+i, 220-z_values[i], lcd.BLUE)
#lcd.rect(0, 180, 320, 160, lcd.BLACK) # 그래프 영역 지우기
# if i % 20 == 0: # 20개의 데이터마다 레이블을 그립니다.
# lcd.text((i-1), 200, str(x_values[i-1]), lcd.RED)
# lcd.text((i-1), 210, str(y_values[i-1]), lcd.GREEN)
# lcd.text((i-1), 220, str(z_values[i-1]), lcd.BLUE)
def save_data_automatically():
global last_save_time
current_time = time.ticks_ms()
if (current_time - last_save_time) >= 3600000: # 5분 = 300초 = 300,000 밀리초
save_data_to_file()
last_save_time = current_time
def buttonA_wasPressed():
global logging, file_num
logging = not logging
if logging:
label4.setText("Logging")
# 로깅을 시작할 때 파일 번호를 증가시키고, 빈 데이터 리스트를 초기화합니다.
file_num += 1
global accel_data
accel_data = []
else:
label4.setText("Paused")
save_data_to_file() # 로깅을 중지할 때 데이터를 파일에 저장합니다.
btnA.wasPressed(buttonA_wasPressed)
def refresh_graph_area():
# 그래프 영역을 지우는 로직, 여기서는 전체 그래프 영역을 검은색으로 채웁니다.
lcd.fillRect(30, 130, 320, 130, lcd.BLACK)
def buttonB_wasPressed():
# 그래프 영역을 새로고침하는 함수를 호출합니다.
refresh_graph_area()
# 필요한 경우, 그래프 데이터 리스트를 초기화할 수도 있습니다.
global x_values, y_values, z_values
x_values, y_values, z_values = [], [], []
btnB.wasPressed(buttonB_wasPressed)
def buttonC_wasPressed():
global log_visible
save_data_to_file()
if not log_visible: # 로그 파일 목록이 보이지 않는 경우
try:
files = os.listdir('/sd') # SD 카드의 파일 목록을 가져옵니다.
log_files = [f for f in files if f.endswith('.csv')] # .txt 파일만 선택합니다.
display_text = '\n'.join(log_files) if log_files else "No CSV files found"
#print(os.listdir('/sd'))
except Exception as e:
display_text = "Error accessing /sd"
label6.setText(display_text) # 화면에 로그 파일 목록을 표시합니다.
log_visible = True # 로그 파일 목록의 상태를 업데이트합니다.
else: # 로그 파일 목록이 보이는 경우
label6.setText("") # 로그 파일 목록을 지웁니다.
log_visible = False # 로그 파일 목록의 상태를 업데이트합니다.
btnC.wasPressed(buttonC_wasPressed)
# 데이터를 파일로 저장하는 함수
def save_data_to_file():
global accel_data, file_num
if accel_data:
# SD 카드가 마운트된 기본 경로 설정
base_path = '/sd'
filename = '{}/accel_data_{}.csv'.format(base_path, file_num)
try:
# 지정된 경로가 존재하는지 확인하고, 없으면 생성
#if not os.path.exists(base_path):
# os.makedirs(base_path)
# 파일에 데이터 쓰기
with open(filename, 'w') as f:
for data in accel_data:
f.write(','.join(map(str, data)) + '\n')
print("Data saved to:", filename)
file_num += 1 # 다음 파일 번호로 업데이트
accel_data = [] # 데이터 리스트 초기화
except Exception as e:
print("Failed to save data:", e)
while True:
x, y, z = imu0.acceleration
label0.setText('X: ' + str(x))
label1.setText('Y: ' + str(y))
label2.setText('Z: ' + str(z))
timestamp = time.ticks_ms() # 현재 시각을 밀리초로 가져옵니다.
t = time.localtime(timestamp // 1000 ) # 밀리초를 초로 변환하고, 시간을 서울 시간으로 변환합니다.
current_time = "{:02d}:{:02d}:{:02d}".format(t[3], t[4], t[5]) # 시간, 분, 초를 24시간 형식으로 변환합니다.
label3.setText('Time: ' + current_time) # 화면에 시각 표시
batVoltage = power.getBatVoltage() # 배터리 전압을 가져옵니다.
batPercentage = (batVoltage - 3.2) * 100 if batVoltage > 3.2 else 0 # 배터리 잔량을 계산합니다[1].
label5.setText('Battery: {:.1f}%'.format(batPercentage)) # 화면에 배터리 상태 표시
accel_data.append([timestamp, x, y, z])
x_values.append(int((x+1)*30)) # 값의 범위를 -1~1에서 0~60으로 변환합니다.
y_values.append(int((y+1)*30)) # 값의 범위를 -1~1에서 0~60으로 변환합니다.
z_values.append(int((z+1)*30)) # 값의 범위를 -1~1에서 0~60으로 변환합니다.
update_graph(x_values, y_values, z_values)
if logging:
save_data_automatically()
#if len(x_values) > 30000: # 예를 들어 배열 크기가 특정 크기를 넘어서면 자동으로 저장
# save_data_to_file()
# print(accel_data)
상용 프로그램 개발 분야에도 활용이 가능할지는 잘 모르겠으나
간단한 코딩에 활용하는 것은 충분히 가능하다는 결론이고
짠 프로그램을 다른 언어로 변환하는 것도 잘 되는 것 같았습니다.
마지막으로 m5stack에서 프로그램이 작동하는 화면을 올리면서 글을 마무리하려고 합니다.