목차
파이썬으로 그래프 출력 - 커서 값 출력하기#1
이전 글에서 아두이노(Arduino)를 사용하여 온도와 습도 데이터를 랜덤으로 생성하고, 이를 시리얼 통신을 통해 컴퓨터로 전송하였습니다. 컴퓨터는 받은 데이터를 파이썬(Python)을 이용해 그래프로 나타냈으며, 이번 프로젝트에서는 그래프 위에 마우스를 올렸을 때 해당 위치의 온도와 습도 값을 표시하는 이벤트 처리 코드를 작성하는 것입니다.
이를 구현하기 위한 방법을 자세히 설명하겠습니다. 먼저, 아두이노는 온도와 습도 센서를 통해 데이터를 수집합니다. 이 데이터는 아두이노의 시리얼 포트를 통해 컴퓨터로 전송됩니다. 파이썬에서는 pyserial과 같은 라이브러리를 사용하여 이 시리얼 데이터를 읽을 수 있습니다.
데이터를 읽은 후, 파이썬에서는 matplotlib 같은 시각화 라이브러리를 이용해 데이터를 그래프로 표현합니다. matplotlib에서는 figure.canvas.mpl_connect 메소드를 사용하여 마우스 이벤트를 처리할 수 있습니다. 예를 들어, motion_notify_event를 사용하면 마우스 포인터가 그래프 위를 움직일 때마다 이벤트가 발생하고, 이를 통해 해당 위치의 데이터 값을 얻을 수 있습니다.
아두이노 코드>>
void setup() {
// 시리얼 통신을 시작합니다. 보드에 맞는 속도로 설정하세요.
Serial.begin(9600);
}
void loop() {
// 가상의 온도와 습도 값을 생성합니다.
// 예를 들어, 온도는 20~30도 사이, 습도는 40~60% 사이의 값으로 설정할 수 있습니다.
float temperature = 20 + random(100) / 10.0; // 20.0 ~ 29.9 사이의 값
float humidity = 40 + random(200) / 10.0; // 40.0 ~ 59.9 사이의 값
// 시리얼 통신을 통해 온도와 습도 값을 전송합니다.
// Serial.print("온도: ");
// Serial.print(temperature);
// Serial.print(" °C, 습도: ");
// Serial.print(humidity);
// Serial.println(" %");
Serial.println(String(temperature) + "," + String(humidity));
// 0.5초 동안 대기합니다.
delay(500);
}
파이썬으로 그래프 출력 - 커서 값 출력하기#2
파이썬 전체 코드>>
import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import serial
import threading
# 시리얼 포트 설정
ser = serial.Serial('COM6', 9600)
# 그래프 창 및 스레드 관리를 위한 변수
graph_window = None
thread = None
continue_reading = False
max_data_points = 60
def start_graph():
global graph_window, thread, continue_reading
if graph_window is not None:
return
graph_window = tk.Toplevel()
graph_window.title("Arduino Temperature and Humidity Graph")
# 그래프 설정
fig, (ax1, ax2) = plt.subplots(2, 1)
temp_line, = ax1.plot([], [], label='Temperature', color='blue')
humi_line, = ax2.plot([], [], label='Humidity', color='orange')
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')
# 어노테이션 초기화
annot1 = ax1.annotate("", xy=(0,0), xytext=(-20,20), textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
annot1.set_visible(False)
annot2 = ax2.annotate("", xy=(0,0), xytext=(-20,20), textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
annot2.set_visible(False)
canvas = FigureCanvasTkAgg(fig, master=graph_window)
widget = canvas.get_tk_widget()
widget.pack(fill=tk.BOTH, expand=True)
def update_annot(annot, line, ind):
x, y = line.get_xdata()[ind], line.get_ydata()[ind]
annot.xy = (x, y)
annot.set_text(f"x={x:.2f}, y={y:.2f}")
annot.get_bbox_patch().set_alpha(0.4)
def on_plot_hover(event):
vis = annot1.get_visible()
if event.inaxes == ax1:
cont, ind = temp_line.contains(event)
if cont:
update_annot(annot1, temp_line, ind["ind"][0])
annot1.set_visible(True)
fig.canvas.draw_idle()
elif vis:
annot1.set_visible(False)
fig.canvas.draw_idle()
vis = annot2.get_visible()
if event.inaxes == ax2:
cont, ind = humi_line.contains(event)
if cont:
update_annot(annot2, humi_line, ind["ind"][0])
annot2.set_visible(True)
fig.canvas.draw_idle()
elif vis:
annot2.set_visible(False)
fig.canvas.draw_idle()
canvas.mpl_connect("motion_notify_event", on_plot_hover)
continue_reading = True
def read_from_arduino():
x, y_temp, y_humi = [0], [0], [0]
while continue_reading:
try:
line = ser.readline().decode('utf-8').strip()
temperature, humidity = map(float, line.split(','))
x.append(x[-1] + 1)
y_temp.append(temperature)
y_humi.append(humidity)
if len(x) > max_data_points:
x = x[-max_data_points:]
y_temp = y_temp[-max_data_points:]
y_humi = y_humi[-max_data_points:]
temp_line.set_xdata(x)
temp_line.set_ydata(y_temp)
humi_line.set_xdata(x)
humi_line.set_ydata(y_humi)
ax1.relim()
ax1.autoscale_view()
ax2.relim()
ax2.autoscale_view()
canvas.draw()
except ValueError:
pass
thread = threading.Thread(target=read_from_arduino)
thread.daemon = True
thread.start()
def close_graph():
global graph_window, continue_reading
continue_reading = False
if graph_window is not None:
graph_window.destroy()
graph_window = None
def update_data_points():
global max_data_points
try:
max_data_points = int(data_points_entry.get())
except ValueError:
print("Please enter a valid integer for data points.")
root = tk.Tk()
root.title("Arduino Data Viewer")
start_button = tk.Button(root, text="Start Graph", command=start_graph)
start_button.pack()
close_button = tk.Button(root, text="Close Graph", command=close_graph)
close_button.pack()
data_points_label = tk.Label(root, text="Set Data Points:")
data_points_label.pack()
data_points_entry = tk.Entry(root)
data_points_entry.pack()
data_points_button = tk.Button(root, text="Set", command=update_data_points)
data_points_button.pack()
root.mainloop()
제공된 코드에서 그래프에 커서를 올렸을 때 해당 위치의 값을 출력하는 부분은 on_plot_hover 함수를 통해 처리됩니다.
어노테이션 초기화
annot1과 annot2는 matplotlib의 어노테이션 객체입니다. 이들은 각각 온도 그래프(ax1)와 습도 그래프(ax2)에 대한 정보를 표시하는 데 사용됩니다. 어노테이션은 set_visible(False)를 사용하여 초기에는 보이지 않게 설정됩니다.
on_plot_hover 함수
이 함수는 마우스가 그래프 위에서 움직일 때마다 호출됩니다. 함수 내부에서는 두 개의 그래프 (온도와 습도)에 대한 이벤트를 각각 확인합니다.
온도 그래프(ax1)에 대한 이벤트 처리
if event.inaxes == ax1: 코드를 통해 마우스 이벤트가 온도 그래프에서 발생했는지 확인합니다. cont, ind = temp_line.contains(event)를 사용하여 마우스 포인터가 온도 그래프의 데이터 포인트 중 하나를 가리키고 있는지 확인합니다. 만약 가리키고 있다면 (cont가 True일 경우), update_annot 함수를 호출하여 해당 데이터 포인트에 대한 정보를 어노테이션으로 표시합니다.
습도 그래프(ax2)에 대한 이벤트 처리
온도 그래프와 유사하게, 마우스 이벤트가 습도 그래프에서 발생했는지 확인합니다. 마찬가지로 cont, ind = humi_line.contains(event)를 사용하여 마우스 포인터가 습도 그래프의 데이터 포인트를 가리키는지 확인하고, 가리키고 있다면 해당 정보를 어노테이션으로 표시합니다.
update_annot 함수
이 함수는 어노테이션 객체를 업데이트하여 그래프에 표시할 텍스트와 위치를 설정합니다. annot.set_text(f"x={x:.2f}, y={y:.2f}") 라인에서는 선택된 데이터 포인트의 x(시간)와 y(온도 또는 습도) 값을 표시합니다.
그래프 업데이트
마지막으로 fig.canvas.draw_idle()을 호출하여 그래프를 업데이트하고 변경된 어노테이션을 화면에 표시합니다.
결과>>
코드 다운로드>>
'아두이노 Arduino > 파이썬(Python)' 카테고리의 다른 글
아두이노 온도, 습도 값 파이썬으로 그래프 출력하기, 일정 개수만 출력, 버튼으로 그래프 만들기 (0) | 2024.01.18 |
---|---|
아두이노 온도, 습도 값 파이썬으로 그래프 출력하기(Arduino, Python, Serial, Graph) (1) | 2024.01.14 |
Ping이란? CMD, 파이썬으로 예제 실습하기(Window OS) (2) | 2023.12.20 |
[Python] 파이썬 실행파일(exe) 만들기(Pyinstaller, Batch file) (0) | 2022.11.14 |
[아두이노] 파이썬 threading timer 로 주기적으로 LED 켜고 끄기(타이머, 인터럽트, Interrupt) (0) | 2022.05.18 |