콘텐츠로 이동

12.1 날씨 신호등

오프라인 Live 모드 LED 스트립 12구

이 절에서는 10장에서 학습한 강수 분류 모델과 공개 기상 예보를 LED 스트립에 함께 표시하는 날씨 신호등을 구성한다. LED 1~6번은 예보 서비스의 내일 강수확률을 막대로, LED 7~12번은 직접 학습한 모델의 판정을 색으로 표시한다. 며칠 동안 두 표시를 실제 날씨와 비교하면, 10장에서 수치로 확인한 모델의 한계를 실물로 확인할 수 있다.

시스템 구성

구성 요소 역할
Open-Meteo 예보 API 서울의 오늘 예상 강수량과 내일 최대 강수확률 제공(인증키 불필요)
조건문 모델 my_model 10.4절 가중치 적용 결정트리(깊이 3)를 if 문으로 변환한 함수
PC(mBlock 파이썬 편집기, Live 모드) 예보 수신, 모델 판정, 사이버파이 제어
사이버파이 + 포켓쉴드 + LED 스트립 판정 결과 표시(4.7절 연결 구성)

예보 수신은 PC에서 실행한다. Live 모드에서는 코드가 PC의 파이썬에서 실행되고 사이버파이는 USB로 제어되므로(4.3절), PC의 인터넷 연결로 예보 API에 접속할 수 있다.

결정트리의 조건문 변환

10.4절의 가중치 적용 결정트리를 학습한 뒤 clf_b.tree_.threshold로 분기 기준값을 확인하면 다음과 같다. export_text는 소수 첫째 자리로 반올림해 출력하므로, 조건문에는 반올림하지 않은 기준값을 사용한다.

분기 특징 기준값 export_text 표기
1단계 precip_mm 2.35 2.4
2단계(왼쪽) doy_cos −0.1425 −0.1
3단계(왼쪽) doy_sin 0.4189 0.4
2단계(오른쪽) doy_cos 0.1912 0.2
3단계(오른쪽) precip_mm 11.25 11.2

모든 잎이 같은 범주를 출력하는 분기(일조시간 8.75, doy_sin 0.2154)는 판정 결과에 영향이 없으므로 조건문에서 생략한다. 이렇게 작성한 my_model 함수는 2021~2025년 1,825일 전체에서 원래 결정트리와 판정이 100% 일치했다.

왼쪽 가지의 조건 doy_cos <= -0.1425 and doy_sin <= 0.4189는 연중 약 158일째(6월 상순)부터 약 266일째(9월 하순)까지에 해당한다. 가중치를 적용한 모델은 이 기간에 오늘 비가 오지 않았더라도 다음 날을 강수로 판정한다. 장마와 여름철 소나기가 집중되는 기간의 강수 빈도를 학습한 결과다.

날씨 신호등 프로그램

mBlock 파이썬 편집기에서 사이버파이를 연결하고 Live 모드를 선택한 뒤 다음 코드를 실행한다.

import cyberpi
import json
import math
import urllib.request
from datetime import date

PORT = "s1"
URL = ("https://api.open-meteo.com/v1/forecast"
       "?latitude=37.5665&longitude=126.978"
       "&daily=precipitation_sum,precipitation_probability_max"
       "&timezone=Asia%2FSeoul&forecast_days=2")


def my_model(precip_mm, doy_sin, doy_cos):
    """10.4절 가중치 적용 결정트리(깊이 3)의 조건문 변환. 1 = 다음 날 강수"""
    if precip_mm <= 2.35:
        if doy_cos <= -0.1425 and doy_sin <= 0.4189:
            return 1
        return 0
    if doy_cos <= 0.1912:
        return 1
    if precip_mm <= 11.25:
        return 0
    return 1


# 1) 예보 수신
with urllib.request.urlopen(URL, timeout=10) as r:
    daily = json.loads(r.read().decode("utf-8"))["daily"]
today_precip = daily["precipitation_sum"][0]                 # 오늘 예상 강수량(mm)
tomorrow_prob = daily["precipitation_probability_max"][1]    # 내일 최대 강수확률(%)

# 2) 내 모델 판정
d = date.today().timetuple().tm_yday
doy_sin = math.sin(2 * math.pi * d / 365.25)
doy_cos = math.cos(2 * math.pi * d / 365.25)
mine = my_model(today_precip, doy_sin, doy_cos)

# 3) LED 표시: 1~6번 = 예보 강수확률 막대, 7~12번 = 내 모델 판정
n = round(tomorrow_prob / 100 * 6)
left = ["b"] * n + ["k"] * (6 - n)
right = ["b" if mine == 1 else "y"] * 6
cyberpi.pocket.led_set_bri(30, PORT)
cyberpi.pocket.led_show(" ".join(left + right), PORT)

cyberpi.display.clear()
cyberpi.console.println("forecast {}%".format(tomorrow_prob))
cyberpi.console.println("my model: {}".format("RAIN" if mine == 1 else "DRY"))
단계 처리 내용
1) 예보 수신 예보 API의 JSON 응답에서 daily 항목을 추출한다. 각 목록의 0번이 오늘, 1번이 내일 값이다.
2) 모델 판정 오늘 예상 강수량과 오늘 날짜의 계절 위치를 my_model에 입력한다. 학습 때와 같은 방식(9.2절)으로 doy_sin·doy_cos를 산출한다.
3) LED 표시 예보 강수확률을 6칸 비율로 환산해 파랑으로 점등하고, 모델이 강수로 판정하면 7~12번을 파랑, 무강수면 노랑으로 점등한다.

표시 예

다음은 사이버파이 대신 함수 호출 기록 장치를 연결해 2026-11-08에 같은 코드를 실행한 결과다. 내일 강수확률 53%가 6칸 중 3칸(b b b)으로 표시되고, 모델은 오늘 예상 강수량과 11월의 계절 위치로 무강수(y)를 판정했다.

('led_set_bri', 30, 's1')
('led_show', 'b b b k k k y y y y y y', 's1')
('clear',)
('println', 'forecast 53%')
('println', 'my model: DRY')

학습 자료와 입력 자료의 차이

모델은 재분석 자료의 일강수량으로 학습했지만, 이 프로그램은 예보 API의 오늘 예상 강수량을 입력한다. 오전에 실행하면 오늘의 강수가 아직 발생하지 않은 상태의 예상값이 입력된다. 학습 자료와 입력 자료의 산출 방식이 다르면 판정 성능이 학습 때와 달라질 수 있다.

관찰 과제

1주일 동안 매일 같은 시각에 프로그램을 실행하고 다음 표를 기록한다.

날짜 예보 강수확률(%) 내 모델 판정 다음 날 실제 강수 여부

기록이 끝나면 예보와 내 모델의 적중 횟수를 비교하고, 10.3절의 재현율·정밀도 개념으로 결과를 설명한다.

확인 문제

  1. 예보 강수확률이 70% 이상이면 LED 1~6번 전체를 빨강으로 점등하도록 수정하시오.
  2. my_model을 10.2절의 기본 결정트리(가중치 미적용)로 교체하면 11월의 판정이 달라지는지 확인하시오.
  3. time.sleep과 while 반복문을 추가해 1시간마다 예보를 다시 수신하는 프로그램으로 확장하시오.

초안 확인 사항

예보 수신·모델 판정·LED 명령 생성은 PC에서 함수 호출 기록 장치로 시험했다(위 표시 예). mBlock 파이썬 편집기 내장 파이썬(Windows판 3.6.5)에서의 HTTPS 접속, 교내망의 api.open-meteo.com 접속, LED 스트립 실물 점등은 실기기로 확인한 뒤 확정한다.