콘텐츠로 이동

Chapter 14: IoT 시계 - 통합 프로젝트

🎯 이 장에서 배우는 것

  • [ ] 여러 기능(WiFi, NTP, API, OLED)을 하나의 시스템으로 통합할 수 있다
  • [ ] OLED에 시간과 날씨를 함께 표시할 수 있다
  • [ ] 자동 업데이트 기능을 구현할 수 있다
  • [ ] 진짜 IoT 기기를 완성할 수 있다

💡 왜 이걸 배우나요?

지금까지 우리는 WiFi 연결, NTP 시간 동기화, 웹 API 호출, OLED 표시를 각각 배웠어요. 하지만 실제 IoT 기기는 이 모든 기능이 하나로 합쳐져 동작합니다!

스마트워치를 생각해보세요. 시간도 보여주고, 날씨도 알려주고, 자동으로 업데이트되죠. 오늘 우리가 만들 IoT 시계가 바로 그런 기기예요!

이 프로젝트를 완성하면: - 🕐 실시간 시계가 OLED에 표시됩니다 - 🌤️ 현재 날씨와 온도가 함께 나타납니다 - 🔄 자동으로 시간과 날씨가 업데이트됩니다

진짜 IoT 기기를 만드는 거예요! 🎉


📚 핵심 개념

개념 1: 시스템 통합이란?

  1. 비유로 시작: "시스템 통합은 마치 오케스트라 지휘자와 같아요. 각 악기(기능)가 따로 연주하면 소음이지만, 지휘자가 조화롭게 이끌면 아름다운 음악이 됩니다."

  2. 정확한 정의: "여러 개의 독립적인 기능을 하나의 프로그램에서 순서대로, 또는 동시에 동작하도록 연결하는 것입니다."

  3. 예시로 확인: "WiFi 연결 → NTP 동기화 → 날씨 API 호출 → OLED 표시. 이 순서가 잘 맞아야 시계가 제대로 작동해요."

쉽게 말하면: 레고 블록처럼 각 기능을 조립해서 하나의 완성품을 만드는 것!

flowchart LR A[📶 WiFi 연결] --> B[🕐 NTP 동기화] B --> C[🌤️ 날씨 API] C --> D[📺 OLED 표시] D --> E[🔄 자동 업데이트] E --> B style A fill:#e3f2fd,stroke:#1976d2 style B fill:#fff3e0,stroke:#f57c00 style C fill:#e8f5e9,stroke:#388e3c style D fill:#fce4ec,stroke:#c2185b style E fill:#f3e5f5,stroke:#7b1fa2

개념 2: 자동 업데이트

  1. 비유로 시작: "자동 업데이트는 마치 알람시계처럼, 정해진 시간마다 알아서 동작하는 것이에요."

  2. 정확한 정의: "일정 시간 간격으로 데이터를 새로 가져와 화면을 갱신하는 기능입니다."

  3. 예시로 확인: "매 초마다 시간 업데이트, 10분마다 날씨 업데이트처럼요."

쉽게 말하면: "1초마다 시계 갱신, 10분마다 날씨 갱신" - 타이머 역할!


🔨 따라하기

Step 1: 시스템 설계하기

목표: IoT 시계의 전체 구조를 이해합니다.

flowchart TB subgraph 초기화["🚀 초기화 (한 번만)"] A1[WiFi 연결] --> A2[NTP 동기화] A2 --> A3[첫 날씨 가져오기] end subgraph 메인루프["🔄 메인 루프 (계속 반복)"] B1[현재 시간 읽기] --> B2[OLED에 시간 표시] B2 --> B3{10분 지났나?} B3 -->|Yes| B4[날씨 업데이트] B3 -->|No| B5[1초 대기] B4 --> B5 B5 --> B1 end 초기화 --> 메인루프 style A1 fill:#e3f2fd,stroke:#1976d2 style A2 fill:#fff3e0,stroke:#f57c00 style A3 fill:#e8f5e9,stroke:#388e3c style B2 fill:#fce4ec,stroke:#c2185b

OLED 화면 레이아웃:

┌────────────────────────┐
│  14:35:27              │  ← 현재 시간 (큰 글씨)
│  2024-01-15 월요일     │  ← 날짜
│──────────────────────│
│  ☀ 맑음  15°C         │  ← 날씨 정보
│  Seoul                 │  ← 도시 이름
└────────────────────────┘

여기서 잠깐! 🤔 화면을 구역별로 나누면 정보를 깔끔하게 표시할 수 있어요!


Step 2: 기본 코드 통합하기

목표: WiFi, NTP, OLED를 하나로 합칩니다.

코드:

# === WHAT: IoT 시계 기본 틀 ===
# WiFi 연결 → NTP 동기화 → OLED에 시간 표시

# --- WHY: 각 기능을 순서대로 연결해야 해요 ---
# WiFi가 먼저 연결되어야 NTP와 API를 사용할 수 있어요

import network
import ntptime
import time
from machine import Pin, I2C
import ssd1306

# HOW: 설정값 정의
WIFI_SSID = "여기에_와이파이_이름"
WIFI_PASS = "여기에_비밀번호"

# I2C와 OLED 초기화
i2c = I2C(0, sda=Pin(4), scl=Pin(5))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

def connect_wifi():
    """WiFi에 연결하는 함수"""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    if not wlan.isconnected():
        print("WiFi 연결 중...")
        oled.fill(0)
        oled.text("WiFi...", 0, 0)
        oled.show()

        wlan.connect(WIFI_SSID, WIFI_PASS)

        # 최대 10초 대기
        for i in range(10):
            if wlan.isconnected():
                break
            time.sleep(1)

    if wlan.isconnected():
        print("WiFi 연결 성공!")
        print("IP:", wlan.ifconfig()[0])
        return True
    else:
        print("WiFi 연결 실패")
        return False

def sync_time():
    """NTP로 시간 동기화"""
    try:
        ntptime.settime()
        print("시간 동기화 완료!")
        return True
    except:
        print("시간 동기화 실패")
        return False

def get_korean_time():
    """한국 시간으로 변환 (UTC+9)"""
    t = time.localtime(time.time() + 9 * 3600)
    return t

# 실행
if connect_wifi():
    sync_time()

    # 시간 표시 테스트
    t = get_korean_time()
    oled.fill(0)
    oled.text("{:02d}:{:02d}:{:02d}".format(t[3], t[4], t[5]), 0, 0)
    oled.show()

실행 결과:

WiFi 연결 중...
WiFi 연결 성공!
IP: 192.168.0.15
시간 동기화 완료!
(OLED에 현재 시간 표시)


Step 3: 날씨 정보 추가하기

목표: 날씨 API를 연동합니다.

코드:

# === WHAT: 날씨 정보 가져오기 ===
# OpenWeatherMap API로 현재 날씨를 받아옵니다

import urequests
import json

# --- WHY: 실시간 날씨를 표시하려면 API가 필요해요 ---

# API 설정 (미리 발급받은 키 사용)
API_KEY = "여기에_API_키"
CITY = "Seoul"

def get_weather():
    """날씨 정보 가져오기"""
    try:
        url = f"http://api.openweathermap.org/data/2.5/weather?q={CITY}&appid={API_KEY}&units=metric"

        response = urequests.get(url)
        data = json.loads(response.text)
        response.close()

        # 필요한 정보 추출
        weather = {
            "temp": round(data["main"]["temp"]),
            "desc": data["weather"][0]["main"],
            "city": data["name"]
        }

        print(f"날씨: {weather['desc']}, {weather['temp']}°C")
        return weather

    except Exception as e:
        print("날씨 가져오기 실패:", e)
        return None

# 날씨 설명을 한글로 변환
def translate_weather(desc):
    """영어 날씨를 간단한 한글로"""
    weather_dict = {
        "Clear": "맑음",
        "Clouds": "흐림",
        "Rain": "비",
        "Snow": "눈",
        "Drizzle": "이슬비",
        "Mist": "안개"
    }
    return weather_dict.get(desc, desc)

여기서 잠깐! 🤔 API 호출은 인터넷 트래픽을 사용하니까, 너무 자주 하지 않는 게 좋아요. 10분에 한 번이면 충분해요!


📝 전체 코드

# === IoT 시계 - 완성 코드 ===
# WiFi + NTP + 날씨 API + OLED 통합

import network
import ntptime
import urequests
import json
import time
from machine import Pin, I2C
import ssd1306

# ========== 설정 ==========
WIFI_SSID = "여기에_와이파이_이름"
WIFI_PASS = "여기에_비밀번호"
API_KEY = "여기에_API_키"
CITY = "Seoul"
WEATHER_UPDATE_MIN = 10  # 날씨 업데이트 간격 (분)

# ========== 초기화 ==========
i2c = I2C(0, sda=Pin(4), scl=Pin(5))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

# 전역 변수
weather_data = {"temp": "--", "desc": "---", "city": CITY}
last_weather_update = 0

# ========== 함수들 ==========
def connect_wifi():
    """WiFi 연결"""
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    if not wlan.isconnected():
        show_message("WiFi 연결중...")
        wlan.connect(WIFI_SSID, WIFI_PASS)

        for i in range(10):
            if wlan.isconnected():
                break
            time.sleep(1)

    return wlan.isconnected()

def sync_time():
    """NTP 시간 동기화"""
    try:
        ntptime.settime()
        return True
    except:
        return False

def get_korean_time():
    """한국 시간 (UTC+9)"""
    return time.localtime(time.time() + 9 * 3600)

def get_weather():
    """날씨 정보 가져오기"""
    global weather_data
    try:
        url = f"http://api.openweathermap.org/data/2.5/weather?q={CITY}&appid={API_KEY}&units=metric"
        response = urequests.get(url)
        data = json.loads(response.text)
        response.close()

        weather_data = {
            "temp": round(data["main"]["temp"]),
            "desc": data["weather"][0]["main"],
            "city": data["name"]
        }
        return True
    except:
        return False

def show_message(msg):
    """간단한 메시지 표시"""
    oled.fill(0)
    oled.text(msg, 0, 28)
    oled.show()

def update_display():
    """OLED 화면 업데이트"""
    t = get_korean_time()

    oled.fill(0)

    # 시간 (큰 글씨 효과 - 16픽셀 위치)
    time_str = "{:02d}:{:02d}:{:02d}".format(t[3], t[4], t[5])
    oled.text(time_str, 24, 5)

    # 날짜
    date_str = "{}-{:02d}-{:02d}".format(t[0], t[1], t[2])
    oled.text(date_str, 24, 20)

    # 구분선
    oled.hline(0, 32, 128, 1)

    # 날씨 정보
    weather_str = "{} {}C".format(weather_data["desc"], weather_data["temp"])
    oled.text(weather_str, 0, 40)
    oled.text(weather_data["city"], 0, 52)

    oled.show()

# ========== 메인 실행 ==========
def main():
    global last_weather_update

    # 1. WiFi 연결
    if not connect_wifi():
        show_message("WiFi 실패!")
        return

    show_message("WiFi OK!")
    time.sleep(1)

    # 2. 시간 동기화
    if sync_time():
        show_message("시간 동기화 OK!")
    else:
        show_message("시간 동기화 실패")
    time.sleep(1)

    # 3. 첫 날씨 가져오기
    show_message("날씨 확인중...")
    get_weather()
    last_weather_update = time.time()

    # 4. 메인 루프
    show_message("시작!")
    time.sleep(1)

    while True:
        # 화면 업데이트
        update_display()

        # 10분마다 날씨 업데이트
        if time.time() - last_weather_update > WEATHER_UPDATE_MIN * 60:
            get_weather()
            last_weather_update = time.time()

        # 1초 대기
        time.sleep(1)

# 실행
main()

⚠️ 자주 하는 실수

실수 1: WiFi 정보를 그대로 두고 실행

증상: WiFi 연결 실패! 또는 무한 대기

원인: SSID와 비밀번호를 수정하지 않음

해결:

# 잘못된 코드
WIFI_SSID = "여기에_와이파이_이름"
WIFI_PASS = "여기에_비밀번호"

# 올바른 코드
WIFI_SSID = "MyHome_WiFi"  # 실제 와이파이 이름
WIFI_PASS = "mypassword123"  # 실제 비밀번호

실수 2: API 키 없이 날씨 요청

증상: 401 Unauthorized 에러

원인: API 키를 발급받지 않았거나, 잘못 입력함

해결:

# 잘못된 코드
API_KEY = "여기에_API_키"

# 올바른 코드 (openweathermap.org에서 발급)
API_KEY = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

실수 3: 한국 시간 변환 누락

증상: 시간이 9시간 늦게 표시됨

원인: UTC 시간을 그대로 사용

해결:

# 잘못된 코드
t = time.localtime()  # UTC 시간

# 올바른 코드
t = time.localtime(time.time() + 9 * 3600)  # 한국 시간 (+9시간)


✅ 스스로 점검하기

  1. WiFi → NTP → API 순서로 연결해야 하는 이유는 무엇인가요?

  2. 날씨를 1초마다가 아닌 10분마다 업데이트하는 이유는?

  3. time.time() + 9 * 3600에서 9 * 3600은 무엇을 의미하나요?

정답 확인 1. **순서의 이유**: WiFi가 연결되어야 인터넷을 사용할 수 있고, 인터넷이 있어야 NTP 서버에서 시간을 받고, API로 날씨를 가져올 수 있어요. 2. **업데이트 간격**: 날씨는 급하게 변하지 않고, API 호출이 너무 잦으면 서버에 부담을 주고 API 사용량 제한에 걸릴 수 있어요. 3. **시간 계산**: 9시간(한국 시간대) × 3600초(1시간) = 32400초. UTC 시간에 이 값을 더하면 한국 시간이 됩니다.

🚀 더 해보기

도전 1: 날씨 아이콘 추가 (쉬움)

날씨 상태에 따라 간단한 텍스트 아이콘을 추가해보세요!

# 힌트: 날씨별 이모지 문자 사용
icons = {"Clear": "*", "Clouds": "~", "Rain": "="}

도전 2: 버튼으로 화면 전환 (중간)

버튼을 누르면 시계 ↔ 상세 날씨 화면을 전환해보세요!

도전 3: 알람 기능 추가 ⭐ (어려움)

특정 시간이 되면 부저가 울리는 알람 기능을 추가해보세요!

# 힌트
ALARM_HOUR = 7
ALARM_MIN = 30
if t[3] == ALARM_HOUR and t[4] == ALARM_MIN:
    # 부저 울리기


🔗 다음 장으로

축하합니다! 🎉

진짜 IoT 기기를 완성했어요! 이 시계는: - ✅ 인터넷에서 정확한 시간을 가져오고 - ✅ 실시간 날씨 정보를 표시하고 - ✅ 자동으로 업데이트됩니다

배운 것 정리: - 여러 기능을 하나로 통합하는 방법 - 자동 업데이트 구현 (타이머 개념) - 실제 IoT 기기의 동작 원리

다음 장에서는 15차시 전체를 돌아보며, 여러분의 학습 여정을 포트폴리오로 정리합니다. 이 IoT 시계도 훌륭한 포트폴리오 작품이 될 거예요! 📁