Мой хомасистент - это самописная система мониторинга и управления домашними устройствами, построенная на PHP, MySQL и MQTT. В этой статье я подробно разберу как работает каждая часть системы.
Основной файл index.php
содержит несколько ключевых функций:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
try {
// Создаем подключение к MySQL с использованием PDO
$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);
Использование utf8mb4
важно для корректного хранения эмодзи и других специальных символов.
// SQL запрос для получения последних значений всех датчиков
$stmt = $pdo->query("
SELECT s.topic, MAX(s.value) AS value, MAX(s.timestamp) as last_time,
i.name, i.room, i.group_name, i.icon
FROM sensor_data s
LEFT JOIN sensor_info i ON s.topic = i.topic
GROUP BY s.topic
");
$rows = $stmt->fetchAll();
Этот запрос объединяет таблицы sensor_data
(сами данные) и sensor_info
(метаинформация о датчиках) и для каждого топика берет максимальное значение (последнее по времени).
// Преобразуем сырые данные в удобный для отображения формат
$topics = [];
foreach ($rows as $row) {
// Определяем тип датчика из топика (последняя часть пути)
$type = basename($row['topic']);
// Сопоставляем типы датчиков с единицами измерения
$unit = match($type) {
'temperature' => '°C',
'humidity' => '%',
'light' => 'лк',
'pm25' => 'мкг/м³',
'co2' => 'ppm',
'water_pressure' => 'бар',
'valve' => 'Вкл/Выкл',
default => ''
};
// Фильтруем только нужные типы датчиков
if (!in_array($type, ['valve', 'temperature', 'humidity', 'light', 'pm25', 'co2'])) {
continue;
}
// Формируем массив с данными для отображения
$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'] ?? '📡' // Иконка по умолчанию
];
}
HTML и JavaScript часть отвечает за:
// Группируем датчики по группам для удобного отображения
$groups = [];
foreach ($topics as $data) {
$groupName = $data['group'];
if (!isset($groups[$groupName])) {
$groups[$groupName] = [];
}
$groups[$groupName][] = $data;
}
// Сортируем датчики внутри каждой группы по комнатам
foreach ($groups as &$groupItems) {
usort($groupItems, function ($a, $b) {
return strcmp($a['room'], $b['room']);
});
}
Для каждого устройства генерируется карточка с:
Пример кода клапана:
<div class="card" id="<?= rawurlencode($data['topic']) ?>">
<div class="title">
<?= $data['icon'] ?>
<?= htmlspecialchars($data['name']) ?>
<small>(<?= htmlspecialchars($data['room']) ?>)</small>
</div>
<div class="value">
<span class="status-indicator status-<?= strtolower($data['value']) === 'вкл' ? 'on' : 'off' ?>"></span>
<?= htmlspecialchars($data['value']) ?>
</div>
<div class="timestamp">🕒 <?= htmlspecialchars($data['timestamp']) ?></div>
<div class="controls">
<button class="btn btn-on" onclick="sendCommand('<?= rawurlencode($data['topic']) ?>', 'ON')">ВКЛ</button>
<button class="btn btn-off" onclick="sendCommand('<?= rawurlencode($data['topic']) ?>', 'OFF')">ВЫКЛ</button>
</div>
</div>
function sendCommand(topic, command) {
// Заменяем /status на /set в топике для команд
const setTopic = topic.replace('/status', '/set');
// Формируем payload в зависимости от команды
let payload = {};
if (command === 'toggle') {
payload = { "command": "toggle" };
} else if (command === 'ON' || command === 'OFF') {
payload = { "state": command };
}
// Отправляем команду через API
fetch(`/admin/send_mqtt.php?topic=${encodeURIComponent(setTopic)}&payload=${encodeURIComponent(JSON.stringify(payload))}`, {
method: 'GET'
})
.then(response => response.text())
.then(data => alert("Команда отправлена"))
.catch(error => alert("Ошибка: " + error));
}
// Для каждого canvas с id начинающимся на "chart-"
document.querySelectorAll('canvas[id^="chart-"]').forEach(canvas => {
// Получаем топик из id элемента
const topic = decodeURIComponent(canvas.id.replace('chart-', ''));
// Загружаем исторические данные
fetch('/api/history.php?topic=' + encodeURIComponent(topic))
.then(res => res.json())
.then(data => {
if (!Array.isArray(data) || data.length === 0) return;
// Подготавливаем данные для графика
const labels = data.map(p => p.timestamp.split(' ')[1]);
const values = data.map(p => parseFloat(p.value)).filter(v => !isNaN(v));
// Создаем график с помощью Chart.js
new Chart(canvas.getContext('2d'), {
type: 'line',
data: {
labels: labels,
datasets: [{
data: values,
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 }, // Скрываем ось X
y: { beginAtZero: false }
}
}
});
});
});
async function getWeather(lat, lon, city) {
try {
// Запрашиваем данные с open-meteo API
const res = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true&hourly=relativehumidity_2m,pressure_msl,visibility,cloudcover&daily=uv_index_max&timezone=auto`);
const data = await res.json();
// Извлекаем нужные параметры
const temp = data.current_weather.temperature;
const wind = data.current_weather.windspeed;
const weatherCode = data.current_weather.weathercode;
// ... и другие параметры ...
// Генерируем HTML для отображения погоды
document.getElementById(`weather-${city.toLowerCase()}`).innerHTML = `
<div class="weather-header">
<strong>📍 ${city}</strong> :
${getWeatherDescription(weatherCode)} <strong>${temp}°C</strong> |
💨 Ветер: <strong>${wind} км/ч</strong> |
// ... остальные параметры ...
</div>
`;
} catch (e) {
console.error(`Ошибка загрузки (${city}):`, e);
document.getElementById(`weather-${city.toLowerCase()}`).innerHTML =
`⚠️ Ошибка загрузки данных (${city})`;
}
}
Мой хомасистент объединяет данные с различных датчиков и устройств в удобную панель управления с:
Система продолжает развиваться - в планах добавление новых типов датчиков, нотификаций и автоматизаций.
Все объекты (квартира, дача, гараж, будущая теплица) объединены в единую виртуальную сеть через WireGuard:
Используем энергоэффективные Zigbee-устройства:
Система построена на:
💡 Админка доступна по запросу - если у вас похожий проект или хотите реализовать подобное, пишите в комментариях!
Внимание: Cтатьи здесь сгенерированы нейросетью, пока не правил ошибки, только запустил его да и не до этого. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
НО!
Каждый кейс я реально делал минимум один раз. Серьёзно.
Сервера стоят, клиенты довольны, дата-центры не горят.
Это не просто копипаста — это опыт, выстраданный в бою, просто пересказанный через ИИ.
Если у вас есть вопросы, или Нашли неточность? пишите в коментах —
вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!