Умный дом давно перебрался из выставочных стендов в обычные квартиры: датчики климата, реле, термостаты и умные розетки продаются в любом магазине электроники. Владелец дома хочет не крутить настройки, а одним взглядом понимать, что происходит дома: какая температура в детской, не пересохла ли рассада на балконе, работает ли обогрев гаража. Дашборд в браузере закрывает эту потребность без установки приложений: страница открывается на планшете у входа или на телефоне в отъезде, и картина дома всегда под рукой.
За аккуратной плиткой со стрелкой тренда стоит цепочка: радиопротокол, шлюз, брокер сообщений, сервер, канал в браузер, график. Дальше эта цепочка разобрана по звеньям, с кодом, именами протоколов и числами, чтобы собрать собственный веб-дашборд или осмысленно настроить готовый.
Путь телеметрии от радиодатчика до браузерной страницы по цепочке звеньев
Типичный температурный датчик не умеет говорить с браузером напрямую: у него батарейка, радио и экономия каждого джоуля. Путь показаний всегда многоступенчатый: датчик шлёт значения по радио Zigbee или Wi-Fi, шлюз публикует их брокеру MQTT, сервер подписан на нужные топики и раздаёт браузеру по WebSocket. За каждым звеном стоит своя логика: Zigbee держит батарейные датчики годами на одном элементе питания, Wi-Fi берёт скорость и вычислительную мощь у домашней розетки, а прошивка ESPHome оживляет дешёвые платы ESP32 и ESP8266, и те публикуют телеметрию сразу в MQTT, минуя лишних посредников. Платформа Zigbee2MQTT переводит радиосеть Zigbee на язык топиков брокера и поддерживает тысячи моделей устройств, поэтому редкий датчик остаётся за бортом. Устройства на розеточном питании с прошивкой Tasmota умеют то же самое без шлюза вообще. Главное правило звучит просто: браузер никогда не лезет в радио напрямую, он всегда читает уже собранную и опубликованную телеметрию. Выбор между локальным и облачным контуром висит на шлюзе: локальный не зависит от провайдера, облачный удобнее для доступа, зрелая схема держит локальный контур основным, а наружу выпускает только читателя.
Шлюз в этой цепочке не просто переводчик с радио на брокер. Он держит сеть устройств, хранит их состояние и переживает пропажу интернета: автоматика продолжает работать, даже когда страница недоступна. За автономность шлюза и платят, потому что умный дом, засыпающий вместе с роутером провайдера, умным не считается.
MQTT топики QoS и завещание брокера как язык общения устройств и сайта
MQTT это протокол публикации и подписки с брокером в центре. Устройства публикуют сообщения в топики, пути со слешами вроде home/livingroom/temperature, а сервер дашборда подписывается на нужные маски, например home/+/temperature, где плюс подменяет один уровень. Гарантии передачи регулируются уровнями QoS: нулевой шлёт сообщение не более одного раза и годится для потока телеметрии, первый доставляет минимум один раз и уместен для команд переключения, второй гарантирует ровно одну передачу и почти не нужен в доме. Флаг retain велит брокеру хранить последнее сообщение топика и сразу выдавать его новому подписчику, поэтому плитка показывает значение сразу после загрузки, а не ждёт следующего цикла датчика. Особый подарок это завещание, Last Will: клиент при подключении оставляет сообщение на случай внезапной потери связи, и брокер публикует его сам, благодаря чему дашборд честно показывает датчик оффлайн, а не зависшее старое значение.
Схема топиков заслуживает проектирования заранее. Рабочий порядок такой: по каждому устройству два топика, state для фактического состояния и set для команд, а слева направо читается иерархия: home, комната, тип, имя устройства. Полезная нагрузка в JSON с полем времени и значением читается и людьми, и машинами, а единая схема делает дашборд предсказуемым: новая лампочка в детской ложится на плитку без новой строки кода, маска подписки уже накрывает её путь.
Подключение из браузера идёт по WebSocket поверх TLS, у брокера Mosquitto для этого отдельный слушатель, обычно на порту 9001:
import mqtt from "mqtt";
const client = mqtt.connect("wss://broker.example.net:9001", {
username: "dash",
password: "w7xQp2m",
reconnectPeriod: 2000
});
client.subscribe("home/+/temperature");
client.on("message", (topic, payload) => {
updateTile(topic, JSON.parse(payload.toString()));
});
WebSocket и SSE против опроса в передаче живых показаний на дашборд
Способов доставить значение в браузер три, и у каждого свой характер. Опрос по таймеру это запрос раз в несколько секунд: просто, но баланс между мусорным трафиком и свежестью не сходится, а между опросами плитка молча врёт старым значением. Server-Sent Events держат однонаправленный канал от сервера: браузерный EventSource подключается одной строкой, сервер лишь толкает события, а переподключение встроено в сам API. WebSocket это полный дуплекс: сервер шлёт телеметрию, страница отдаёт команды, один канал закрывает все потребности. Практичная гибридная схема: поток измерений идёт через MQTT поверх WebSocket или через SSE, команды управления через тот же канал в обратную сторону. Переподключение обязательно с экспоненциальной паузой, иначе тысяча открытых страниц после сбоя сети ударит по брокеру одновременно и положит его на полминуты. И маленькая вежливость к батареям: когда вкладка не видна, поток разумно притормозить до всплытия, невидимому дашборду свежие цифры не нужны.
Экономия байтов здесь не жадность, а гигиена. Сообщение с одной температурой в JSON весит десятки байтов, поток десятка датчиков с минутным интервалом скромен, но при открытии страницы каждая крупица ложится на канал. Разумно обновлять только изменившиеся плитки, слать дельту, а не весь массив, и держать графики ленивыми, чтобы экран с двумя сотнями плиток не дёргался целиком из-за одного датчика.
Хранение истории измерений и агрегация рядов для графиков без тормозов
Для графиков нужен склад истории, и обычная реляционная таблица с наивным индексом быстро выдыхается на миллионах точек. Специализированные хранилища рядов решают это из коробки: TimescaleDB расширяет PostgreSQL гипертаблицами с автосегментацией по времени, InfluxDB держит собственную модель измерений, и обе умеют сжимать старое и подчищать его политиками хранения. Правило агрегации продиктовано здравым смыслом: сырые точки за последний день, пятиминутные средние за неделю, часовые за год, и запрос на длинном диапазоне трогает тысячи строк вместо миллионов.
Цена вопроса смешная: год показаний десяти датчиков с минутным интервалом это около пяти миллионов точек, единицы гигабайт даже без сжатия, а политики хранения и компрессия урезают объём в разы. Дороже обходится отсутствие истории: без неё нет ни графиков, ни понимания, сколько реально работал обогрев, ни честной стрелки тренда. Два самых частых запроса это последнее значение и час спарклайна, оба обязаны отвечать за миллисекунды, поэтому индексы по времени и устройству закладывают сразу.
В TimescaleDB агрегация выглядит так:
SELECT time_bucket('5 minutes', ts) AS bucket,
avg(temperature) AS t
FROM readings
WHERE device = 'livingroom'
AND ts > now() - interval '7 days'
GROUP BY bucket
ORDER BY bucket;
Для отрисовки хватает зрелых библиотек вроде Chart.js и Apache ECharts, а спарклайны на плитках разумно рисовать на SVG вообще без зависимостей: двадцать точек линии не заслуживают мегабайта графики.
Содержание плиток дашборда от текущего значения до стрелки тренда и нормы
Содержание плитки важнее её красоты. Хорошая плитка отвечает на три вопроса без единого клика: что это за датчик, сколько сейчас, всё ли в норме. Отсюда обязательный набор: название с комнатой, крупное текущее значение, диапазон нормы, стрелка тренда за последний час, время последнего обновления и цвет состояния, где зелёный означает норму, жёлтый отклонение, красный событие, требующее взгляда. Плитки группируют по комнатам, а сценарии вроде кино или ночного режима выносят отдельной панелью, чтобы управление не пряталось в глубине. Прогрессивное раскрытие спасает от шума: плитка показывает главное, тап раскрывает график за сутки и настройки. Для кухонного планшета с занятыми руками правила суровые: крупные цели касания, высокий контраст, шрифт, читаемый с пары шагов, потому что дашборд умного дома чаще всего читают мельком и на ходу, а ночью выручает тёмная тема.
Отдельный слой содержания это оповещения. Значение вне нормы дольше получаса должно само найти владельца, а не ждать его визита на страницу: уведомление о протечке теряет смысл к утру. Отсюда дополнительный вопрос к содержанию: что должно случиться, когда значение уйдёт из нормы. А над всеми плитками держится сводка по дому одной строкой, всё ли зелёное и где отклонение, чтобы заходить глубже только по необходимости.
Управление устройствами с мгновенным откликом и честным откатом при таймауте
Нажатие должно ощущаться мгновенным, и у интерфейсов есть старое правило: реакция до десятой доли секунды воспринимается как отклик, до секунды как заметная задержка. Радио, брокер и устройство в цепочку не всегда успевают за сотню миллисекунд, поэтому честнее схема optimistic UI: плитка перекрашивается сразу в момент клика, команда уходит с QoS 1, а сервер слушает state-топик устройства и сверяет фактическое состояние. Если за три секунды подтверждение не пришло, интерфейс откатывает плитку и сообщает о проблеме, вместо фантомного включения. Команды лучше посылать целевым состоянием on и off, а не переключением: повторная передача тогда не выключит свет, который только что включили, и идемпотентность спасает ровно там, где радио теряет пакеты.
async function setRelay(deviceId, target) {
const tile = optimisticFlip(deviceId, target);
client.publish(`home/${deviceId}/set`, target, { qos: 1 });
const confirmed = await waitForState(deviceId, target, 3000);
if (!confirmed) rollbackTile(tile);
}
Когда управлением пользуются несколько человек, побеждает не тот, кто нажал последним, а правда из state-топика: устройство само подтверждает финальное состояние, и две одновременные команды не оставляют на плитке спорный результат. Права полезно расслоить: гостевой профиль с чтением без кнопок, детский с замком на опасных реле, полный доступ только владельцу. Простая мера экономит больше нервов, чем любой красивый график.
Готовые платформы и свой код Home Assistant Node-RED Grafana и ESPHome
Собирать всё с нуля не обязательно, и чаще всего не нужно. Home Assistant собирает сотни интеграций и позволяет сложить дашборд из карточек мышкой, разумный старт, когда важна скорость. Node-RED хорош для потоков автоматизации и простых панелей, Grafana заточена на ряды и панели графиков, ESPHome закрывает потребность в собственных датчиках на ESP32 с прошивкой из пары строк YAML. Свой код оправдан, когда нужен интерфейс с собственным брендом, нестандартная логика или встраивание панели в продукт, и в этом случае стек из MQTT.js, TimescaleDB и любого фронтенд-фреймворка собирается за пару недель.
Безопасность в этой архитектуре держится на трёх китах: TLS на каждом звене, авторизация на брокере со списками прав по учёткам и закрытый наружный контур. Брокер, торчащий в публичную сеть без надобности, это приглашение к плохим экспериментам, а локальный контур с контролируемым наружным доступом сохраняет и приватность, и управление из отъезда. Отдельные учётки для сервера дашборда и для каждого устройства дают точечное отключение без остановки всей системы.
Путь внедрения одинаков для любого выбора:
- вывести все датчики в единый брокер MQTT с понятной схемой топиков;
- поднять канал в браузер, WebSocket или SSE, с переподключением и паузами;
- собрать склад истории на TimescaleDB или InfluxDB с политиками хранения;
- описать плитки по комнатам и типам, добавив нормы и время последнего обновления;
- повесить команды на MQTT с QoS 1 и optimistic UI с откатом;
- закрыть канал TLS и авторизацией, добавить гостевой режим только для чтения;
- настроить агрегацию рядов и проверить графики на годовом диапазоне.
Хороший дашборд умного дома это не максимум цифр, а быстрый ответ на один вопрос: всё ли дома в порядке. Цепочка радиосигнал, брокер, канал, график стала скучной и предсказуемой, и именно эта скучность делает её надёжной: каждый протокол на своём месте, каждая плитка отвечает на свой вопрос, а человек получает картину дома за один взгляд. И если однажды плитка сообщит о протечке раньше, чем вода дойдёт до ноутбука, вся цепочка окупится целиком.