Категории

sensors.php админка для управления метаданными датчиков

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

про sensors.php

🛠️ sensors.php — Управление метаданными датчиков

Этот файл отвечает за настройку описательной информации о ваших IoT-устройствах: как они называются, в какой комнате находятся, к какой группе относятся и какую иконку показывать в интерфейсе.

Данные хранятся в таблице sensor_info и сопоставляются с топиками из sensor_data. Например:

Интерфейс позволяет:

Это упрощает построение пользовательского интерфейса: вы оперируете не «сырыми» MQTT-топиками, а понятными именами и категориями.

<?php
session_start();

// Защита по IP — раскомментируй, если нужна
// $allowed_ips = ['192.168.0.1', '127.0.0.1'];
// if (!in_array($_SERVER['REMOTE_ADDR'], $allowed_ips)) {
//     header('HTTP/1.1 403 Forbidden');
//     die("Доступ запрещён");
// }

// Авторизация
$valid_username = 'iot_user';
$valid_password = '123456';

$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = $_POST['username'] ?? '';
    $password = $_POST['password'] ?? '';

    if ($username === $valid_username && $password === $valid_password) {
        $_SESSION['iot_admin'] = true;
    } else {
        $error = "Неверный логин или пароль";
    }
}

if (!isset($_SESSION['iot_admin'])) {
    ?>
    <!DOCTYPE html>
    <html lang="ru">
    <head>
        <meta charset="UTF-8">
        <title>🔐 Вход в админку</title>
    </head>
    <body>
        <h2>🔐 Авторизация</h2>
        <?php if ($error): ?>
            <p style="color:red"><?= $error ?></p>
        <?php endif; ?>
        <form method="post">
            <label>Логин<br><input type="text" name="username" required></label><br>
            <label>Пароль<br><input type="password" name="password" required></label><br>
            <button type="submit">Войти</button>
        </form>
    </body>
    </html>
    <?php
    exit;
}
?>

<?php
// Подключение к БД
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);

    // Обработка формы добавления/редактирования
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save'])) {
        $topic = $_POST['topic'] ?? '';
        $name = $_POST['name'] ?? '';
        $room = $_POST['room'] ?? '';
        $group = $_POST['group_name'] ?? '';
        $icon = $_POST['icon'] ?? '';

        if (!empty($topic)) {
            $stmt = $pdo->prepare("
                INSERT INTO sensor_info (topic, name, room, group_name, icon)
                VALUES (?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE
                    name = VALUES(name),
                    room = VALUES(room),
                    group_name = VALUES(group_name),
                    icon = VALUES(icon)
            ");
            $stmt->execute([$topic, $name, $room, $group, $icon]);
        }
    }

    // Получаем список всех топиков из sensor_data
    $stmt = $pdo->query("SELECT DISTINCT topic FROM sensor_data");
    $topics = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);

    // Получаем текущие данные из sensor_info
    $stmt = $pdo->query("SELECT * FROM sensor_info");
    $info = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $infoMap = [];
    foreach ($info as $row) {
        $infoMap[$row['topic']] = $row;
    }

} catch (PDOException $e) {
    die("Ошибка подключения: " . $e->getMessage());
}

// Удаление
if (isset($_GET['delete'])) {
    $topic_to_delete = $_GET['delete'];

    $stmt = $pdo->prepare("DELETE FROM sensor_info WHERE topic = ?");
    $stmt->execute([$topic_to_delete]);

    $stmt = $pdo->prepare("DELETE FROM sensor_data WHERE topic = ?");
    $stmt->execute([$topic_to_delete]);

    $stmt = $pdo->prepare("DELETE FROM sensor_history WHERE topic = ?");
    $stmt->execute([$topic_to_delete]);

    header("Location: sensors.php");
    exit;
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>🛠️ Админка IoT</title>
<style>
    :root {
        --bg-main: #1e1e2e;
        --bg-panel: #252536;
        --bg-input: #2d2d3d;
        --text: #e0e0ff;
        --text-muted: #a0a0c0;
        --border: #3a3a4a;
        --accent: #6c63ff;
        --accent-hover: #5a52d5;
        --success: #4caf50;
        --error: #f44336;
        --warning: #ff9800;
    }

    body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        background-color: var(--bg-main);
        color: var(--text);
        padding: 20px;
        margin: 0;
    }

    h1, h2 {
        color: #ffffff;
        margin-bottom: 20px;
    }

    .form-box {
        background: var(--bg-panel);
        padding: 20px;
        border-radius: 10px;
        margin-bottom: 30px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        border: 1px solid var(--border);
    }

    label {
        display: block;
        margin-top: 14px;
        color: #d0d0e8;
        font-size: 0.95em;
    }

    small {
        display: block;
        color: var(--text-muted);
        font-size: 0.85em;
        margin-top: 4px;
    }

    input, select, textarea, datalist {
        width: 100%;
        padding: 10px;
        margin-top: 6px;
        background: var(--bg-input);
        border: 1px solid var(--border);
        border-radius: 6px;
        color: var(--text);
        font-size: 1em;
    }

    input[type="checkbox"] {
        width: auto;
        margin-right: 8px;
    }

    .schedule-days label {
        display: inline-flex;
        align-items: center;
        margin-right: 16px;
        color: var(--text);
    }

    button, input[type="submit"] {
        padding: 10px 20px;
        background: var(--accent);
        color: white;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        font-weight: 600;
        transition: background 0.2s;
    }

    button:hover, input[type="submit"]:hover {
        background: var(--accent-hover);
    }

    table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 20px;
        background: var(--bg-panel);
        border: 1px solid var(--border);
        border-radius: 8px;
        overflow: hidden;
    }

    th, td {
        padding: 12px;
        border: 1px solid var(--border);
        text-align: left;
        color: var(--text);
    }

    th {
        background: #2a2a3a;
        font-weight: 600;
    }

    .status-enabled {
        color: var(--success);
    }

    .status-disabled {
        color: var(--error);
    }

    .actions a {
        margin-right: 12px;
        text-decoration: none;
        color: var(--accent);
    }

    .actions a:hover {
        text-decoration: underline;
    }

    .message {
        padding: 12px;
        margin: 14px 0;
        border-radius: 6px;
        font-weight: 500;
    }

    .error {
        background: rgba(244, 67, 54, 0.15);
        color: #f8d7da;
        border: 1px solid var(--error);
    }

    .success {
        background: rgba(76, 175, 80, 0.15);
        color: #d4edda;
        border: 1px solid var(--success);
    }

    code {
        background: rgba(0, 0, 0, 0.2);
        padding: 2px 6px;
        border-radius: 4px;
        font-family: monospace;
        color: #a0f0a0;
    }
</style>
</head>
<body>

<h1>🛠️ Админка: Редактирование датчиков</h1>

<p>Здесь можно задать имя, комнату, группу и иконку для каждого топика.</p>

<!-- Форма добавления нового -->
<form method="post">
    <label>Топик:</label><br>
    <input type="text" name="topic" required list="topics"><br><br>

    <label>Имя датчика:</label><br>
    <input type="text" name="name"><br><br>

    <label>Комната:</label><br>
    <input type="text" name="room"><br><br>

    <label>Группа (например: Дом, Дача):</label><br>
    <input type="text" name="group_name"><br><br>

    <label>Иконка (например: 🌡️, 🚪, 🌿):</label><br>
    <input type="text" name="icon"><br><br>

    <input type="submit" name="save" value="Сохранить">
</form>

<!-- Список всех записей -->
<h2>📊 Все датчики</h2>
<table>
    <thead>
        <tr>
            <th>Топик</th>
            <th>Имя</th>
            <th>Комната</th>
            <th>Группа</th>
            <th>Иконка</th>
            <th>Управление</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($topics as $topic):
            $entry = $infoMap[$topic] ?? [];
            $is_status_topic = str_contains($topic, '/status'); // например, sensors/valve/status
        ?>
            <tr>
                <td><?= htmlspecialchars($topic) ?></td>
                <td><?= htmlspecialchars($entry['name'] ?? '') ?></td>
                <td><?= htmlspecialchars($entry['room'] ?? '') ?></td>
                <td><?= htmlspecialchars($entry['group_name'] ?? '') ?></td>
                <td><?= htmlspecialchars($entry['icon'] ?? '') ?></td>
                <td>
                    <a href="#" onclick="fillForm('<?= addslashes($topic) ?>', '<?= addslashes($entry['name'] ?? '') ?>', '<?= addslashes($entry['room'] ?? '') ?>', '<?= addslashes($entry['group_name'] ?? '') ?>', '<?= addslashes($entry['icon'] ?? '') ?>')">✏️ Ред.</a>
                    <a href="?delete=<?= urlencode($topic) ?>" onclick="return confirm('Удалить этот датчик?')">🗑️ Удалить</a>

                    <?php if ($is_status_topic): ?>
                        <button onclick="sendCommand('<?= $topic ?>')">🔄 Переключить</button>
                    <?php endif; ?>
                </td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

<script>
function fillForm(topic, name, room, group, icon) {
    document.querySelector("[name='topic']").value = topic;
    document.querySelector("[name='name']").value = name;
    document.querySelector("[name='room']").value = room;
    document.querySelector("[name='group_name']").value = group;
    document.querySelector("[name='icon']").value = icon;
}

function sendCommand(topic) {
    const setTopic = topic.replace('/status', '/set');
    fetch(`/admin/send_mqtt.php?topic=${encodeURIComponent(setTopic)}&command=toggle`)
        .then(response => response.text())
        .then(data => alert("Команда отправлена"))
        .catch(err => alert("Ошибка: " + err));
}
</script>

<!-- Кнопка выхода -->
<form method="post" action="/admin/logout.php" style="margin-top: 30px;">
    <button type="submit">🚪 Выйти</button>
</form>

</body>
</html>

Комментарии

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

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

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

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

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

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


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

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