Категории

live_data.php скрипт получения актуальных данных для обновления данных без перезагрузки страницы

25.11.2025 20:15 | коды из категории: IOT умный дом

про live_data.php API-эндпоинт мониторинга IoT-системы

API-эндпоинт мониторинга IoT-системы

Этот PHP-скрипт служит централизованным API-интерфейсом для получения актуальных данных от умной IoT-системы на базе Raspberry Pi, Zigbee-датчиков и MQTT-брокера. Он агрегирует информацию из базы данных и ОС, формируя структурированный JSON-ответ, который используется веб-интерфейсом для отображения состояния системы в реальном времени.

Что делает скрипт:

Для чего это нужно?

Скрипт позволяет создать единый информационный дашборд для домашней IoT-инфраструктуры: оперативно видеть состояние датчиков, отслеживать работу автоматизаций, замечать аномалии и контролировать здоровье сервера — всё через один HTTP-запрос.

Технические детали:

Этот эндпоинт — ядро веб-интерфейса умного дома, обеспечивающее прозрачность и контроль над распределённой IoT-системой.

<?php
date_default_timezone_set('Europe/Moscow');
header('Content-Type: application/json; charset=utf8mb4');

// Отключаем отладку — чтобы не засорять JSON
error_reporting(0);
ini_set('display_errors', 0);

try {
    $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);

    // === 1. Последние данные датчиков ===
    $stmt = $pdo->query("
        SELECT s1.topic, s1.value, s1.timestamp as last_time, i.name, i.room, i.group_name, i.icon
        FROM sensor_data s1
        LEFT JOIN sensor_info i ON s1.topic = i.topic
        WHERE s1.timestamp = (
            SELECT MAX(s2.timestamp)
            FROM sensor_data s2
            WHERE s2.topic = s1.topic
        )
        GROUP BY s1.topic
    ");
    $rows = $stmt->fetchAll();

    $topics = [];
    foreach ($rows as $row) {
        $type = basename($row['topic']);
        if ($type === 'status') $type = 'valve';

        $unit = match($type) {
            'temperature' => '°C',
            'humidity' => '%',
            'light' => 'лк',
            'pm25' => 'мкг/м³',
            'co2' => 'ppm',
            'water_pressure' => 'бар',
            'valve' => 'Вкл/Выкл',
            'current' => 'мА',
   'illuminance' => 'лк',
    'presence' => '',
    'battery' => '%',
    'voltage' => 'mV',


            default => ''
        };

        if (!in_array($type, [
            'valve', 'temperature', 'humidity', 'light', 'pm25', 'co2', 'status',
            'voltage', 'current', 'power', 'contact', 'vibration','illuminance', 'presence', 'battery'
        ])) continue;


$display_value = $row['value'] ?? 'Нет данных';
if ($type === 'presence') {
    $is_presence = ($display_value == 'true' || $display_value == '1' || strtolower($display_value) === 'on');
    $display_value = $is_presence ? '🚶 Движение' : '❌ Тихо';
}


        $topics[] = [
            'topic' => $row['topic'],
'value' => $display_value,
          //  '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'] ?? '📡'
        ];
    }

    // === 2. Статус последнего и следующего правила ===
    $last_executed_rule = null;
    $stmt_last = $pdo->prepare("
        SELECT name, last_triggered, action_topic, action_payload
        FROM automations
        WHERE last_triggered IS NOT NULL
        ORDER BY last_triggered DESC
        LIMIT 1
    ");
    $stmt_last->execute();
    $last_row = $stmt_last->fetch(PDO::FETCH_ASSOC);
    if ($last_row) {
        $payload = json_decode($last_row['action_payload'], true);
        $last_executed_rule = [
            'name' => $last_row['name'],
            'last_triggered' => $last_row['last_triggered'],
            'action_topic' => $last_row['action_topic'],
            'payload_str' => is_array($payload)
                ? implode(', ', array_map(fn($k, $v) => "$k: $v", array_keys($payload), array_values($payload)))
                : $last_row['action_payload']
        ];
    }

    // Проверка ежедневного сброса
    $daily_reset_today = false;
    $stmt_check = $pdo->prepare("SELECT 1 FROM daily_resets WHERE reset_date = ?");
    $stmt_check->execute([date('Y-m-d')]);
    $daily_reset_today = $stmt_check->fetch() !== false;

    // === 3. Ближайшая запланированная задача ===
    $next_scheduled = null;
    $stmt_tasks = $pdo->prepare("
        SELECT
            id, name, schedule_type, schedule_time, schedule_days,
            action_topic, action_payload, delay_seconds
        FROM automations
        WHERE enabled = 1 AND schedule_type != 'none'
        ORDER BY created_at
    ");
    $stmt_tasks->execute();
    $tasks = $stmt_tasks->fetchAll(PDO::FETCH_ASSOC);

    $upcoming = [];
    foreach ($tasks as $task) {
        $now = new DateTime();
        $next_run = null;

        if ($task['schedule_type'] === 'daily') {
            $run_time = new DateTime($task['schedule_time']);
            $run_time->setDate($now->format('Y'), $now->format('m'), $now->format('d'));
            if ($run_time < $now) $run_time->modify('+1 day');
            $next_run = $run_time;
        } elseif ($task['schedule_type'] === 'weekly') {
            $days_map = ['mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6, 'sun' => 0];
            $scheduled_days = explode(',', $task['schedule_days'] ?? '');
            foreach ($scheduled_days as $day) {
                if (!isset($days_map[$day])) continue;
                $day_num = $days_map[$day];
                $current_day = (int)$now->format('N');
                $days_ahead = ($day_num - $current_day + 7) % 7;
                if ($days_ahead == 0 && $now->format('H:i:s') >= $task['schedule_time']) {
                    $days_ahead = 7;
                }
                $candidate = clone $now;
                $candidate->modify("+{$days_ahead} days");
                $candidate->setTime(
                    (int)substr($task['schedule_time'], 0, 2),
                    (int)substr($task['schedule_time'], 3, 2),
                    (int)substr($task['schedule_time'], 6, 2)
                );
                if (!$next_run || $candidate < $next_run) {
                    $next_run = $candidate;
                }
            }
        }

        if ($next_run) {
            $upcoming[] = [
                'name' => $task['name'],
                'next_run' => $next_run->format('Y-m-d H:i:s'),
                'time' => $task['schedule_time'],
                'delay' => (int)$task['delay_seconds']
            ];
        }
    }

    if (!empty($upcoming)) {
        usort($upcoming, fn($a, $b) => strtotime($a['next_run']) <=> strtotime($b['next_run']));
        $next_scheduled = $upcoming[0];
    }

    // === 4. Тренды (тревоги) ===
    $trend_alerts = [];
    $monitor_topics = [
        'zigbee2mqtt/0x00124b003548b510/temperature',
        'zigbee2mqtt/bedroom_sensor/temperature',
        'zigbee2mqtt/0x00124b003548b510/battery',
        'zigbee2mqtt/bedroom_sensor/battery',
        'zigbee2mqtt/0xa4c1385d1ba39457/voltage',
        'zigbee2mqtt/0x00124b003548b510/voltage',
        'zigbee2mqtt/bedroom_sensor/voltage'
    ];

    foreach ($monitor_topics as $topic) {
        $stmt_hist = $pdo->prepare("
            SELECT value, timestamp
            FROM sensor_history
            WHERE topic = ?
            ORDER BY timestamp DESC
            LIMIT 5
        ");
        $stmt_hist->execute([$topic]);
        $history = $stmt_hist->fetchAll(PDO::FETCH_ASSOC);
        if (count($history) < 2) continue;

        $latest = $history[0];
        $oldest = $history[count($history) - 1];
        $current_value = floatval($latest['value']);
        $past_value = floatval($oldest['value']);
        $time_diff_sec = strtotime($latest['timestamp']) - strtotime($oldest['timestamp']);
        if ($time_diff_sec <= 0) continue;
        $rate_per_min = ($current_value - $past_value) / ($time_diff_sec / 60);
        $minutes = round($time_diff_sec / 60);

        $alert = null;
        if (strpos($topic, 'temperature') !== false && $rate_per_min > 0.6) {
            $alert = "🔥 Резкий рост температуры: {$past_value}°C → {$current_value}°C за {$minutes} мин (" . number_format($rate_per_min, 1) . "°C/мин)";
        } elseif (strpos($topic, 'voltage') !== false && str_contains($topic, 'bedroom') && $rate_per_min < -0.1) {
            $alert = "⚡ Напряжение упало: {$past_value}V → {$current_value}V";
        } elseif (strpos($topic, 'battery') !== false && $rate_per_min < -1.0) {
            $alert = "🔋 Батарея быстро разряжается: {$past_value}% → {$current_value}%";
        } elseif ($topic === 'zigbee2mqtt/0xa4c1385d1ba39457/voltage') {
            if ($current_value < 200) {
                $alert = "⚡ Критическое падение напряжения: {$current_value}V — возможна авария!";
            } elseif ($rate_per_min < -10) {
                $alert = "⚡ Напряжение резко упало: {$past_value}V → {$current_value}V";
            }
        }

        if ($alert) {
            $trend_alerts[] = ['message' => $alert];
        }
    }

    // === 5. Системное время ===
    $current_time = date('Y-m-d H:i:s');


// === 6. Данные RPi ===
$stmt_rpi = $pdo->query("SELECT * FROM rpi_monitor ORDER BY timestamp DESC LIMIT 1");
$rpi_data = $stmt_rpi->fetch(PDO::FETCH_ASSOC);

// === 7. Статусы сервисов ===
$services_to_check = ['iot-automation.service', 'mqtt-to-mysql.service', 'zigbee2mqtt.service', 'iot-mqtt-listener.service', 'mosquitto.service'];
$service_statuses = [];
foreach ($services_to_check as $service) {
    $status = trim(shell_exec("systemctl is-active " . escapeshellarg($service) . " 2>&1"));
    $is_active = ($status === 'active');
    $service_statuses[] = [
        'name' => $service,
        'status' => $status,
        'is_active' => $is_active,
        'color' => $is_active ? 'ok' : 'critical'
    ];
}

// === 8. Статистика логов ===
$log_stats = [];
$stmt_log = $pdo->prepare("SELECT keyword, count FROM log_stats WHERE date = ?");
$stmt_log->execute([date('Y-m-d')]);
foreach ($stmt_log->fetchAll(PDO::FETCH_ASSOC) as $row) {
    $log_stats[$row['keyword']] = (int)$row['count'];
}




    // === Ответ ===
    echo json_encode([
        'topics' => $topics,
        'last_executed_rule' => $last_executed_rule,
        'next_scheduled' => $next_scheduled,
        'daily_reset_today' => $daily_reset_today,
        'trend_alerts' => $trend_alerts,
    'rpi_data' => $rpi_data,
    'service_statuses' => $service_statuses,
    'log_stats' => $log_stats,
       'current_time' => $current_time
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => 'Ошибка сервера: ' . $e->getMessage()]);
}

Комментарии

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

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

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

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

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

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


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

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