↩️ Назад

Категории

sensors.php - как работает панель IoT-датчиков: разбор кода

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

про sensors.php датчики с графиками

обновлено 15.02.2026

Логика работы файла sensors.php

Файл sensors.php отвечает за отображение текущих значений датчиков и подготовку данных для построения графиков на веб-странице. Он работает в связке с двумя таблицами в базе данных:

Основные функции и этапы работы

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

Устанавливается соединение с MySQL через PDO с включённым режимом исключений для отладки.

2. Выборка только тех датчиков, у которых есть история

Выполняется SQL-запрос, который:

Это гарантирует, что на странице отобразятся только датчики с графиками.

3. Определение единицы измерения

По последнему сегменту топика (например, voltage, temperature) определяется единица измерения:

Если тип неизвестен — единица остаётся пустой.

4. Формирование данных для отображения

Для каждого датчика создаётся ассоциативный массив с полями:

5. Группировка по категориям

Все датчики группируются по полю group. Если группа не указана или равна «Без группы», она заменяется на «Общие».

6. Сортировка групп

Группы выводятся в заданном порядке: сначала «Дом», затем «Дача», потом «Общие». Остальные (если появятся) — в конце.

7. Генерация HTML и JavaScript

Для каждой карточки:

При загрузке страницы:

Ключевой принцип

Страница показывает только то, что имеет историю. Это позволяет управлять набором отображаемых датчиков централизованно — через Python-скрипт, который решает, какие топики писать в sensor_history.

Преимущества подхода

<?php
date_default_timezone_set('Europe/Moscow');

try {
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=iot_db;charset=utf8mb4", "iot_user", "Abudfv09540056");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Получаем последние данные ТОЛЬКО для топиков, у которых есть хотя бы одна запись в истории
    $stmt = $pdo->prepare("
        SELECT 
            s.topic,
            s.value,
            s.timestamp as last_time,
            i.name,
            i.room,
            i.group_name,
            i.icon
        FROM sensor_info i
        INNER JOIN sensor_data s ON i.topic = s.topic
        INNER JOIN (
            SELECT topic, MAX(timestamp) as max_time
            FROM sensor_data
            GROUP BY topic
        ) latest ON s.topic = latest.topic AND s.timestamp = latest.max_time
        WHERE i.topic IN (SELECT DISTINCT topic FROM sensor_history)
        ORDER BY i.group_name, i.name
    ");
    $stmt->execute();
    $rows = $stmt->fetchAll();

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

        $unit = match($type) {
            'temperature' => '°C',
            'humidity' => '%',
            'light', 'illuminance' => 'лк',
            'pm25' => 'мкг/м³',
            'co2' => 'ppm',
            'voltage' => 'В',
            'current' => 'А',
            'power' => 'Вт',
            'energy' => 'кВт·ч',
            'battery' => '%',
            'pressure' => 'гПа',
            default => ''
        };

        $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'] ?? '📡'
        ];
    }

    // Группировка
    $groups = [];
    foreach ($topics as $data) {
        $groupName = $data['group'];
        if (empty($groupName) || $groupName === 'Без группы') {
            $groupName = 'Общие';
        }
        if (!isset($groups[$groupName])) $groups[$groupName] = [];
        $groups[$groupName][] = $data;
    }

    $preferredOrder = ['Дом', 'Дача', 'Общие'];
    uksort($groups, function($a, $b) use ($preferredOrder) {
        $posA = array_search($a, $preferredOrder);
        $posB = array_search($b, $preferredOrder);
        if ($posA === false) $posA = 999;
        if ($posB === false) $posB = 999;
        return $posA - $posB;
    });

} catch (Exception $e) {
    die("Ошибка БД: " . htmlspecialchars($e->getMessage()));
}
?>


<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IoT Датчики с графиками</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <link rel="stylesheet" href="/css/style.css">
<style>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-top: 10px; }
.group-section { margin-bottom: 40px; }
        .group-section h2 { color: #cccccc; }
        .grid { display: grid; }
        .value { margin-bottom: 10px; }
        .dannie {font-size: 28px;}
</style>

</head>
<body>
    <h1>📊 Датчики с графиками</h1>

    <?php if (empty($topics)): ?>
        <p>Нет доступных датчиков с графиками.</p>
    <?php else: ?>
        <?php foreach ($groups as $groupName => $groupItems): ?>
            <div class="group-section">
                <h2><?= htmlspecialchars($groupName) ?></h2>
                <div class="grid">
                    <?php foreach ($groupItems as $data): 
                        $safeId = 'sensor-' . base64_encode($data['topic']);
                    ?>
                        <div class="card">
                            <div class="title">
                                <?= $data['icon'] ?>
                                <?= htmlspecialchars($data['name']) ?>
                                <small style="color:#888">(<?= htmlspecialchars($data['room']) ?>)</small>
                            </div>
                            <div class="value"><?= htmlspecialchars($data['value']) ?> <?= htmlspecialchars($data['unit']) ?></div>
                            <div class="timestamp">🕔 <?= htmlspecialchars($data['timestamp']) ?></div>
                            <canvas id="chart-<?= htmlspecialchars($safeId) ?>" height="100"></canvas>
                        </div>
                    <?php endforeach; ?>
                </div>
            </div>
        <?php endforeach; ?>
    <?php endif; ?>

    <script>
        const charts = {};
        document.querySelectorAll('canvas[id^="chart-"]').forEach(canvas => {
            const safeId = canvas.id.replace('chart-', '');
            const topic = atob(safeId.replace('sensor-', ''));

            const chart = new Chart(canvas.getContext('2d'), {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [{
                        label: '',
                        data: [],
                        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 }, y: { beginAtZero: false } },
                    animation: false
                }
            });
            charts[topic] = chart;
            loadChartData(topic);
        });

        function loadChartData(topic) {
            fetch('/api/history.php?topic=' + encodeURIComponent(topic))
                .then(r => r.json())
                .then(data => {
                    if (!Array.isArray(data) || data.length < 2) return;
                    const labels = data.map(p => p.timestamp.split(' ')[1]);
                    const values = data.map(p => parseFloat(p.value)).filter(v => !isNaN(v));
                    if (values.length === 0) return;
                    const chart = charts[topic];
                    if (chart) {
                        chart.data.labels = labels;
                        chart.data.datasets[0].data = values;
                        chart.update('none');
                    }
                });
        }

        function escapeHtml(text) {
            return String(text)
                .replace(/&/g, "&amp;")
                .replace(/</g, "&lt;")
                .replace(/>/g, "&gt;");
        }

        function getSafeId(topic) {
            return 'sensor-' + btoa(topic);
        }

        // Обновление значений каждую минуту
        function updateValues() {
            fetch('/api/live_data.php')
                .then(r => r.json())
                .then(data => {
                    data.topics.forEach(item => {
                        const id = getSafeId(item.topic);
                        const card = document.getElementById(id);
                        if (!card) return;
                        const valueEl = card.querySelector('.value');
                        const timeEl = card.querySelector('.timestamp');
                        if (valueEl) {
                            valueEl.textContent = `${item.value} ${item.unit}`;
                        }
                        if (timeEl) {
                            timeEl.textContent = '🕔 ' + item.timestamp;
                        }
                    });
                })
                .catch(console.error);
        }

        document.addEventListener('DOMContentLoaded', () => {
            updateValues();
            setInterval(updateValues, 60_000);
            setInterval(() => Object.keys(charts).forEach(loadChartData), 60_000);
        });
    </script>
</body>
</html>




Категории:

Категории

Комментарии

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

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

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

Посетителей сегодня: 0
о блоге | карта блога

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