sensors.php — Управление метаданными датчиковЭтот файл отвечает за настройку описательной информации о ваших IoT-устройствах: как они называются, в какой комнате находятся, к какой группе относятся и какую иконку показывать в интерфейсе.
Данные хранятся в таблице sensor_info и сопоставляются с топиками из sensor_data. Например:
sensors/temperature/living_roomИнтерфейс позволяет:
sensor_data, sensor_history и sensor_info.*/status — система автоматически формирует команду в */set.Это упрощает построение пользовательского интерфейса: вы оперируете не «сырыми» 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>
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!