🌐 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”:
- Учитель открывает готовый дашборд на проекторе (заранее подготовленный)
- Подключает датчик температуры к Raspberry Pi
- Показывает live обновление графика температуры на веб-странице
- 🆕 Демонстрирует управление светодиодом через веб-интерфейс
- Открывает на телефоне - показывает responsive design
- 🆕 Показывает код - “Это всего 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 секунд
🆕 Структура презентации:
- Problem Statement: Какую IoT задачу решает дашборд (30 сек)
- Live Demo: Показ работающего интерфейса (90 сек)
- Technical Highlights: Интересные технические решения (45 сек)
- 🆕 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 СЛОЖНОСТЬ ИЗУЧЕНИЯ →
2 ↑
3 ПОЛЕЗНОСТЬ ДЛЯ IoT
4
5[Простая, высокая полезность] - HTML/CSS базовый дизайн
6[Простая, средняя полезность] - Bootstrap для быстрого прототипирования
7[Сложная, высокая полезность] - JavaScript + WebSockets для real-time
8[Сложная, средняя полезность] - Продвинутые CSS анимации
🆕 Рефлексивные вопросы:
- Что оказалось сложнее - backend логика или frontend интерфейс?
- Какая часть создания дашборда была самой интересной?
- 🆕 Как бы вы улучшили user experience своего интерфейса?
- 🆕 Готовы ли ваши родители пользоваться созданным интерфейсом?
- Какие еще возможности хотели бы добавить в дашборд?
📝 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 загружен: