Девять устройств в доме живут на батарейках. Три из них — «таблетки» CR2032 на 3,0 В, остальные — AAA или AA. Одинаковый порог «менять при 20 %» для всех даёт ерунду: шкалы у элементов разные. Поэтому появился справочник типов элементов, карточка на главной, отдельная страница и починенный ночной cron. Ниже — что сделано, почему именно так, и что осталось на тебе.
Фактическое состояние на момент написания — из базы дома, не выдумано:
| Устройство | Элемент | Заряд | Напряжение | Состояние |
|---|---|---|---|---|
| Датчик протечки (ванная) | CR2032 ×1 (3,0 В) | 75 % | 2500 мВ | пора менять напряжение дошло до порога 2,70 В |
| Кран SONOFF SWV | AA ×4 (6,0 В) | 84 % | не отдаёт | нет связи молчит 18 дн — цифры устарели |
| Датчик освещённости | CR2032 ×1 (3,0 В) | 100 % | 3000 мВ | нет связи молчит 18 дн — цифры устарели |
| Садовый кран PushOk | AAA ×2 (3,0 В) | 83 % | 2700 мВ | в норме до порога 2,60 В запас есть |
| Датчик почвы (большой горшок) | AAA ×2 (3,0 В) | 92,5 % | 2900 мВ | в норме |
| Спальня: темп/влажность | CR2032 ×1 (3,0 В) | 100 % | 2900 мВ | в норме |
| Датчик вибрации/двери | CR2032 ×1 (3,0 В) | 100 % | не отдаёт | в норме |
| Датчик почвы (земляника) | AAA ×2 (3,0 В) | 100 % | не отдаёт | в норме |
| Датчик присутствия (mmWave) | AAA (3,0 В) | 100 % | не отдаёт | в норме |
Первая мысль — «взять все топики, где есть battery, и сравнить с числом». Не работает по трём причинам, и
каждую пришлось закрыть отдельно.
CR2032 — это одна таблетка на 3,0 В. Кран полива — четыре батарейки AA, то есть 6,0 В. Порог 2,7 В осмыслен для таблетки и бессмыслен для крана (у которого 6 В — это норма). Поэтому пороги живут в справочнике, по устройству.
Процент считает не устройство, а zigbee2mqtt — по приблизительной кривой разряда. Кривая ступенчатая, и на краях диапазона врёт: видел датчики, которые месяц показывают «100 %», а потом сразу падают в ноль. Напряжение же приходит с устройства как есть. Поэтому состояние берётся по худшему из двух сигналов: если вольтаж уже ушёл ниже порога, а процент ещё бодрый — всё равно «пора менять».
voltage — это милливольты (2500, 2900, 3000).
У розетки с измерением мощности то же самое поле voltage — это вольты сети (207…253).
Два следствия:
2500 <= 2.70 — это ложь, и разряженная батарейка молча уехала бы в «в норме». В коде пороги умножаются на 1000.battery_specОдна строка на устройство. Это единственное место, где правятся пороги — код их не дублирует. Хочешь поменять порог или добавить новое устройство: одна строка в базе, страницы трогать не нужно.
CR2032 (1 шт., 3,0 В) менять ≤ 2,70 В или 20 % · мёртвая ≤ 2,40 В или 5 % AAA (2 шт., 3,0 В) менять ≤ 2,60 В или 20 % · мёртвая ≤ 2,20 В или 5 % AA (4 шт., 6,0 В) менять ≤ 5,20 В или 20 % · мёртвая ≤ 4,40 В или 5 %
Показывает только то, что требует внимания: «вышли из строя», «пора менять», «нет связи». Если всё хорошо — одна зелёная строка «Все 9 устройств в норме», чтобы не мозолила глаза.
/admin/batteries.phpПолная таблица: устройство, комната, элемент, заряд, напряжение, пороги, когда обновлялось, тренд за 7 суток. Ссылка — в верхнем меню (значок 🔋) и из карточки на главной. Открывается только после входа в панель, как остальная админка.
Раньше история писалась только для температуры и влажности: у батарейных топиков стоял флаг
save_history = 0. То есть видеть «сейчас» можно было, а «как падало за месяц» — нет.
Включено для 14 топиков. Розетку намеренно не включал: там сетевое напряжение, оно и так в истории.
Первые точки уже пошли. Полноценный тренд будет через несколько дней: батарейные сообщения приходят редко (раз в часы), а некоторые датчики пишут вообще раз в сутки.
0 9 * * * check_battery.py падало каждый день.
В логе — Access denied for user 'iot_user'@'localhost': скрипт ходил в базу под старой учёткой,
а пароли давно вынесены в /etc/iot/credentials.env. Плюс он сравнивал процент с одним порогом
11 % для всех устройств подряд — не различая таблетку и четыре AA. И писал лог в /var/log/battery_check.log,
который принадлежит www-data: из-под mazzick туда писать нельзя, поэтому лог и выглядел битым.
Теперь скрипт берёт пороги из того же справочника, что панель, читает секреты из
/etc/iot/credentials.env, пишет лог в свою папку. Учитывает процент, напряжение и флаг
battery_low, который некоторые устройства ставят сами.
~/.battery_state.json и шлёт письмо только если состояние изменилось — или раз в 7 дней,
пока проблема не устранена. Иначе ежедневное «всё в норме» через неделю уходит в спам вместе с настоящими тревогами.
Проверено: второй запуск печатает «состояние не менялось ... молчу» и письма не шлёт.
healthcheck.shОбщая проверка дома теперь показывает сводку по батарейкам. Добавлена как информационная, а не как ошибка: про разряженные элементы уже пишет утреннее письмо, и второе уведомление о том же было бы дублем.
/etc/iot/credentials.ini стоит [ai] enabled = 0 (нет ключа нейросети), поэтому все карточки
наблюдения выключены и точки не собираются. Это не поломка, но проверка этого не знала — и одинаковое письмо
через неделю ушло бы в спам. Теперь проверка различает: если выключены и карточки, и нейросеть — печатается
[SKIP] и счётчик ошибок не растёт. Общий итог: «ЗДОРОВ, ошибок 0», сторож молчит.
mysql iot_db -e "SELECT device, battery_type, cells, warn_v, dead_v FROM battery_spec ORDER BY sort_order;"Правка одной строки:
mysql iot_db -e "UPDATE battery_spec SET warn_v=2.65 WHERE device='bedroom_sensor';"Новое устройство —
INSERT INTO battery_spec (device, title, battery_type, cells, nominal_v, warn_v, dead_v, has_voltage) VALUES (...).
Важно: device — это friendly_name из zigbee2mqtt (то, что видно в панели), а has_voltage=1
только если устройство действительно отдаёт поле voltage в милливольтах.
# прогон проверки батареек (письмо уйдёт только если состояние изменилось) /home/mazzick/check_battery.py # полная проверка дома /home/mazzick/healthcheck.sh # состояние всех устройств прямо в консоли php -r "require '/var/www/html/iot/config/iot_config.php'; require '/var/www/html/iot/public_html/include/battery_lib.php'; print_r(iot_battery_status(iot_pdo()));"
| Что | Где |
|---|---|
| Справочник элементов и порогов | таблица battery_spec в базе iot_db |
| Логика расчёта (одна на всех) | /var/www/html/iot/public_html/include/battery_lib.php |
| Страница батареек | /var/www/html/iot/public_html/admin/batteries.php |
| Карточка на главной | index.php, блок перед вкладкой «Cron Задачи» |
| Ночная проверка с письмом | /home/mazzick/check_battery.py + cron в 09:00 |
| Память о прошлом письме | /home/mazzick/.battery_state.json |
| Лог проверки | /home/mazzick/battery_check.log |
| Бэкапы всех правок | /home/mazzick/przorka_backup/battery_20260927/ |
| Общая проверка дома | /home/mazzick/healthcheck.sh (раздел 10 — батарейки) |
Все исходные версии файлов сохранены до правок. Вернуть страницу главную:
cp /home/mazzick/przorka_backup/battery_20260927/index.php.orig /var/www/html/iot/public_html/index.php cp /home/mazzick/przorka_backup/battery_20260927/links_panel.php.orig /var/www/html/iot/public_html/include/links_panel.php cp /home/mazzick/przorka_backup/battery_20260927/check_battery.py.orig /home/mazzick/check_battery.py cp /home/mazzick/przorka_backup/battery_20260927/healthcheck.sh.orig /home/mazzick/healthcheck.sh
Таблицы battery_spec и включённую историю можно убрать одной командой, панель от этого работать не перестанет:
mysql iot_db -e "DROP TABLE battery_spec;" mysql iot_db -e "UPDATE sensor_info SET save_history=0 WHERE topic LIKE 'zigbee2mqtt/%/battery';"