콘텐츠로 이동

4단원 마무리 - 나만의 환경 대시보드 완성

🎯 이 장에서 배우는 것

  • [ ] 온습도, 조도 데이터를 표시하는 대시보드를 완성할 수 있다
  • [ ] AI에게 추가 기능을 요청하여 개성 있는 대시보드로 발전시킬 수 있다
  • [ ] 스마트폰에서 접속하여 시연할 수 있다

⏱️ 예상 학습 시간: 2차시


📚 미리 알아야 할 것

지금까지 배운 내용을 총정리해볼게요:

  • Flask 웹서버: 피코에서 웹페이지 제공
  • 센서 데이터 읽기: 온습도(DHT11), 조도(CDS)
  • JSON API: /api/data로 실시간 데이터 전송
  • JavaScript fetch: 자동 갱신으로 화면 업데이트

📚 핵심 개념

개념: 대시보드 완성이란?

대시보드 완성은 마치 자동차 계기판을 조립하는 것과 같아요.
속도계, 연료계, 경고등을 한곳에 모아 운전자가 한눈에 볼 수 있게 만드는 거죠!

완성된 대시보드의 구성요소:

flowchart TB subgraph 센서["📡 센서 수집"] A[🌡️ 온도] B[💧 습도] C[☀️ 조도] end subgraph 서버["🖥️ 피코 서버"] D["/api/data"] end subgraph 화면["📱 대시보드"] E[숫자 표시] F[상태 색상] G[경고 알림] end A --> D B --> D C --> D D --> E D --> F D --> G

🔨 따라하기

Step 1: 통합 대시보드 코드 완성

main.py - 센서 통합 서버:

from machine import Pin, ADC
import dht
import network
import socket
import json
import time

# 센서 설정
dht_sensor = dht.DHT11(Pin(15))
cds = ADC(Pin(26))

# WiFi 연결
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('WiFi이름', '비밀번호')

while not wlan.isconnected():
    time.sleep(1)

print(f'접속 주소: http://{wlan.ifconfig()[0]}')

# HTML 페이지
html = """<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>🏠 환경 대시보드</title>
    <style>
        body { 
            font-family: Arial; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            margin: 0;
            padding: 20px;
        }
        .container {
            max-width: 400px;
            margin: 0 auto;
        }
        h1 { 
            color: white; 
            text-align: center;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
        }
        .card {
            background: white;
            border-radius: 20px;
            padding: 25px;
            margin: 15px 0;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            text-align: center;
            transition: transform 0.3s;
        }
        .card:hover { transform: scale(1.02); }
        .icon { font-size: 50px; }
        .value { 
            font-size: 48px; 
            font-weight: bold;
            margin: 10px 0;
        }
        .label { color: #666; font-size: 14px; }
        .temp { color: #e74c3c; }
        .humid { color: #3498db; }
        .light { color: #f39c12; }
        .status {
            padding: 8px 16px;
            border-radius: 20px;
            color: white;
            font-size: 14px;
            margin-top: 10px;
            display: inline-block;
        }
        .good { background: #27ae60; }
        .warning { background: #f39c12; }
        .danger { background: #e74c3c; }
        .update-time {
            color: rgba(255,255,255,0.8);
            text-align: center;
            font-size: 12px;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🏠 내 방 환경</h1>

        <div class="card">
            <div class="icon">🌡️</div>
            <div class="value temp" id="temp">--</div>
            <div class="label">온도 (°C)</div>
            <div class="status" id="temp-status">측정 중...</div>
        </div>

        <div class="card">
            <div class="icon">💧</div>
            <div class="value humid" id="humid">--</div>
            <div class="label">습도 (%)</div>
            <div class="status" id="humid-status">측정 중...</div>
        </div>

        <div class="card">
            <div class="icon">☀️</div>
            <div class="value light" id="light">--</div>
            <div class="label">밝기 (%)</div>
            <div class="status" id="light-status">측정 중...</div>
        </div>

        <div class="update-time" id="update-time"></div>
    </div>

    <script>
        function getStatus(type, value) {
            if (type === 'temp') {
                if (value < 18) return ['🥶 추워요', 'warning'];
                if (value > 28) return ['🥵 더워요', 'danger'];
                return ['😊 쾌적해요', 'good'];
            }
            if (type === 'humid') {
                if (value < 30) return ['건조해요', 'warning'];
                if (value > 70) return ['습해요', 'warning'];
                return ['적당해요', 'good'];
            }
            if (type === 'light') {
                if (value < 20) return ['🌙 어두워요', 'warning'];
                if (value > 80) return ['😎 밝아요', 'good'];
                return ['적당해요', 'good'];
            }
        }

        function updateDashboard() {
            fetch('/api/data')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('temp').textContent = data.temperature;
                    document.getElementById('humid').textContent = data.humidity;
                    document.getElementById('light').textContent = data.light;

                    // 상태 업데이트
                    let [tempText, tempClass] = getStatus('temp', data.temperature);
                    let [humidText, humidClass] = getStatus('humid', data.humidity);
                    let [lightText, lightClass] = getStatus('light', data.light);

                    document.getElementById('temp-status').textContent = tempText;
                    document.getElementById('temp-status').className = 'status ' + tempClass;
                    document.getElementById('humid-status').textContent = humidText;
                    document.getElementById('humid-status').className = 'status ' + humidClass;
                    document.getElementById('light-status').textContent = lightText;
                    document.getElementById('light-status').className = 'status ' + lightClass;

                    // 시간 표시
                    let now = new Date();
                    document.getElementById('update-time').textContent = 
                        '마지막 업데이트: ' + now.toLocaleTimeString();
                });
        }

        updateDashboard();
        setInterval(updateDashboard, 3000);
    </script>
</body>
</html>"""

def get_sensor_data():
    """모든 센서 데이터 읽기"""
    try:
        dht_sensor.measure()
        temp = dht_sensor.temperature()
        humid = dht_sensor.humidity()
    except:
        temp, humid = 0, 0

    # 조도를 퍼센트로 변환 (0~65535 → 0~100)
    light_raw = cds.read_u16()
    light = int((1 - light_raw / 65535) * 100)

    return {"temperature": temp, "humidity": humid, "light": light}

# 웹서버 시작
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)

print('대시보드 서버 시작!')

while True:
    cl, addr = s.accept()
    request = cl.recv(1024).decode()

    if 'GET /api/data' in request:
        data = get_sensor_data()
        response = json.dumps(data)
        cl.send('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n')
        cl.send(response)
    else:
        cl.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n')
        cl.send(html)

    cl.close()

Step 2: 스마트폰에서 접속하기

flowchart LR A["📱 스마트폰"] -->|"같은 WiFi"| B["📶 공유기"] B --> C["🔌 피코 W"] style A fill:#e8f5e9 style C fill:#fff3e0

접속 방법:

  1. 스마트폰을 같은 WiFi에 연결
  2. Thonny 콘솔에 표시된 IP 주소 확인
  3. 스마트폰 브라우저에서 http://IP주소 입력

체크포인트: 스마트폰에서 온도, 습도, 조도가 모두 보이면 성공!


🤖 AI와 함께 기능 추가하기

이제 AI에게 원하는 기능을 요청해보세요!

요청 예시 1: 그래프 추가

"온도 변화를 보여주는 간단한 막대 그래프를 추가하고 싶어요.
최근 10개 데이터를 보여주면 좋겠어요."

요청 예시 2: 알림음 추가

"온도가 30도를 넘으면 부저가 울리게 하고 싶어요.
어떻게 코드를 수정하면 될까요?"

요청 예시 3: 디자인 변경

"대시보드 배경색을 어두운 테마로 바꾸고 싶어요.
다크모드처럼요!"

💡 : AI에게 현재 코드를 보여주고, 원하는 결과를 구체적으로 설명하면 더 정확한 답을 얻을 수 있어요!


📝 전체 시스템 구성

flowchart TB subgraph HW["하드웨어"] P["Pico W"] D["DHT11"] C["CDS 센서"] end subgraph SW["소프트웨어"] S["Flask 서버"] A["JSON API"] H["HTML/CSS/JS"] end subgraph USER["사용자"] PC["💻 컴퓨터"] PH["📱 스마트폰"] end D --> P C --> P P --> S S --> A S --> H A --> PC A --> PH H --> PC H --> PH

⚠️ 주의할 점

1. WiFi 연결 문제

증상: 스마트폰에서 접속이 안 돼요

해결: - 피코와 스마트폰이 같은 WiFi인지 확인 - 공유기의 AP 격리(Client Isolation) 기능이 꺼져 있는지 확인

2. 센서 오류

증상: 온도가 0으로 표시돼요

해결: - DHT11 연결 핀 번호 확인 (GP15) - 센서 전원(3.3V, GND) 확인


✅ 점검하기

1. 대시보드에서 표시되는 정보 3가지는?

정답 확인 온도(°C), 습도(%), 조도(밝기 %)

2. 스마트폰에서 접속하려면 어떤 조건이 필요한가요?

정답 확인 피코 W와 스마트폰이 **같은 WiFi 네트워크**에 연결되어 있어야 합니다.

3. AI에게 기능 추가를 요청할 때 포함하면 좋은 것은?

정답