Категории

Автоматические инсайты для умного дома

14.09.2025 17:51 | коды из категории: IOT умный дом

Как я создал умный дом без облаков: от датчиков до автоматических инсайтов

про Автоматические инсайты для умного дома

Многие считают, что «умный дом» — это Alexa, Google Home или платформы вроде Home Assistant. Но настоящий умный дом — это когда он не ждёт команды, а сам говорит: «Здесь проблема». В этой статье — пошаговый гайд, как я сделал систему на Raspberry Pi, которая анализирует данные, выявляет риски и пишет идеи каждые 5 минут — без интернета, без нейросетей, без подписок.

можно и в админку добавить, но если вы шарите то лучше вручную прописывать условия

Цель

Создать локальную систему, которая:

Архитектура системы

Простая схема из четырёх компонентов:

  1. Датчики — Zigbee, DS18B20, INA219, ADS1115
  2. MQTT-брокер — mosquitto на Pi
  3. База данных — MySQL (MariaDB)
  4. Анализатор — PHP-скрипт на сервере

Шаг 1: Таблицы в базе данных

Создадим две таблицы — для данных и для идей.

CREATE TABLE IF NOT EXISTS sensor_data (
    id INT AUTO_INCREMENT PRIMARY KEY,
    topic VARCHAR(255) NOT NULL,
    value TEXT NOT NULL,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS insights (
    id INT AUTO_INCREMENT PRIMARY KEY,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Шаг 2: Таблица правил автоматизации

Таблица automations — хранит все триггеры и расписания.

CREATE TABLE IF NOT EXISTS automations (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL COMMENT 'Название правила',
    trigger_topic VARCHAR(255) NOT NULL COMMENT 'Топик-триггер',
    condition_operator ENUM('>', '<', '==', '!=', '>=', '<=') NOT NULL DEFAULT '==',
    condition_value VARCHAR(50) NOT NULL COMMENT 'Значение для условия',
    condition_duration INT DEFAULT 0 COMMENT 'Условие должно соблюдаться N секунд (0 = мгновенно)',
    action_topic VARCHAR(255) NOT NULL COMMENT 'Топик для действия',
    action_payload TEXT NOT NULL COMMENT 'Payload для отправки',
    schedule_type ENUM('none','daily','weekly') DEFAULT 'none' COMMENT 'Тип расписания',
    enabled TINYINT(1) DEFAULT 1 COMMENT 'Активно ли правило',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_triggered TIMESTAMP NULL,
    failure_count INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Шаг 3: Пример правила в БД

Добавим правило: «Если температура > 45°C — напомни о перегреве».

INSERT INTO automations (
    name,
    trigger_topic,
    condition_operator,
    condition_value,
    schedule_type,
    enabled
) VALUES (
    'Перегрев контактов',
    'sensors/temperature/kitchen',
    '>',
    '45',
    'none',
    1
);

Шаг 4: Скрипт анализа — insights.php

Вот минимальная версия скрипта, который читает данные и генерирует идеи. Он работает на PHP + PDO.

<?php
session_start();
if (!isset($_SESSION['iot_admin'])) {
    header('Location: /admin/sensors.php');
    exit;
}

try {
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=iot_db;charset=utf8mb4", "user", "123456");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Ошибка подключения: " . $e->getMessage());
}

// === Получаем последние данные по ключевым топикам ===
$topics = [
    'sensors/temperature/kitchen',      // температура рядом с контактами
    'sensors/voltage/power_supply',     // напряжение питания
    'sensors/resistance/contact_kitchen', // сопротивление контактов (если есть датчик)
    'sensors/current/water_valve',      // ток через клапан
    'zigbee2mqtt/0x00124b003548b510/battery', // батарейка датчика
];

$data = [];
foreach ($topics as $topic) {
    $stmt = $pdo->prepare("
        SELECT topic, value, timestamp 
        FROM sensor_data 
        WHERE topic = ? 
        ORDER BY timestamp DESC 
        LIMIT 1
    ");
    $stmt->execute([$topic]);
    if ($row = $stmt->fetch()) {
        $data[$topic] = [
            'value' => $row['value'],
            'timestamp' => $row['timestamp']
        ];
    } else {
        $data[$topic] = ['value' => null, 'timestamp' => '—'];
    }
}

// === Формируем “инсайт” на основе правил ===
$insights = [];

// 🔥 ПОЖАР / ПЕРЕГРЕВ
if (
    isset($data['sensors/temperature/kitchen']['value']) &&
    $data['sensors/temperature/kitchen']['value'] > 45
) {
    $insights[] = "⚠️ **Перегрев!** Температура у контактов: {$data['sensors/temperature/kitchen']['value']}°C — возможно, плохой контакт или короткое замыкание.";
}

// ⚡ ПЛОХОЙ КОНТАКТ — РОСТ СОПРОТИВЛЕНИЯ
if (
    isset($data['sensors/resistance/contact_kitchen']['value']) &&
    $data['sensors/resistance/contact_kitchen']['value'] > 50 // Ом — пример для плохого контакта
) {
    $insights[] = "⚠️ **Плохой контакт!** Сопротивление: {$data['sensors/resistance/contact_kitchen']['value']} Ом — выше нормы (обычно < 5 Ом). Проверьте соединения!";
}

// 🔋 БАТАРЕЙКА НИЗКАЯ
if (
    isset($data['zigbee2mqtt/0x00124b003548b510/battery']['value']) &&
    $data['zigbee2mqtt/0x00124b003548b510/battery']['value'] < 20
) {
    $insights[] = "🔋 **Низкий заряд батареи:** {$data['zigbee2mqtt/0x00124b003548b510/battery']['value']}%. Замените батарейку — датчик может отключиться.";
}

// ⚠️ НАПРЯЖЕНИЕ ПАДАЕТ
if (
    isset($data['sensors/voltage/power_supply']['value']) &&
    $data['sensors/voltage/power_supply']['value'] < 10
) {
    $insights[] = "⚡ **Низкое напряжение:** {$data['sensors/voltage/power_supply']['value']} В — возможно, проблема с блоком питания или перегрузка сети.";
}

// 💧 ПРИСУТСТВИЕ ТОКА ПРИ ЗАКРЫТОМ КРАНЕ
if (
    isset($data['sensors/current/water_valve']['value']) &&
    $data['sensors/current/water_valve']['value'] > 0.1 // 100 мА при закрытом клапане — плохо
) {
    $insights[] = "💧 **Ток при закрытом кране:** {$data['sensors/current/water_valve']['value']} А — возможна утечка или залипание контакта клапана.";
}

// === Генерация "идеи" раз в 5 минут ===
$last_insight_time = null;
$stmt = $pdo->query("SELECT MAX(created_at) as last FROM insights ORDER BY created_at DESC LIMIT 1");
if ($row = $stmt->fetch()) {
    $last_insight_time = $row['last'];
}

$now = new DateTime();
$five_min_ago = (new DateTime())->modify('-5 minutes');

$should_generate = !$last_insight_time || $last_insight_time < $five_min_ago->format('Y-m-d H:i:s');

// Если пора — генерим новую идею
if ($should_generate && !empty($insights)) {
    $idea = "🔧 Автоматический анализ системы:\n";
    foreach ($insights as $i) {
        $idea .= "- " . preg_replace('/<[^>]*>/', '', $i) . "\n"; // чистим HTML
    }

    $stmt = $pdo->prepare("INSERT INTO insights (content, created_at) VALUES (?, NOW())");
    $stmt->execute([$idea]);

    $insights[] = "🧠 **Новая идея от системы:**\n" . $idea;
}

// === Получаем последние 10 идей ===
$stmt = $pdo->query("SELECT content, created_at FROM insights ORDER BY created_at DESC LIMIT 10");
$history = $stmt->fetchAll(PDO::FETCH_ASSOC);

?>

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>🧠 Умный анализ — Идеи для тебя</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; background: #121212; color: #fff; padding: 30px; }
        h1 { color: #00c6ff; text-align: center; margin-bottom: 40px; }
        .card { background: #1f1f1f; padding: 20px; border-radius: 10px; margin-bottom: 20px; border-left: 4px solid #00c6ff; }
        .alert { background: #2c1a1a; border-left: 4px solid #e74c3c; padding: 15px; margin: 15px 0; }
        .info { background: #1a2c2c; border-left: 4px solid #00c6ff; padding: 15px; margin: 15px 0; }
        code { background: #333; padding: 3px 6px; border-radius: 4px; }
        .btn { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px; }
        .btn:hover { background: #0056b3; }
        .timestamp { color: #aaa; font-size: 0.9em; margin-top: 10px; }
        pre { background: #000; padding: 15px; border-radius: 8px; overflow-x: auto; white-space: pre-wrap; }
    </style>
</head>
<body>

<h1>🧠 Умный анализ — Идеи для тебя</h1>

<div class="card">
    <h2>🔥 Активные риски (обнаружено)</h2>
    <?php if (empty($insights)): ?>
        <p style="color: #888;">Все в порядке — рисков не обнаружено.</p>
    <?php else: ?>
        <?php foreach ($insights as $insight): ?>
            <?php if (strpos($insight, 'Новая идея') !== false): ?>
                <div class="info">
                    <?= nl2br(htmlspecialchars($insight)) ?>
                </div>
            <?php else: ?>
                <div class="alert">
                    <?= $insight ?>
                </div>
            <?php endif; ?>
        <?php endforeach; ?>
    <?php endif; ?>
</div>

<div class="card">
    <h2>📜 История идей (последние 10)</h2>
    <?php if (empty($history)): ?>
        <p style="color: #888;">Пока нет исторических идей.</p>
    <?php else: ?>
        <?php foreach ($history as $item): ?>
            <div style="margin-bottom: 15px;">
                <pre><?= htmlspecialchars($item['content']) ?></pre>
                <div class="timestamp">📅 <?= htmlspecialchars($item['created_at']) ?></div>
            </div>
        <?php endforeach; ?>
    <?php endif; ?>
</div>

<button class="btn" onclick="location.href='/admin/insights.php?refresh=1'">🔄 Обновить</button>

<form method="post" action="/admin/logout.php" style="margin-top: 40px;">
    <button type="submit">🚪 Выйти</button>
</form>

<script>
    setTimeout(() => {
        location.reload();
    }, 300000); // обновлять каждые 5 минут
</script>

</body>
</html>

Шаг 5: Как отправить тестовые данные

Чтобы протестировать систему — отправь MQTT-сообщения вручную:

mosquitto_pub -t "sensors/temperature/kitchen" -m "52"
mosquitto_pub -t "sensors/resistance/contact_kitchen" -m "65"
mosquitto_pub -t "zigbee2mqtt/0x00124b003548b510/battery" -m "15"
mosquitto_pub -t "sensors/current/water_valve" -m "0.15"

После этого открой http://raspberrypi/admin/insights.php — ты увидишь предупреждения.

Шаг 6: Настройка автообновления

Чтобы страница обновлялась каждые 5 минут — добавь в конец файла insights.php:

Результат

Система теперь выводит:

⚠️ Перегрев! Температура у контактов: 52°C — возможно, плохой контакт.
⚠️ Плохой контакт! Сопротивление: 65 Ом — выше нормы (<5 Ом).
🔋 Низкий заряд батареи: 15%. Замените батарейку.

🧠 Новая идея от системы:
🔧 Автоматический анализ системы:
- Перегрев! Температура у контактов: 52°C — возможно, плохой контакт.
- Плохой контакт! Сопротивление: 65 Ом — выше нормы (<5 Ом).
- Низкий заряд батареи: 15%. Замените батарейку.

Почему это лучше, чем ChatGPT?

Итог

Ты не нуждаешься в нейросетях, чтобы предсказать пожар. Тебе нужно знать:

Это не AI. Это инженерное мышление. И оно работает лучше, чем любая LLM, потому что оно знает контекст твоего дома — а не обучено на общих данных.

Теперь мой дом не просто «умный» — он заботливый.

Теги: #linux #PHP #MySQL #MQTT #IoT #DIY #automation #smarthome #raspberrypi

Комментарии

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

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

← Назад к списку

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

Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

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


кто я | книга | контакты без контактов

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