아두이노 Arduino/파이썬(Python)

아두이노 온도, 습도 값 파이썬으로 그래프 출력 - 커서 값 출력하기(Arduino, Cursor, Python, Cordination, graph)

끄적끄적아무거나 2024. 1. 25. 08:44
반응형

 

목차

     

     

     

     

     

     

     

    파이썬으로 그래프 출력 - 커서 값 출력하기#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()을 호출하여 그래프를 업데이트하고 변경된 어노테이션을 화면에 표시합니다.

     

     

     

    결과>>

     

     

     

    코드 다운로드>>

    test00.py
    0.00MB

     

     

     

    반응형