Skip to main content

🌐 Flask фреймворк и первые веб-интерфейсы. Превращаем IoT-данные в красивые дашборды

📋 Паспорт спринта

Параметр Значение
Предмет Интернет вещей (элективный курс)
Класс 9 класс
Спринт № 21 из 36
Тип занятия Практическая веб-разработка с IoT интеграцией
Продолжительность 90 минут
Формат Hands-on кодинг + дизайн интерфейсов

🎯 Цели спринта (Sprint Goals)

Основная цель:

Освоить создание веб-интерфейсов для IoT систем с помощью Flask фреймворка и научиться визуализировать данные с датчиков в реальном времени

Конкретные результаты спринта:

  • Учащиеся создают первое Flask веб-приложение на Raspberry Pi
  • Понимают архитектуру веб-приложений (frontend, backend, database)
  • Реализуют REST API endpoints для получения IoT данных
  • Создают интерактивные дашборды с графиками в реальном времени
  • Интегрируют физические датчики с веб-интерфейсом
  • 🆕 Применяют принципы UX/UI дизайна для IoT интерфейсов
  • 🆕 Реализуют базовую аутентификацию и безопасность веб-приложения

🔄 Sprint Retrospective предыдущего спринта (0-3 мин)

Быстрая проверка домашнего задания:

  • “Поднимите руку, кто создал план безопасности для умного дома?”
  • “Кто рассчитал ROI от инвестиций в кибербезопасность?”
  • 🆕 “Какая самая критичная уязвимость оказалась в вашем анализе?”
  • “Кто попробовал настроить реальные firewall правила дома?”

Связка с новой темой: “Отлично! Вы защитили IoT устройства от хакеров. Но как пользователи будут удобно управлять всеми этими устройствами? Сегодня создаем красивые веб-интерфейсы!”


🕐 Sprint Timeline (90 минут)

⚡ SPRINT START (3-8 мин): Активация через “веб-магию”

Задача: Показать мощь современных веб-интерфейсов для IoT и вдохновить на создание собственных

🆕 Демо-эксперимент “IoT Dashboard Magic”:

  1. Учитель открывает готовый дашборд на проекторе (заранее подготовленный)
  2. Подключает датчик температуры к Raspberry Pi
  3. Показывает live обновление графика температуры на веб-странице
  4. 🆕 Демонстрирует управление светодиодом через веб-интерфейс
  5. Открывает на телефоне - показывает responsive design
  6. 🆕 Показывает код - “Это всего 50 строк Python!”

Wow-момент для класса: “То, что вы видите - профессиональный IoT дашборд. Через 90 минут каждый из вас создаст свой!”

🆕 Формулировка challenge: “Сегодня вы станете IoT веб-разработчиками! Задача - создать интерфейс, который бабушка сможет использовать для управления умным домом!”

📚 THEORY BLOCK (8-25 мин): Основы веб-разработки для IoT

Микро-блок 1 (8-13 мин): Архитектура веб-приложений

 1🏗️ АРХИТЕКТУРА IoT ВЕБ-ПРИЛОЖЕНИЯ:
 2
 3┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
 4│   FRONTEND      │    │    BACKEND      │    │   HARDWARE      │
 5│  (Browser)      │◄──►│  (Flask/Pi)     │◄──►│  (Sensors)      │
 6└─────────────────┘    └─────────────────┘    └─────────────────┘
 7       │                        │                        │
 8   HTML/CSS/JS              Python Flask              GPIO/I2C
 9   Charts/Graphs            REST API                 Temperature
10   Responsive UI            Database                 Humidity
11   Real-time updates        Authentication           LEDs/Motors
12
13🔄 ПОТОК ДАННЫХ:
141. Датчик читает температуру (Hardware)
152. Python скрипт получает данные (Backend)
163. Flask API отдает JSON (Backend → Frontend)
174. JavaScript строит график (Frontend)
185. Пользователь видит результат (Browser)
19
20📊 КОМПОНЕНТЫ STACK'А:
21├── Frontend: HTML5 + CSS3 + JavaScript + Chart.js
22├── Backend: Python Flask + SQLite + GPIO
23├── Communication: HTTP/HTTPS + WebSockets
24└── Deployment: Raspberry Pi + WiFi

🆕 Аналогия с рестораном:

  • Frontend = Меню и интерьер (то, что видят клиенты)
  • Backend = Кухня (где готовится еда)
  • Database = Склад продуктов (где хранятся данные)
  • API = Официант (передает заказы между залом и кухней)

Микро-блок 2 (13-18 мин): Flask фреймворк - основы

 1🐍 FLASK - МИКРОФРЕЙМВОРК ДЛЯ PYTHON:
 2
 3 ПОЧЕМУ FLASK ДЛЯ IoT?
 4├── Легкий (подходит для Raspberry Pi)
 5├── Простой в изучении
 6├── Гибкий (можно добавить что угодно)
 7├── Большое сообщество
 8└── Отличная интеграция с GPIO
 9
10🔧 ОСНОВНЫЕ КОНЦЕПЦИИ:
11┌─────────────────────────────────────────┐
12 from flask import Flask                 
13 app = Flask(__name__)                   
14                                         
15 @app.route('/')                           Маршрут (URL)
16 def home():                               Функция-обработчик
17     return '<h1>Hello IoT!</h1>'          Ответ браузеру
18                                         
19 if __name__ == '__main__':              
20     app.run(host='0.0.0.0', port=5000)   Запуск сервера
21└─────────────────────────────────────────┘
22
23🛤️ ROUTING (МАРШРУТИЗАЦИЯ):
24├── @app.route('/')  Главная страница
25├── @app.route('/api/temperature')  API для температуры
26├── @app.route('/dashboard')  Дашборд с графиками
27└── @app.route('/control')  Управление устройствами
28
29📄 TEMPLATES (ШАБЛОНЫ):
30├── HTML шаблоны в папке templates/
31├── Jinja2 синтаксис {{ variable }}
32├── Наследование шаблонов {% extends %}
33└── Динамическое содержимое

Микро-блок 3 (18-25 мин): Frontend для IoT - особенности

 1🎨 FRONTEND ДЛЯ IoT ИНТЕРФЕЙСОВ:
 2
 3📱 RESPONSIVE DESIGN:
 4├── Работает на телефонах, планшетах, ПК
 5├── CSS Grid + Flexbox для layout
 6├── Breakpoints для разных экранов
 7└── Touch-friendly кнопки и элементы
 8
 9📊 ВИЗУАЛИЗАЦИЯ ДАННЫХ:
10├── Real-time графики (Chart.js, D3.js)
11├── Gauge метры для датчиков
12├── Status indicators (LED lights)
13├── Historical data charts
14└── Interactive controls (sliders, switches)
15
16⚡ REAL-TIME ОБНОВЛЕНИЯ:
17├── AJAX запросы каждые N секунд
18├── WebSockets для instant updates
19├── Server-Sent Events (SSE)
20└── Progressive enhancement
21
22🎯 UX ПРИНЦИПЫ ДЛЯ IoT:
23├── Clarity: Четко видно состояние устройств
24├── Feedback: Мгновенная реакция на действия
25├── Simplicity: Минимум кликов для базовых задач
26├── Accessibility: Доступно для всех пользователей
27└── Reliability: Работает даже при плохом соединении
28
29🔧 ИНСТРУМЕНТЫ:
30├── HTML5: Семантическая разметка
31├── CSS3: Анимации, transitions, grid
32├── JavaScript (ES6+): Async/await, fetch API
33├── Libraries: Chart.js, Bootstrap, jQuery
34└── Icons: Font Awesome, Material Icons

☕ BREAK (25-30 мин): Техническая пауза

🛠️ ПРАКТИЧЕСКИЙ БЛОК (30-75 мин): “IoT Web Studio” - Создание дашбордов

Этап 1: Подготовка среды разработки (30-35 мин)

4 команды веб-студий:

  • 🔵 Blue Studio: “Weather Station” - метеостанция с датчиками
  • 🔴 Red Studio: “Smart Home Control” - управление освещением и климатом
  • 🟢 Green Studio: “Garden Monitor” - система мониторинга растений
  • 🟡 Yellow Studio: “Security Dashboard” - панель безопасности

🆕 Роли в каждой студии:

  • Frontend Developer - создает HTML/CSS/JS интерфейс
  • Backend Developer - пишет Flask API и работает с датчиками
  • UX/UI Designer - проектирует пользовательский опыт
  • DevOps Engineer - настраивает деплой и мониторинг

Этап 2: Базовое Flask приложение (35-45 мин)

🆕 Универсальный starter kit для всех команд:

Шаг 1: Структура проекта (5 минут)

 1mkdir iot_dashboard
 2cd iot_dashboard
 3
 4# Создание структуры папок
 5mkdir templates static static/css static/js static/img
 6touch app.py
 7touch templates/index.html templates/base.html
 8touch static/css/style.css static/js/main.js
 9
10# Установка зависимостей
11pip3 install flask flask-socketio RPi.GPIO

Шаг 2: Минимальный Flask сервер (5 минут)

 1# app.py
 2from flask import Flask, render_template, jsonify
 3import RPi.GPIO as GPIO
 4import random
 5import time
 6from datetime import datetime
 7
 8app = Flask(__name__)
 9
10# Настройка GPIO (пример для LED)
11LED_PIN = 18
12GPIO.setmode(GPIO.BCM)
13GPIO.setup(LED_PIN, GPIO.OUT)
14led_state = False
15
16@app.route('/')
17def index():
18    return render_template('index.html')
19
20@app.route('/api/sensor-data')
21def get_sensor_data():
22    # Имитация данных датчика (потом заменим на реальные)
23    data = {
24        'temperature': round(random.uniform(18, 25), 1),
25        'humidity': round(random.uniform(40, 70), 1),
26        'timestamp': datetime.now().strftime('%H:%M:%S'),
27        'led_state': led_state
28    }
29    return jsonify(data)
30
31@app.route('/api/toggle-led', methods=['POST'])
32def toggle_led():
33    global led_state
34    led_state = not led_state
35    GPIO.output(LED_PIN, GPIO.HIGH if led_state else GPIO.LOW)
36    return jsonify({'success': True, 'led_state': led_state})
37
38if __name__ == '__main__':
39    try:
40        app.run(host='0.0.0.0', port=5000, debug=True)
41    except KeyboardInterrupt:
42        GPIO.cleanup()

Этап 3: Специализированные проекты по студиям (45-65 мин)

🔵 BLUE STUDIO: “Weather Station Dashboard”

 1# Специализированный код для метеостанции
 2import Adafruit_DHT
 3
 4# Настройка датчика температуры/влажности
 5DHT_SENSOR = Adafruit_DHT.DHT22
 6DHT_PIN = 4
 7
 8@app.route('/api/weather')
 9def get_weather():
10    humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)
11    
12    if humidity is not None and temperature is not None:
13        data = {
14            'temperature': round(temperature, 1),
15            'humidity': round(humidity, 1),
16            'feels_like': round(temperature + (humidity - 50) * 0.1, 1),
17            'comfort_level': get_comfort_level(temperature, humidity),
18            'timestamp': datetime.now().isoformat(),
19            'status': 'online'
20        }
21    else:
22        # Fallback к симулированным данным
23        data = {
24            'temperature': round(random.uniform(18, 25), 1),
25            'humidity': round(random.uniform(40, 70), 1),
26            'feels_like': round(random.uniform(18, 26), 1),
27            'comfort_level': 'comfortable',
28            'timestamp': datetime.now().isoformat(),
29            'status': 'simulated'
30        }
31    
32    return jsonify(data)
33
34def get_comfort_level(temp, humidity):
35    if 20 <= temp <= 24 and 40 <= humidity <= 60:
36        return 'comfortable'
37    elif temp < 18 or temp > 26:
38        return 'uncomfortable'
39    else:
40        return 'acceptable'
41
42@app.route('/api/forecast')
43def get_forecast():
44    # Простой прогноз на основе текущих данных
45    current_temp = random.uniform(18, 25)
46    forecast = []
47    
48    for i in range(24):  # 24 часа прогноза
49        hour = (datetime.now().hour + i) % 24
50        temp_variation = random.uniform(-2, 2)
51        forecast.append({
52            'hour': f"{hour:02d}:00",
53            'temperature': round(current_temp + temp_variation, 1),
54            'humidity': round(random.uniform(40, 70), 1)
55        })
56    
57    return jsonify(forecast)

HTML Template для Weather Station:

 1<!-- templates/weather.html -->
 2{% extends "base.html" %}
 3{% block title %}IoT Weather Station{% endblock %}
 4
 5{% block content %}
 6<div class="weather-dashboard">
 7    <div class="current-conditions">
 8        <div class="temp-display">
 9            <span id="temperature">--</span>°C
10        </div>
11        <div class="humidity-display">
12            <span id="humidity">--</span>% влажность
13        </div>
14        <div class="comfort-indicator">
15            <span id="comfort">Загрузка...</span>
16        </div>
17    </div>
18    
19    <div class="chart-container">
20        <canvas id="temperatureChart"></canvas>
21    </div>
22    
23    <div class="forecast-container">
24        <h3>Прогноз на 24 часа</h3>
25        <div id="hourly-forecast"></div>
26    </div>
27</div>
28
29<script>
30// Инициализация графика
31const ctx = document.getElementById('temperatureChart').getContext('2d');
32const temperatureChart = new Chart(ctx, {
33    type: 'line',
34    data: {
35        labels: [],
36        datasets: [{
37            label: 'Температура',
38            data: [],
39            borderColor: 'rgb(75, 192, 192)',
40            tension: 0.1
41        }, {
42            label: 'Влажность',
43            data: [],
44            borderColor: 'rgb(54, 162, 235)',
45            tension: 0.1
46        }]
47    },
48    options: {
49        responsive: true,
50        scales: {
51            y: {
52                beginAtZero: false
53            }
54        }
55    }
56});
57
58// Обновление данных каждые 5 секунд
59setInterval(updateWeatherData, 5000);
60updateWeatherData(); // Первая загрузка
61
62function updateWeatherData() {
63    fetch('/api/weather')
64        .then(response => response.json())
65        .then(data => {
66            document.getElementById('temperature').textContent = data.temperature;
67            document.getElementById('humidity').textContent = data.humidity;
68            document.getElementById('comfort').textContent = data.comfort_level;
69            
70            // Добавление данных в график
71            const now = new Date().toLocaleTimeString();
72            temperatureChart.data.labels.push(now);
73            temperatureChart.data.datasets[0].data.push(data.temperature);
74            temperatureChart.data.datasets[1].data.push(data.humidity);
75            
76            // Ограничение количества точек на графике
77            if (temperatureChart.data.labels.length > 20) {
78                temperatureChart.data.labels.shift();
79                temperatureChart.data.datasets[0].data.shift();
80                temperatureChart.data.datasets[1].data.shift();
81            }
82            
83            temperatureChart.update();
84        });
85}
86</script>
87{% endblock %}

🔴 RED STUDIO: “Smart Home Control Panel”

 1# Специализированный код для умного дома
 2import json
 3
 4# Состояние устройств умного дома
 5home_devices = {
 6    'living_room': {
 7        'lights': {'state': False, 'brightness': 50, 'color': '#ffffff'},
 8        'temperature': 22,
 9        'ac': {'state': False, 'target_temp': 22}
10    },
11    'bedroom': {
12        'lights': {'state': False, 'brightness': 30, 'color': '#ffaa00'},
13        'temperature': 20,
14        'ac': {'state': False, 'target_temp': 20}
15    },
16    'kitchen': {
17        'lights': {'state': True, 'brightness': 80, 'color': '#ffffff'},
18        'temperature': 24
19    }
20}
21
22@app.route('/api/devices')
23def get_devices():
24    return jsonify(home_devices)
25
26@app.route('/api/device/<room>/<device>', methods=['POST'])
27def control_device(room, device):
28    from flask import request
29    
30    if room not in home_devices:
31        return jsonify({'error': 'Room not found'}), 404
32        
33    data = request.get_json()
34    
35    if device == 'lights':
36        if 'state' in data:
37            home_devices[room]['lights']['state'] = data['state']
38            # Здесь бы был код управления реальными лампочками
39            # GPIO.output(LIGHT_PINS[room], GPIO.HIGH if data['state'] else GPIO.LOW)
40        
41        if 'brightness' in data:
42            home_devices[room]['lights']['brightness'] = data['brightness']
43            # PWM управление яркостью
44            
45        if 'color' in data:
46            home_devices[room]['lights']['color'] = data['color']
47            # RGB LED управление
48    
49    elif device == 'ac' and 'ac' in home_devices[room]:
50        if 'state' in data:
51            home_devices[room]['ac']['state'] = data['state']
52        if 'target_temp' in data:
53            home_devices[room]['ac']['target_temp'] = data['target_temp']
54    
55    return jsonify({'success': True, 'devices': home_devices[room]})
56
57@app.route('/api/energy-usage')
58def get_energy_usage():
59    # Симуляция данных энергопотребления
60    usage_data = []
61    base_consumption = 1000  # Ватт базовое потребление
62    
63    for hour in range(24):
64        # Больше потребления утром и вечером
65        time_factor = 1 + 0.3 * abs(sin(hour * pi / 12))
66        
67        # Учет включенных устройств
68        device_consumption = 0
69        for room in home_devices:
70            if home_devices[room]['lights']['state']:
71                device_consumption += 60 * (home_devices[room]['lights']['brightness'] / 100)
72            if 'ac' in home_devices[room] and home_devices[room]['ac']['state']:
73                device_consumption += 1500  # Кондиционер потребляет много
74        
75        total = (base_consumption * time_factor + device_consumption) * random.uniform(0.9, 1.1)
76        
77        usage_data.append({
78            'hour': hour,
79            'consumption': round(total, 1),
80            'cost': round(total * 0.005, 2)  # 5 копеек за кВт*ч
81        })
82    
83    return jsonify(usage_data)

🟢 GREEN STUDIO: “Garden Monitor System”

 1# Специализированный код для мониторинга сада
 2import board
 3import busio
 4import adafruit_ssd1306
 5
 6# Состояние растений
 7plants = {
 8    'tomatoes': {
 9        'soil_moisture': 45,
10        'light_level': 80,
11        'water_level': 60,
12        'last_watered': '2 hours ago',
13        'status': 'healthy'
14    },
15    'herbs': {
16        'soil_moisture': 30,
17        'light_level': 70,
18        'water_level': 40,
19        'last_watered': '1 day ago',
20        'status': 'needs_water'
21    },
22    'flowers': {
23        'soil_moisture': 55,
24        'light_level': 90,
25        'water_level': 80,
26        'last_watered': '1 hour ago',
27        'status': 'healthy'
28    }
29}
30
31@app.route('/api/plants')
32def get_plants():
33    # Обновление данных датчиков (симуляция)
34    for plant in plants:
35        plants[plant]['soil_moisture'] += random.randint(-5, 3)
36        plants[plant]['light_level'] += random.randint(-10, 10)
37        plants[plant]['water_level'] = max(0, plants[plant]['water_level'] - random.randint(0, 2))
38        
39        # Определение статуса
40        if plants[plant]['soil_moisture'] < 20:
41            plants[plant]['status'] = 'critical'
42        elif plants[plant]['soil_moisture'] < 35:
43            plants[plant]['status'] = 'needs_water'
44        else:
45            plants[plant]['status'] = 'healthy'
46    
47    return jsonify(plants)
48
49@app.route('/api/water/<plant_name>', methods=['POST'])
50def water_plant(plant_name):
51    if plant_name not in plants:
52        return jsonify({'error': 'Plant not found'}), 404
53    
54    # Симуляция полива
55    plants[plant_name]['soil_moisture'] = min(100, plants[plant_name]['soil_moisture'] + 40)
56    plants[plant_name]['water_level'] = min(100, plants[plant_name]['water_level'] + 30)
57    plants[plant_name]['last_watered'] = 'just now'
58    plants[plant_name]['status'] = 'healthy'
59    
60    # Здесь бы был код управления реальным насосом
61    # GPIO.output(PUMP_PINS[plant_name], GPIO.HIGH)
62    # time.sleep(5)  # Полив 5 секунд
63    # GPIO.output(PUMP_PINS[plant_name], GPIO.LOW)
64    
65    return jsonify({
66        'success': True, 
67        'message': f'{plant_name} watered successfully',
68        'plant_status': plants[plant_name]
69    })
70
71@app.route('/api/garden-stats')
72def get_garden_stats():
73    total_plants = len(plants)
74    healthy_plants = len([p for p in plants.values() if p['status'] == 'healthy'])
75    avg_moisture = sum(p['soil_moisture'] for p in plants.values()) / total_plants
76    
77    return jsonify({
78        'total_plants': total_plants,
79        'healthy_plants': healthy_plants,
80        'plants_needing_attention': total_plants - healthy_plants,
81        'average_soil_moisture': round(avg_moisture, 1),
82        'water_usage_today': random.randint(5, 15),  # литров
83        'garden_health_score': round((healthy_plants / total_plants) * 100, 1)
84    })

🟡 YELLOW STUDIO: “Security Dashboard”

 1# Специализированный код для панели безопасности
 2import hashlib
 3from datetime import datetime, timedelta
 4
 5# Журнал событий безопасности
 6security_events = []
 7security_status = {
 8    'system_armed': False,
 9    'doors': {
10        'front_door': {'locked': True, 'last_opened': '2 hours ago'},
11        'back_door': {'locked': True, 'last_opened': '1 day ago'},
12        'garage': {'locked': False, 'last_opened': '30 minutes ago'}
13    },
14    'cameras': {
15        'entrance': {'status': 'online', 'recording': True},
16        'backyard': {'status': 'online', 'recording': True},
17        'garage': {'status': 'offline', 'recording': False}
18    },
19    'sensors': {
20        'motion_living_room': {'triggered': False, 'last_trigger': 'never'},
21        'motion_hallway': {'triggered': False, 'last_trigger': '3 hours ago'},
22        'window_sensor_1': {'open': False, 'last_opened': '1 day ago'}
23    }
24}
25
26@app.route('/api/security-status')
27def get_security_status():
28    # Симуляция случайных событий
29    if random.random() < 0.1:  # 10% шанс события
30        event_types = ['motion_detected', 'door_opened', 'camera_offline', 'sensor_triggered']
31        event = {
32            'type': random.choice(event_types),
33            'location': random.choice(['entrance', 'backyard', 'garage', 'living_room']),
34            'timestamp': datetime.now().isoformat(),
35            'severity': random.choice(['low', 'medium', 'high'])
36        }
37        security_events.append(event)
38        
39        # Ограничиваем количество событий
40        if len(security_events) > 50:
41            security_events.pop(0)
42    
43    return jsonify({
44        'status': security_status,
45        'recent_events': security_events[-10:],  # Последние 10 событий
46        'alerts_count': len([e for e in security_events[-10:] if e['severity'] == 'high'])
47    })
48
49@app.route('/api/arm-system', methods=['POST'])
50def arm_system():
51    from flask import request
52    data = request.get_json()
53    
54    security_status['system_armed'] = data.get('armed', False)
55    
56    event = {
57        'type': 'system_armed' if security_status['system_armed'] else 'system_disarmed',
58        'location': 'control_panel',
59        'timestamp': datetime.now().isoformat(),
60        'severity': 'medium'
61    }
62    security_events.append(event)
63    
64    return jsonify({
65        'success': True,
66        'armed': security_status['system_armed'],
67        'message': f"Security system {'armed' if security_status['system_armed'] else 'disarmed'}"
68    })
69
70@app.route('/api/door-control/<door_name>', methods=['POST'])
71def control_door(door_name):
72    from flask import request
73    
74    if door_name not in security_status['doors']:
75        return jsonify({'error': 'Door not found'}), 404
76    
77    data = request.get_json()
78    action = data.get('action')  # 'lock' or 'unlock'
79    
80    if action == 'lock':
81        security_status['doors'][door_name]['locked'] = True
82    elif action == 'unlock':
83        security_status['doors'][door_name]['locked'] = False
84        security_status['doors'][door_name]['last_opened'] = 'just now'
85    
86    event = {
87        'type': f'door_{action}',
88        'location': door_name,
89        'timestamp': datetime.now().isoformat(),
90        'severity': 'low'
91    }
92    security_events.append(event)
93    
94    return jsonify({
95        'success': True,
96        'door_status': security_status['doors'][door_name],
97        'message': f'{door_name} {action}ed successfully'
98    })

Этап 4: Frontend и стилизация (65-75 мин)

🆕 Универсальный базовый template:

 1<!-- templates/base.html -->
 2<!DOCTYPE html>
 3<html lang="ru">
 4<head>
 5    <meta charset="UTF-8">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>{% block title %}IoT Dashboard{% endblock %}</title>
 8    
 9    <!-- Bootstrap CSS для быстрого прототипирования -->
10    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
11    <!-- Font Awesome для иконок -->
12    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
13    <!-- Chart.js для графиков -->
14    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
15    
16    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
17    
18    <style>
19    /* Кастомные стили для IoT дашборда */
20    .dashboard-container {
21        padding: 20px;
22        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
23        min-height: 100vh;
24    }
25    
26    .card {
27        border-radius: 15px;
28        box-shadow: 0 8px 32px rgba(0,0,0,0.1);
29        backdrop-filter: blur(10px);
30        border: 1px solid rgba(255,255,255,0.2);
31        margin-bottom: 20px;
32    }
33    
34    .sensor-value {
35        font-size: 2.5rem;
36        font-weight: bold;
37        color: #2c3e50;
38    }
39    
40    .status-indicator {
41        width: 12px;
42        height: 12px;
43        border-radius: 50%;
44        display: inline-block;
45        margin-right: 8px;
46    }
47    
48    .status-online { background-color: #27ae60; }
49    .status-offline { background-color: #e74c3c; }
50    .status-warning { background-color: #f39c12; }
51    
52    .control-button {
53        border-radius: 25px;
54        padding: 10px 30px;
55        font-weight: bold;
56        transition: all 0.3s ease;
57    }
58    
59    .control-button:hover {
60        transform: translateY(-2px);
61        box-shadow: 0 4px 15px rgba(0,0,0,0.2);
62    }
63    
64    @media (max-width: 768px) {
65        .dashboard-container {
66            padding: 10px;
67        }
68        .sensor-value {
69            font-size: 1.8rem;
70        }
71    }
72    </style>
73</head>
74<body>
75    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
76        <div class="container">
77            <a class="navbar-brand" href="/">
78                <i class="fas fa-home"></i> IoT Dashboard
79            </a>
80            <span class="navbar-text">
81                <span class="status-indicator status-online"></span>
82                System Online
83            </span>
84        </div>
85    </nav>
86    
87    <div class="dashboard-container">
88        {% block content %}{% endblock %}
89    </div>
90    
91    <!-- Bootstrap JS -->
92    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
93    <!-- Кастомный JavaScript -->
94    <script src="{{ url_for('static', filename='js/main.js') }}"></script>
95    
96    {% block scripts %}{% endblock %}
97</body>
98</html>

🆕 Универсальный JavaScript для real-time обновлений:

  1// static/js/main.js
  2class IoTDashboard {
  3    constructor() {
  4        this.updateInterval = 5000; // 5 секунд
  5        this.charts = {};
  6        this.init();
  7    }
  8    
  9    init() {
 10        this.startAutoUpdate();
 11        this.setupEventListeners();
 12        this.showNotification('Dashboard loaded successfully', 'success');
 13    }
 14    
 15    startAutoUpdate() {
 16        setInterval(() => {
 17            this.updateAllData();
 18        }, this.updateInterval);
 19        
 20        // Первоначальная загрузка
 21        this.updateAllData();
 22    }
 23    
 24    async updateAllData() {
 25        try {
 26            // Обновление основных данных датчиков
 27            const sensorData = await this.fetchData('/api/sensor-data');
 28            this.updateSensorDisplays(sensorData);
 29            
 30            // Обновление графиков если они есть
 31            if (this.charts.main) {
 32                this.updateChart('main', sensorData);
 33            }
 34            
 35            // Обновление статуса подключения
 36            this.updateConnectionStatus(true);
 37            
 38        } catch (error) {
 39            console.error('Error updating data:', error);
 40            this.updateConnectionStatus(false);
 41        }
 42    }
 43    
 44    async fetchData(url) {
 45        const response = await fetch(url);
 46        if (!response.ok) {
 47            throw new Error(`HTTP error! status: ${response.status}`);
 48        }
 49        return await response.json();
 50    }
 51    
 52    updateSensorDisplays(data) {
 53        // Обновление значений датчиков
 54        Object.keys(data).forEach(key => {
 55            const element = document.getElementById(key);
 56            if (element) {
 57                element.textContent = data[key];
 58                
 59                // Добавление анимации при обновлении
 60                element.classList.add('updated');
 61                setTimeout(() => {
 62                    element.classList.remove('updated');
 63                }, 500);
 64            }
 65        });
 66    }
 67    
 68    updateChart(chartId, data) {
 69        const chart = this.charts[chartId];
 70        if (!chart) return;
 71        
 72        const now = new Date().toLocaleTimeString();
 73        
 74        // Добавление новой точки данных
 75        chart.data.labels.push(now);
 76        chart.data.datasets.forEach(dataset => {
 77            const value = data[dataset.dataKey];
 78            if (value !== undefined) {
 79                dataset.data.push(value);
 80            }
 81        });
 82        
 83        // Ограничение количества точек (последние 20)
 84        if (chart.data.labels.length > 20) {
 85            chart.data.labels.shift();
 86            chart.data.datasets.forEach(dataset => {
 87                dataset.data.shift();
 88            });
 89        }
 90        
 91        chart.update('none'); // Обновление без анимации для performance
 92    }
 93    
 94    createChart(canvasId, config) {
 95        const ctx = document.getElementById(canvasId);
 96        if (!ctx) return null;
 97        
 98        const chart = new Chart(ctx, config);
 99        this.charts[canvasId] = chart;
100        return chart;
101    }
102    
103    async controlDevice(endpoint, data) {
104        try {
105            const response = await fetch(endpoint, {
106                method: 'POST',
107                headers: {
108                    'Content-Type': 'application/json',
109                },
110                body: JSON.stringify(data)
111            });
112            
113            const result = await response.json();
114            
115            if (result.success) {
116                this.showNotification('Device controlled successfully', 'success');
117                this.updateAllData(); // Обновление после управления
118            } else {
119                this.showNotification('Device control failed', 'error');
120            }
121            
122            return result;
123        } catch (error) {
124            console.error('Error controlling device:', error);
125            this.showNotification('Connection error', 'error');
126        }
127    }
128    
129    setupEventListeners() {
130        // Обработка кликов по кнопкам управления
131        document.addEventListener('click', (e) => {
132            if (e.target.classList.contains('control-button')) {
133                const action = e.target.dataset.action;
134                const device = e.target.dataset.device;
135                
136                if (action && device) {
137                    this.handleDeviceControl(action, device, e.target);
138                }
139            }
140        });
141        
142        // Обработка изменений в слайдерах
143        document.addEventListener('input', (e) => {
144            if (e.target.classList.contains('control-slider')) {
145                const device = e.target.dataset.device;
146                const value = e.target.value;
147                
148                this.handleSliderControl(device, value);
149            }
150        });
151    }
152    
153    handleDeviceControl(action, device, button) {
154        // Визуальная обратная связь
155        button.disabled = true;
156        button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
157        
158        const endpoint = `/api/${device}`;
159        const data = { action: action };
160        
161        this.controlDevice(endpoint, data).then(() => {
162            // Восстановление кнопки
163            setTimeout(() => {
164                button.disabled = false;
165                button.innerHTML = button.dataset.originalText || action;
166            }, 1000);
167        });
168    }
169    
170    handleSliderControl(device, value) {
171        // Debounce для слайдеров (не отправлять запрос при каждом движении)
172        clearTimeout(this.sliderTimeout);
173        this.sliderTimeout = setTimeout(() => {
174            const endpoint = `/api/${device}`;
175            const data = { value: parseInt(value) };
176            this.controlDevice(endpoint, data);
177        }, 300);
178    }
179    
180    updateConnectionStatus(connected) {
181        const indicators = document.querySelectorAll('.status-indicator');
182        indicators.forEach(indicator => {
183            indicator.className = `status-indicator ${connected ? 'status-online' : 'status-offline'}`;
184        });
185        
186        const statusText = document.querySelector('.navbar-text');
187        if (statusText) {
188            statusText.innerHTML = `
189                <span class="status-indicator ${connected ? 'status-online' : 'status-offline'}"></span>
190                System ${connected ? 'Online' : 'Offline'}
191            `;
192        }
193    }
194    
195    showNotification(message, type = 'info') {
196        // Создание toast уведомления
197        const toast = document.createElement('div');
198        toast.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`;
199        toast.style.cssText = 'top: 20px; right: 20px; z-index: 1050; min-width: 300px;';
200        
201        toast.innerHTML = `
202            ${message}
203            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
204        `;
205        
206        document.body.appendChild(toast);
207        
208        // Автоматическое удаление через 3 секунды
209        setTimeout(() => {
210            if (toast.parentNode) {
211                toast.remove();
212            }
213        }, 3000);
214    }
215}
216
217// Инициализация дашборда при загрузке страницы
218document.addEventListener('DOMContentLoaded', () => {
219    window.dashboard = new IoTDashboard();
220});

🎯 DEMO DAY (75-85 мин): Презентация IoT дашбордов

🆕 Формат: “IoT Web Developers Showcase 2025”

  • Время на студию: 2.5 минуты
  • Live demo: Обязательно показать работающий интерфейс
  • Q&A сессия: 30 секунд

🆕 Структура презентации:

  1. Problem Statement: Какую IoT задачу решает дашборд (30 сек)
  2. Live Demo: Показ работающего интерфейса (90 сек)
  3. Technical Highlights: Интересные технические решения (45 сек)
  4. 🆕 User Experience: Как учтены потребности пользователей (15 сек)

🆕 Критерии оценки от “клиентов”:

  • Usability: Интуитивно понятный интерфейс
  • Visual Appeal: Красивый дизайн
  • Functionality: Работающие features
  • Real-time Updates: Динамическое обновление данных
  • Mobile Friendliness: Работа на телефонах

🔍 UX/UI CLINIC (85-87 мин): Анализ пользовательского опыта

🆕 НОВЫЙ БЛОК

Экспресс-анализ созданных интерфейсов:

 1🎨 UX/UI CHECKLIST ДЛЯ IoT:
 2
 3✅ USABILITY:
 4├── Можно ли за 3 секунды понять текущее состояние системы?
 5├── Ясно ли, какие действия можно выполнить?
 6├── Есть ли обратная связь при управлении устройствами?
 7└── Работает ли на мобильных устройствах?
 8
 9✅ ACCESSIBILITY:
10├── Достаточно ли большие кнопки для touch-интерфейсов?
11├── Контрастны ли цвета для людей с нарушениями зрения?
12├── Есть ли альтернативы цветовой индикации?
13└── Понятны ли иконки без подписей?
14
15✅ PERFORMANCE:
16├── Быстро ли загружается интерфейс?
17├── Не "тормозят" ли real-time обновления?
18├── Graceful degradation при плохом соединении?
19└── Оптимизированы ли изображения и ресурсы?
20
21✅ TRUST & SECURITY:
22├── Видно ли, что система работает корректно?
23├── Есть ли индикация безопасности соединения?
24├── Понятно ли, кто имеет доступ к управлению?
25└── Можно ли отменить/откатить действия?

Быстрый peer review: Команды тестируют интерфейсы друг друга

🔄 SPRINT RETRO (87-90 мин): Рефлексия и планирование

🆕 Создание карты технологий веб-разработки:

1                СЛОЖНОСТЬ ИЗУЧЕНИЯ →
23                ПОЛЕЗНОСТЬ ДЛЯ IoT
4                        
5[Простая, высокая полезность] - HTML/CSS базовый дизайн
6[Простая, средняя полезность] - Bootstrap для быстрого прототипирования
7[Сложная, высокая полезность] - JavaScript + WebSockets для real-time
8[Сложная, средняя полезность] - Продвинутые CSS анимации

🆕 Рефлексивные вопросы:

  1. Что оказалось сложнее - backend логика или frontend интерфейс?
  2. Какая часть создания дашборда была самой интересной?
  3. 🆕 Как бы вы улучшили user experience своего интерфейса?
  4. 🆕 Готовы ли ваши родители пользоваться созданным интерфейсом?
  5. Какие еще возможности хотели бы добавить в дашборд?

📝 Sprint Backlog (🆕 Реальный проект домашнего задания)

🆕 Основное задание: “Personal IoT Developer: Создаю дашборд для собственной комнаты”

Сценарий: Ваши родители согласились выделить бюджет на “умную комнату” при условии, что вы создадите удобный интерфейс управления. Они хотят видеть, что происходит в комнате, и иметь возможность контролировать основные параметры.

🏠 Техническое задание:

 1КОМНАТА: Ваша собственная комната
 2БЮДЖЕТ: $200 на IoT компоненты
 3ПОЛЬЗОВАТЕЛИ: Вы + родители (разные уровни IT-грамотности)
 4
 5ПЛАНИРУЕМЫЕ СИСТЕМЫ:
 6🌡️ КЛИМАТ-КОНТРОЛЬ:
 7├── Датчик температуры и влажности
 8├── Умный вентилятор или обогреватель
 9├── Автоматическое проветривание
10└── Уведомления о некомфортных условиях
11
12💡 ОСВЕЩЕНИЕ:
13├── Умные LED ленты или лампочки
14├── Автоматическое включение по движению
15├── Регулировка яркости и цвета
16└── Режимы: работа, отдых, сон
17
18🔊 РАЗВЛЕЧЕНИЯ:
19├── Управление музыкой/подкастами
20├── Smart TV или проектор integration
21├── Gaming setup automation
22└── Режим "не беспокоить" для учебы
23
24🛡️ БЕЗОПАСНОСТЬ И МОНИТОРИНГ:
25├── Датчик движения для статистики присутствия
26├── Камера или датчик открытия двери
27├── Мониторинг качества воздуха
28└── Уведомления родителям при отсутствии
29
30📊 АНАЛИТИКА:
31├── Статистика времени, проведенного в комнате
32├── Паттерны сна и активности
33├── Энергопотребление умных устройств
34└── Рекомендации по оптимизации
35
36ТРЕБОВАНИЯ К ИНТЕРФЕЙСУ:
37• Интуитивно понятный для родителей
38• Быстрый доступ к основным функциям
39• Красивый дизайн (чтобы не стыдно показать друзьям)
40• Работает на телефонах и планшетах
41• Безопасность (родители не должны видеть личную переписку)

🆕 Задание: Создать полнофункциональный веб-дашборд для управления умной комнатой с учетом реальных ограничений и потребностей

🆕 Структура проекта:

 1📋 ПРОЕКТ: "Умная комната - Web Edition"
 2
 31. ИССЛЕДОВАНИЕ И ПЛАНИРОВАНИЕ:
 4   ├── Анализ существующих решений (Xiaomi, Philips Hue, etc.)
 5   ├── Интервью с потенциальными пользователями (родители, друзья)
 6   ├── Составление списка must-have и nice-to-have функций
 7   └── Создание user personas и user journeys
 8
 92. ТЕХНИЧЕСКАЯ АРХИТЕКТУРА:
10   ├── Выбор и обоснование аппаратной платформы
11   ├── Схема подключения датчиков и актуаторов
12   ├── API design для всех функций
13   └── Database schema для хранения данных
14
153. BACKEND РАЗРАБОТКА:
16   ├── Flask приложение с полным набором endpoints
17   ├── Интеграция с реальными или симулированными датчиками
18   ├── Система аутентификации и авторизации
19   └── Logging и error handling
20
214. FRONTEND РАЗРАБОТКА:
22   ├── Responsive дизайн для всех устройств
23   ├── Real-time обновления и уведомления
24   ├── Интерактивные графики и visualizations
25   └── Progressive Web App функциональность
26
275. UX/UI ДИЗАЙН:
28   ├── Wireframes и mockups интерфейса
29   ├── User testing с реальными пользователями
30   ├── Accessibility compliance
31   └── Performance optimization
32
336. БЕЗОПАСНОСТЬ:
34   ├── HTTPS encryption для всех соединений
35   ├── Input validation и SQL injection protection
36   ├── Rate limiting для API endpoints
37   └── Privacy protection for personal data
38
397. ДЕПЛОЙ И МОНИТОРИНГ:
40   ├── Настройка production-ready окружения
41   ├── Automated backups и recovery procedures
42   ├── Performance monitoring и alerts
43   └── Update и maintenance procedures
44
458. 🆕 ЭКОНОМИЧЕСКОЕ ОБОСНОВАНИЕ:
46   ├── Детальная смета компонентов
47   ├── Сравнение с коммерческими решениями
48   ├── ROI calculation (экономия электричества, удобство)
49   └── Plan монетизации (если планируется продавать)

🆕 Этапы реализации (6 недель):

Неделя 1: Research & Planning

  • Исследование пользователей и конкурентов
  • Техническое планирование и выбор компонентов
  • Создание wireframes интерфейса

Неделя 2: Backend Foundation

  • Базовая Flask архитектура
  • API endpoints для всех функций
  • Database integration

Неделя 3: Hardware Integration

  • Подключение реальных датчиков
  • Тестирование GPIO и I2C коммуникации
  • Error handling для hardware failures

Неделя 4: Frontend Development

  • HTML/CSS/JavaScript интерфейс
  • Real-time data visualization
  • Responsive design implementation

Неделя 5: UX/UI Polish

  • User testing и feedback incorporation
  • Performance optimization
  • Security implementation

Неделя 6: Deployment & Documentation

  • Production deployment на Pi
  • Comprehensive documentation
  • Final presentation preparation

🆕 Критерии оценки проекта:

Критерий Отлично (5) Хорошо (4) Удовлетворительно (3)
Technical Implementation Полнофункциональная система, интеграция с hardware Основные функции работают Базовая функциональность
User Experience Интуитивный, красивый, accessible интерфейс Хороший UX, minor issues Функциональный но не polished
Code Quality Clean architecture, documentation, error handling Readable code, basic structure Working but messy code
Innovation Уникальные features, creative solutions Some interesting additions Standard implementation
🆕 Real-world Applicability Ready for actual deployment and use Minor tweaks needed Concept demonstration

🆕 Бонус-направления:

🤖 AI/ML Integration: Добавить машинное обучение для предсказания ваших предпочтений (когда включать свет, оптимальная температура в зависимости от погоды).

🌐 IoT Cloud Integration: Интеграция с облачными сервисами (Google Home, Alexa, IFTTT) для расширенной функциональности.

📱 Mobile App: Создание companion мобильного приложения с push-уведомлениями и offline functionality.

🔗 Social Features: Возможность делиться интересными данными с друзьями (энергосбережение, оптимальные настройки) с соблюдением privacy.

💼 Business Model: Разработка плана превращения проекта в стартап: анализ рынка, monetization strategy, scaling plan.


📊 Sprint Metrics (🆕 Комплексное оценивание)

🆕 Критерии оценки практической работы:

Критерий Отлично (5) Хорошо (4) Удовлетворительно (3)
Flask Backend Полный REST API, error handling, clean code Основные endpoints, работающая логика Базовая Flask структура
Frontend Quality Professional UI, responsive, interactive Good-looking, mostly responsive Functional but basic styling
Real-time Features Smooth updates, WebSockets/SSE, no lag AJAX updates, minor delays Basic periodic refresh
Hardware Integration Multiple sensors, robust GPIO handling Basic sensor integration Simulated sensor data
🆕 User-Centered Design Intuitive UX, user testing, accessibility Good UX principles applied Functional but not user-focused

🆕 Technical Skills Assessment:

Skill Area Advanced (A) Proficient (P) Developing (D)
Python/Flask Custom decorators, blueprints, advanced features Standard routes, templates, basic OOP Simple scripts, following tutorials
HTML/CSS Semantic markup, CSS Grid/Flexbox, animations Good structure, responsive basics Basic tags and styling
JavaScript ES6+, async/await, classes, modules Functions, DOM manipulation, AJAX Basic scripting, copy-paste solutions
Web Architecture Understanding of full stack, security, performance Frontend-backend separation, APIs Basic request-response cycle

🆕 Формирующее оценивание:

  • Problem-solving approach: Systematic debugging vs trial-and-error
  • Code organization: Structured, documented, reusable code
  • User empathy: Considering end-user needs in design decisions
  • 🆕 Technical communication: Explaining solutions to non-technical peers
  • 🆕 Iterative improvement: Incorporating feedback and refining solutions

🆕 Sprint Badges:

  • 🐍 Python Flask Master - за профессиональную backend разработку
  • 🎨 UI/UX Designer - за excellent пользовательский интерфейс
  • Real-time Specialist - за smooth динамические обновления
  • 🔧 Hardware Integrator - за успешную работу с датчиками
  • 📱 Mobile-First Developer - за отличный responsive design
  • 🛡️ Security Conscious - за implementation security best practices
  • 🏆 Full-Stack Developer - за комплексное владение всем стеком

🎒 Sprint Resources

Необходимое оборудование:

Основное оборудование:

  • Raspberry Pi 4 (по одному на команду, итого 4 штуки)
  • MicroSD карты 32GB+ с Flask pre-installed
  • Блоки питания и кабели
  • Breadboards и соединительные провода
  • 🆕 Starter kit датчиков для каждой команды

🆕 Sensor Kits по специализациям:

 1🔵 BLUE TEAM (Weather Station):
 2├── DHT22 (температура/влажность)
 3├── BMP280 (атмосферное давление)  
 4├── Фоторезистор (освещенность)
 5├── LED для индикации
 6└── Резисторы и провода
 7
 8🔴 RED TEAM (Smart Home):
 9├── Relay module (управление нагрузкой)
10├── RGB LED strip или модуль
11├── PIR motion sensor
12├── Potentiometer (симуляция диммера)
13└── Buzzer для уведомлений
14
15🟢 GREEN TEAM (Garden Monitor):
16├── Soil moisture sensor
17├── Water level sensor (или аналог)
18├── Mini pump (или relay для симуляции)
19├── Temperature sensor (waterproof)
20└── LED grow light simulation
21
22🟡 YELLOW TEAM (Security):
23├── PIR motion sensors (2 шт)
24├── Magnetic door sensor
25├── Camera module (если доступна)
26├── Servo motor (замок симуляция)
27└── LCD display для статуса

Компьютерное оборудование:

  • Мониторы/планшеты для каждой команды
  • USB клавиатуры и мыши
  • HDMI кабели
  • 🆕 Смартфоны/планшеты для тестирования responsive design

Программное обеспечение:

Pre-installed на Pi:

1# Базовый Python stack
2pip3 install flask flask-socketio
3pip3 install RPi.GPIO adafruit-circuitpython-dht
4pip3 install sqlite3 requests
5
6# Дополнительные библиотеки
7pip3 install flask-cors  # для CORS если нужно
8pip3 install gunicorn    # production WSGI server
9pip3 install schedule    # для cron-like задач

🆕 Starter Code Templates:

  • Базовый Flask app template
  • HTML/CSS starter kit с responsive framework
  • JavaScript utilities для AJAX и charts
  • GPIO helper functions
  • Database initialization scripts

🆕 Learning Resources:

 1📚 QUICK REFERENCE GUIDES:
 2
 3🐍 FLASK CHEAT SHEET:
 4├── @app.route('/path', methods=['GET', 'POST'])
 5├── render_template('file.html', var=value)
 6├── request.form['field'] / request.json
 7├── jsonify({'key': 'value'})
 8├── url_for('function_name')
 9└── session['key'] = value
10
11🎨 CSS GRID/FLEXBOX:
12├── display: grid; grid-template-columns: 1fr 1fr;
13├── display: flex; justify-content: center;
14├── @media (max-width: 768px) { ... }
15├── gap: 20px; align-items: center;
16└── flex-wrap: wrap; flex-direction: column;
17
18 JAVASCRIPT ESSENTIALS:
19├── fetch('/api/data').then(r => r.json()).then(data => {...})
20├── document.getElementById('id').innerHTML = value
21├── setInterval(function, milliseconds)
22├── addEventListener('click', function)
23└── JSON.parse(string) / JSON.stringify(object)
24
25🔧 GPIO QUICK START:
26├── GPIO.setmode(GPIO.BCM)
27├── GPIO.setup(pin, GPIO.OUT/GPIO.IN)
28├── GPIO.output(pin, GPIO.HIGH/GPIO.LOW)
29├── GPIO.input(pin)
30└── GPIO.cleanup()

🆕 Troubleshooting Guide

🚨 COMMON ISSUES & SOLUTIONS:

❌ Датчики возвращают None/Error: ✅ Проверить подключение проводов: GPIO.setup() before GPIO.input() ✅ Добавить delay между чтениями: time.sleep(0.1) ✅ Использовать try/except для обработки ошибок ✅ Проверить питание датчиков (3.3V vs 5V)

❌ Веб-интерфейс не обновляется: ✅ Проверить JavaScript console на ошибки (F12) ✅ Убедиться что AJAX endpoint возвращает JSON ✅ Добавить CORS headers: from flask_cors import CORS ✅ Проверить сетевое подключение Pi

❌ Charts.js не отображает графики: ✅ Убедиться что Chart.js загружен: