Описание проекта и архива: что за система, из чего она состоит, как развернуть её с нуля и на какие грабли уже наступали. Документ лежит внутри архива рядом с кодом — читать перед тем, как что-то менять.
Самописная домашняя автоматизация без облака и без готовых платформ. Всё крутится на одной Raspberry Pi: Zigbee-устройства (датчики движения, протечки, температуры и влажности, реле, краны, розетки) общаются с координатором, zigbee2mqtt превращает их сообщения в MQTT, Mosquitto раздаёт их, логгер складывает значения в MariaDB, движок правил сравнивает их с таблицей правил и отправляет команды обратно в брокер, а веб-панель показывает состояние и даёт править правила и справочник датчиков.
Ключевая идея архитектуры: брокер — это шина событий, база — состояние и правила. Движок не опрашивает базу по таймеру, а подписан на темы и реагирует в момент сообщения. Расписания, выдержки условия и отложенные команды остаются на такте в одну секунду — это единственное, что действительно требует таймера.
Zigbee-устройства
│ радио
zigbee2mqtt ──► Mosquitto (127.0.0.1:1883, с логином)
│
┌───────────┴────────────┐
mqtt_to_mysql.py mqtt_listener.php
логгер: JSON → таблицы движок: правила → команды в .../set
│ │
└──────► MariaDB ◄───────┘
│
nginx + PHP 8.2 (панель)Сквозной путь одного события: датчик протечки прислал water_leak: true →
zigbee2mqtt опубликовал JSON в zigbee2mqtt/leak_bath → логгер разложил поля в
sensor_data и sensor_history → движок, подписанный на тему, разобрал
JSON, проверил правила на поле water_leak, нашёл совпадение и опубликовал
{"state":"ON"} в zigbee2mqtt/fish_light/set → реле включило свет →
устройство доложило новое состояние → строка в automation_log.
| Служба | Что делает | Порт / сокет | Пользователь |
|---|---|---|---|
zigbee2mqtt | мост Zigbee → MQTT | USB-координатор | пользователь pi |
mosquitto | MQTT-брокер | 127.0.0.1:1883 (логин обязателен) | mosquitto |
mqtt-to-mysql | логгер MQTT → база | исходящие к брокеру и БД | пользователь pi |
iot-automation | движок правил | исходящие к брокеру | www-data |
iot-anomaly | сбор точек для модуля аномалий (в оригинале выключен) | — | пользователь pi |
mariadb | база данных | unix-сокет | mysql |
nginx + php8.2-fpm | веб-панель | :80, только разрешённые адреса | www-data |
mosquitto_pub без учётных данных.
| Путь | Что внутри |
|---|---|
site/public_html/ | корень сайта: главная с карточками и графиками, admin/, api/, css/, icons/, include/ |
site/public_html/admin/sensors.php | вход в панель и справочник датчиков: имя, комната, группа, иконка, признак «сохранять историю» |
site/public_html/admin/logic.php | редактор правил автоматизации (таблица automations) |
site/public_html/admin/mqtt_listener.php | движок правил: подписка на брокер, вычисление условий, публикация команд, журнал |
site/public_html/admin/mqtt_client.php | свой клиент MQTT 3.1.1 на сокетах — используется только движком |
site/public_html/admin/mqtt_to_mysql.py | логгер: раскладка JSON в sensor_data и sensor_history |
site/public_html/admin/batteries.php | состояние элементов питания; общая логика — в include/battery_lib.php |
site/public_html/admin/automation_log.php | журнал срабатываний: что, когда, отправлена ли команда, подтвердило ли устройство |
site/config/iot_config.php | общий конфиг PHP: подключение к базе и константы IOT_*; читает секреты из /etc/iot/ |
site/db/*.sql | таблицы, которые создаёт только администратор базы |
cron/cron_*.php | задачи в обход движка: кормушка, нагрев, свет, письма, полив |
cron/crontab_mazzick.txt, cron/crontab_root.txt | расписания пользователя и root |
scripts/ | healthcheck, сторож с письмом, проверка батареек, сбор аномалий, отправка push, бэкап, чистка базы |
bash_scripts/ | анализ журналов, мониторинг самой малины, рассылка тревог |
systemd/ | четыре юнита: iot-automation, mqtt-to-mysql, iot-anomaly, zigbee2mqtt |
nginx/iot.conf | конфиг сайта: доступ по подсети и запрет отдачи не-веб файлов (.py, .sql, .log, бэкапы) |
mosquitto/ | mosquitto.conf и conf.d/mqtt.conf — слушатель только на localhost, логин обязателен |
zigbee2mqtt/configuration.yaml | конфиг Z2M: последовательный порт, сетевой ключ Zigbee, MQTT, список устройств |
secrets/credentials.ini | все секреты приложения (права 640, владелец root:www-data) |
secrets/credentials.env | те же секреты для shell и cron (640, root:пользователь pi) |
db/schema_iot_db.sql | структура базы: CREATE TABLE, триггеры, процедуры. Без данных |
logrotate-*.conf | ротация журналов (логгер умеет писать гигабайты, если забыть) |
README.md, docs/*.html | это описание, статья об обновлении, таблица новых паролей |
node_modules и vendor (ставятся пакетным
менеджером), журналов, бэкапов и карантина мусора — это следы эксплуатации, а не проект.
22 таблицы. Данные не выгружались — только структура, чтобы было видно устройство.
| Группа | Таблицы | Зачем |
|---|---|---|
| Состояние и история | sensor_data, sensor_history | последнее значение по топику и все значения для графиков |
| Справочник | sensor_info, battery_spec | имена, комнаты, группы датчиков; типы элементов питания и пороги |
| Автоматизация | automations, automation_log, pending_actions, automation_email_sent, email_triggers | правила, журнал срабатываний, отложенные команды и ожидания подтверждений, защита от повторных писем |
| Уведомления | push_notifications, daily_resets | очередь push и суточные счётчики |
| Аномалии | anomaly_watchers, anomaly_samples, anomaly_events, anomaly_requests, anomaly_reports | карточки наблюдения, точки, события, запросы и отчёты (модуль в оригинале выключен) |
| Прочее | rpi_monitor, log_stats, rs485_commands, rs485_sensor_data, sensors, zz_test_perm | метрики самой малины, статистика журналов, задел под RS-485, служебные |
SELECT/INSERT/UPDATE/DELETE.
Создание и изменение таблиц — руками администратора. Это не строгость ради строгости: если
приложение само себе меняет схему, откат превращается в археологию.
nginx php8.2-fpm php8.2-mysql mariadb-server mosquitto mosquitto-clients python3-venv nodejs npm.iot_db в utf8mb4, залить db/schema_iot_db.sql,
завести прикладного и административного пользователей./etc/iot/credentials.ini (640, root:www-data) и
credentials.env (640, root:пользователь pi). Значения — как в README.site/ в /var/www/html/iot/, поправить пути в
nginx/iot.conf, включить сайт, nginx -t && systemctl reload nginx.systemd/ в /etc/systemd/system/,
daemon-reload, enable --now iot-automation mqtt-to-mysql.mosquitto/ в /etc/mosquitto/, пользователя завести
через mosquitto_passwd -c /etc/mosquitto/passwd iot.systemctl enable --now zigbee2mqtt.cron/crontab_*.txt, healthcheck.sh
в домашний каталог, ежечасный iot_watchdog.sh.active, в sensor_data свежие метки
времени, в журнале движка «Загружено N активных правил» и ни одной ошибки PHP./etc/iot/credentials.ini, shell и
cron — credentials.env. Права 640, владелец — тот, кому нужно.deny all..py, .sh, .sql,
.log, .bak, бэкапы и *.phpgood* закрыты отдельными правилами.secrets/* на заглушки, а настоящие хранить только на самой малине.
У батарейных Zigbee-устройств поле voltage — это милливольты (2500…3000), а у
розетки с измерением мощности то же поле — вольты сети (207…253). Если собирать «батарейки»
поиском по имени топика, розетка попадёт в список и будет выглядеть заряженной на тысячи
процентов. Список берётся из battery_spec, а пороги в ней записаны в вольтах —
сравнивать надо с милливольтами.
При output: json в брокер приходит одно сообщение на устройство. Движок обязан
разбирать JSON и проверять правила по каждому полю — иначе правила на датчики молча не
срабатывают, при этом расписания продолжают работать. Симптом: «по времени работает, по топику нет».
Для многоканальных реле возможны две формы: .../set/state_l3 + {"state":"ON"}
или .../set + {"state_l3":"ON"}. Перепутал — в журнале «успешно
отправлено», а реле молчит.
«Условие держится N секунд» должно работать и для триггерных правил, а не только для расписаний. Иначе кран закрывается в ту же секунду, когда открылся.
Отложенные команды и ожидания подтверждений переживают перезапуск только потому, что пишутся
в таблицу pending_actions. Иначе systemctl restart тихо отменяет всё,
что было запланировано.
Строка последнего значения остаётся в базе, даже если устройство выключили полгода назад. Поэтому у каждой карточки в панели есть возраст значения: «18 дн назад» — это не «всё хорошо», это повод проверить связь.
Наличие таблицы проверяется чтением; если её нет — в журнал идёт понятное сообщение, а создаёт её администратор. Иначе каждая страница и каждый cron сыплют «CREATE command denied».
przorka_backup/groups_20261028/*.orig
на самой малине.ALTER TABLE automations DROP COLUMN group_name; — сами правила не пострадают.