Категории

хомасистент для iot устройств

2025-07-30 20:20:31 | IOT умный дом
Разрабатываю мониторинг и управление iot устройствами умного дома

Введение

Мой хомасистент - это самописная система мониторинга и управления домашними устройствами, построенная на PHP, MySQL и MQTT. В этой статье я подробно разберу как работает каждая часть системы.

Структура системы

Основной файл index.php содержит несколько ключевых функций:

1. Подключение к базе данных

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
try {
    // Создаем подключение к MySQL с использованием PDO
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=iot_db;charset=utf8mb4", 
                  "iot_user", "123456");
    // Устанавливаем режим ошибок - исключения при ошибках
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Использование utf8mb4 важно для корректного хранения эмодзи и других специальных символов.

2. Получение данных датчиков

// SQL запрос для получения последних значений всех датчиков
$stmt = $pdo->query("
    SELECT s.topic, MAX(s.value) AS value, MAX(s.timestamp) as last_time, 
           i.name, i.room, i.group_name, i.icon
    FROM sensor_data s
    LEFT JOIN sensor_info i ON s.topic = i.topic
    GROUP BY s.topic
");
$rows = $stmt->fetchAll();

Этот запрос объединяет таблицы sensor_data (сами данные) и sensor_info (метаинформация о датчиках) и для каждого топика берет максимальное значение (последнее по времени).

3. Обработка и форматирование данных

// Преобразуем сырые данные в удобный для отображения формат
$topics = [];
foreach ($rows as $row) {
    // Определяем тип датчика из топика (последняя часть пути)
    $type = basename($row['topic']);
    
    // Сопоставляем типы датчиков с единицами измерения
    $unit = match($type) {
        'temperature' => '°C',
        'humidity' => '%',
        'light' => 'лк',
        'pm25' => 'мкг/м³',
        'co2' => 'ppm',
        'water_pressure' => 'бар',
        'valve' => 'Вкл/Выкл',
        default => ''
    };
    
    // Фильтруем только нужные типы датчиков
    if (!in_array($type, ['valve', 'temperature', 'humidity', 'light', 'pm25', 'co2'])) {
        continue;
    }
    
    // Формируем массив с данными для отображения
    $topics[] = [
        'topic' => $row['topic'],
        'value' => $row['value'] ?? 'Нет данных',
        'timestamp' => $row['last_time'] ?? '—',
        'name' => $row['name'] ?: ucfirst($type), // Имя из БД или автоматическое
        'room' => $row['room'] ?: 'Неизвестная комната',
        'group' => $row['group_name'] ?: 'Без группы',
        'type' => $type,
        'unit' => $unit,
        'icon' => $row['icon'] ?? '📡' // Иконка по умолчанию
    ];
}

Фронтенд: отображение данных

HTML и JavaScript часть отвечает за:

1. Группировка датчиков

// Группируем датчики по группам для удобного отображения
$groups = [];
foreach ($topics as $data) {
    $groupName = $data['group'];
    if (!isset($groups[$groupName])) {
        $groups[$groupName] = [];
    }
    $groups[$groupName][] = $data;
}

// Сортируем датчики внутри каждой группы по комнатам
foreach ($groups as &$groupItems) {
    usort($groupItems, function ($a, $b) {
        return strcmp($a['room'], $b['room']);
    });
}

2. Карточки устройств

Для каждого устройства генерируется карточка с:

  • Названием и иконкой
  • Текущим значением
  • Временем последнего обновления
  • Элементами управления (для клапанов)
  • Графиком истории (для датчиков)

Пример кода клапана:

<div class="card" id="<?= rawurlencode($data['topic']) ?>">
    <div class="title">
        <?= $data['icon'] ?>
        <?= htmlspecialchars($data['name']) ?>
        <small>(<?= htmlspecialchars($data['room']) ?>)</small>
    </div>
    <div class="value">
        <span class="status-indicator status-<?= strtolower($data['value']) === 'вкл' ? 'on' : 'off' ?>"></span>
        <?= htmlspecialchars($data['value']) ?>
    </div>
    <div class="timestamp">🕒 <?= htmlspecialchars($data['timestamp']) ?></div>
    <div class="controls">
        <button class="btn btn-on" onclick="sendCommand('<?= rawurlencode($data['topic']) ?>', 'ON')">ВКЛ</button>
        <button class="btn btn-off" onclick="sendCommand('<?= rawurlencode($data['topic']) ?>', 'OFF')">ВЫКЛ</button>
    </div>
</div>

JavaScript функционал

1. Управление устройствами

function sendCommand(topic, command) {
    // Заменяем /status на /set в топике для команд
    const setTopic = topic.replace('/status', '/set');
    
    // Формируем payload в зависимости от команды
    let payload = {};
    if (command === 'toggle') {
        payload = { "command": "toggle" };
    } else if (command === 'ON' || command === 'OFF') {
        payload = { "state": command };
    }
    
    // Отправляем команду через API
    fetch(`/admin/send_mqtt.php?topic=${encodeURIComponent(setTopic)}&payload=${encodeURIComponent(JSON.stringify(payload))}`, {
        method: 'GET'
    })
    .then(response => response.text())
    .then(data => alert("Команда отправлена"))
    .catch(error => alert("Ошибка: " + error));
}

2. Отображение графиков

// Для каждого canvas с id начинающимся на "chart-"
document.querySelectorAll('canvas[id^="chart-"]').forEach(canvas => {
    // Получаем топик из id элемента
    const topic = decodeURIComponent(canvas.id.replace('chart-', ''));
    
    // Загружаем исторические данные
    fetch('/api/history.php?topic=' + encodeURIComponent(topic))
        .then(res => res.json())
        .then(data => {
            if (!Array.isArray(data) || data.length === 0) return;
            
            // Подготавливаем данные для графика
            const labels = data.map(p => p.timestamp.split(' ')[1]);
            const values = data.map(p => parseFloat(p.value)).filter(v => !isNaN(v));
            
            // Создаем график с помощью Chart.js
            new Chart(canvas.getContext('2d'), {
                type: 'line',
                data: {
                    labels: labels,
                    datasets: [{
                        data: values,
                        borderColor: '#00c6ff',
                        backgroundColor: 'rgba(0, 198, 255, 0.1)',
                        fill: true,
                        tension: 0.4,
                        pointRadius: 0 // Скрываем точки на линии
                    }]
                },
                options: {
                    responsive: true,
                    plugins: { legend: false }, // Скрываем легенду
                    scales: {
                        x: { display: false }, // Скрываем ось X
                        y: { beginAtZero: false }
                    }
                }
            });
        });
});

3. Погодный модуль

async function getWeather(lat, lon, city) {
    try {
        // Запрашиваем данные с open-meteo API
        const res = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true&hourly=relativehumidity_2m,pressure_msl,visibility,cloudcover&daily=uv_index_max&timezone=auto`);
        const data = await res.json();
        
        // Извлекаем нужные параметры
        const temp = data.current_weather.temperature;
        const wind = data.current_weather.windspeed;
        const weatherCode = data.current_weather.weathercode;
        // ... и другие параметры ...
        
        // Генерируем HTML для отображения погоды
        document.getElementById(`weather-${city.toLowerCase()}`).innerHTML = `
            <div class="weather-header">
                <strong>📍 ${city}</strong> :
                ${getWeatherDescription(weatherCode)} <strong>${temp}°C</strong> |
                💨 Ветер: <strong>${wind} км/ч</strong> |
                // ... остальные параметры ...
            </div>
        `;
    } catch (e) {
        console.error(`Ошибка загрузки (${city}):`, e);
        document.getElementById(`weather-${city.toLowerCase()}`).innerHTML = 
            `⚠️ Ошибка загрузки данных (${city})`;
    }
}

Заключение

Мой хомасистент объединяет данные с различных датчиков и устройств в удобную панель управления с:

  • Автоматической группировкой устройств
  • Графиками изменения параметров
  • Управлением исполнительными устройствами
  • Интеграцией с погодным API
  • Адаптивным дизайном

Система продолжает развиваться - в планах добавление новых типов датчиков, нотификаций и автоматизаций.

🔒 Единая защищенная сеть на WireGuard

Все объекты (квартира, дача, гараж, будущая теплица) объединены в единую виртуальную сеть через WireGuard:

  • Сервер расположен в датацентре в защищенном сегменте
  • Каждый объект имеет свой роутер с постоянным подключением к VPN
  • Нет необходимости в "белых" IP-адресах для видеонаблюдения
  • Доступ ко всем устройствам из любой точки мира

📶 Zigbee-сеть для датчиков и устройств

Используем энергоэффективные Zigbee-устройства:

  • Большинство датчиков работают на батарейках 2+ года
  • Широкий выбор недорогих устройств:
    • Краны для полива
    • Датчики влажности почвы и воздуха
    • Сигнализации и герконы
    • Счетчики электроэнергии
  • Zigbee-концентратор на Raspberry Pi собирает данные и отправляет в датацентр

⭐ Ключевые преимущества системы

  1. Ресурсоэффективность - работает даже на старой Raspberry Pi 1 без нагрузки
  2. Синхронизация - единая панель управления для всех объектов
  3. Безопасность - все данные передаются через зашифрованный VPN-канал
  4. Масштабируемость - легко добавить новые объекты и устройства
  5. Экономия - не требует мощного железа в отличие от OpenHAB/Home Assistant

🔧 Технические особенности

Система построена на:

  • Самописном PHP-скрипте для агрегации данных
  • MySQL для хранения показаний датчиков
  • MQTT для обмена сообщениями с устройствами
  • Chart.js для визуализации графиков
  • Адаптивном веб-интерфейсе с автоматическим обновлением

💡 Админка доступна по запросу - если у вас похожий проект или хотите реализовать подобное, пишите в комментариях!

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

DISCLAIMER: Блог-эксперимент

Внимание: Cтатьи здесь сгенерированы нейросетью, пока не правил ошибки, только запустил его да и не до этого. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
НО!
Каждый кейс я реально делал минимум один раз. Серьёзно.
Сервера стоят, клиенты довольны, дата-центры не горят.
Это не просто копипаста — это опыт, выстраданный в бою, просто пересказанный через ИИ.
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA
Кто я | HSH | Контакты и регион