19 KiB
ТЗ: Дашборд доступности устройств в Home Assistant
Цель
Дашборд, показывающий uptime устройств в процентах за три периода: день, неделю, месяц. С сортировкой по убыванию доступности.
Метрика
Доступность = время в статусе «доступен» / общее время периода × 100%
- «Доступен» — любой статус, кроме
unavailableиunknown. Важно: статусoff(выключенное устройство) считается доступным — выключено ≠ недоступно - «Недоступен» — статус
unavailableилиunknown - Результат: процент (0%–100%), один знак после точки
Периоды
По умолчанию: 7 дней. В дашборде — переключатель: 24ч / 7д / 30д.
| Период | Обновление | Описание |
|---|---|---|
| 24ч | каждые 5 мин | Оперативный мониторинг |
| 7д | каждые 15 мин | Основной режим по умолчанию |
| 30д | каждые 2 часа | Долгосрочная статистика |
Переключение периода пересчитывает все данные на лету из истории. Храним кэш для всех трёх периодов, чтобы переключение было мгновенным.
Какие устройства отслеживать
Все entity из доменов физических устройств:
binary_sensor,sensor,switch,lightclimate,cover,lock,media_playerdevice_tracker,vacuum,fan,humidifierwater_heater,siren,button
Исключения (не показывать на дашборде):
- Вспомогательные entity Zigbee2MQTT (
sensor.*_update_state,update.*,button.zigbee2mqtt_*) - Entity с суффиксами
_battery_low,_battery,_linkquality,_update,_identify - Виртуальные/template сенсоры (без device_id)
- Автоматически создаваемые Select/Number для Zigbee-устройств (настройки radar_sensitivity и т.п.)
Архитектура решения
1. Вычисление доступности
Один Python-скрипт (AppDaemon или HA Python custom component):
- Периодически (по расписанию для каждого периода) запрашивает историю из HA API
- Batch-запрос: несколько entity_id через запятую (
filter_entity_id=id1,id2,id3) - Считает для каждого устройства:
- Процент доступности за выбранный период
- Частоту падений (количество переходов в unavailable/unknown)
- Максимальный непрерывный даунтайм
- Sparkline данные (7 точек — по одной за день)
- Сравнение с предыдущим периодом (тренд)
- Записывает результаты как sensor-ы:
sensor.avail_<sanitized_id>с атрибутами
2. Структура sensor-ов
Каждый отслеживаемый device → один sensor с атрибутами:
sensor.avail_light_bra_v_spalne:
state: 97.8 # текущий % доступности
attributes:
entity_id: light.bra_v_spalne
friendly_name: "Бра в спальне"
domain: light
period: 7d # выбранный период
availability_pct: 97.8
down_count: 5
max_downtime_minutes: 102
sparkline: [100, 100, 98, 95, 99, 100, 97.8] # 7 точек
trend: "down" # down/up/stable vs прошлая неделя
last_downtime: "2026-04-14T15:32:00+03:00"
color: "green" # green/yellow/orange/red
3. Варианты реализации
Вариант A: AppDaemon (рекомендуемый)
- Отдельный Python-скрипт в AppDaemon
- Работает по расписанию, batch-обработка
- Легко читать/обновлять, не зависит от HA Core
- ✅ Плюсы: изоляция, легко отлаживать, минимум нагрузки на HA
- ⚠️ Требует: установка AppDaemon как HA addon
Вариант B: HA Custom Component
- Кастомная интеграция, регистрирует dynamic sensor-ы
- Работает как часть HA, доступ к recorder напрямую
- ✅ Плюсы: нативно, без доп. зависимостей
- ⚠️ Минусы: сложнее разработка, перезагрузка при изменениях
Вариант C: HA Python Scripts + REST Command
- Python-скрипт через
python_scriptintegration - Запускается по automation, сохраняет результаты через REST
- ✅ Плюсы: без AppDaemon
- ❌ Минусы:
python_scriptограничен в импортах, сложнее batch
Рекомендация: Вариант A (AppDaemon) ✅ ВЫБРАН
Оптимальный баланс. Изолированный Python-скрипт, batch-обработка, легко масштабировать.
Установлено: AppDaemon 4.5.13, HA 2026.4.2, Python 3.12.13
Путь конфига: /addon_configs/a0d7b954_appdaemon/appdaemon.yaml
Путь приложений: /addon_configs/a0d7b954_appdaemon/apps/
Путь apps.yaml: /addon_configs/a0d7b954_appdaemon/apps/apps.yaml
Slug аддона: a0d7b954_appdaemon
Подключение к HA:
- REST API: автоматическое через SUPERVISOR_TOKEN (
http://supervisor/core/api) - WebSocket (для registry): HA Long-Lived Access Token через
apps.yamlаргументha_token→ws://homeassistant:8123/api/websocket - ⚠️ SUPERVISOR_TOKEN не подходит для прямого WebSocket-подключения к HA — только для REST через supervisor proxy
Первая фаза: только light + switch
Начинаем с минимального набора — только домены light и switch. После обкатки расширяем на остальные.
Устройств после фильтрации: ~32 (2 light + 30 switch)
Дополнительные исключения для switch (настройки реле — не основные устройства):
*_delayed_power_on_state*_detach_relay_mode*_network_indicator*_turbo_mode*_do_not_disturbswitch.zigbee2mqtt_bridge_permit_join
После этих исключений — ~18 основных устройств.
Cold-start (первичная загрузка и перезапуск)
При запуске AppDaemon (или после перезапуска HA) — полный пересчёт текущего активного периода для всех устройств. Не ждать следующего расписания — данные должны быть актуальны сразу.
Rate-limit при batch-запросах: 180 устройств × 30 дней истории — ощутимая нагрузка на HA API. Разбивать на батчи по ~20 entity_id с паузой 1 сек между запросами. Показывать прогресс: sensor.avail_calc_progress с состоянием "47/180" или "idle".
Дашборд
Группировка по комнатам/зонам
Устройства группируются по HA areas (комнатам). Если у устройства нет area — группа «Без комнаты».
- Каждая группа — сворачиваемая секция с заголовком:
🛏️ Спальня (12 устройств, 97.2%) - В заголовке: иконка комнаты, средняя доступность по группе, количество устройств
- По умолчанию: проблемные комнаты (средний % < 95%) раскрыты, остальные свёрнуты
- Сортировка комнат: сначала проблемные, потом по средней доступности (по убыванию)
Мобильная адаптация
На экранах < 768px (мобильный вид):
- Убрать sparkline — не помещается
- Компактная строка: иконка + friendly name + цветной процент (крупный)
- Прогресс-бар — тонкий, одна линия (не блок)
- Сводка падений — скрыта, раскрывается по тапу (expandable)
- Группы комнат — свёрнуты по умолчанию, тап раскрывает список
Десктопный вид — как описано ниже (полный).
Карточка: список устройств с визуалом (десктоп)
Прогресс-бар + сводка для каждого устройства:
💡 Бра в спальне ████████████████████░ 97.8%
📉 100 100 98 95 99 100 98
5 падений, макс. даунтайм 1ч 42мин
Строка устройства:
- Friendly name + иконка домена (💡, 🔌, 🌡️ и т.д.)
- Прогресс-бар — заполненность = доступность (%). Цвет по диапазону:
- 🟢
≥99%— зелёный - 🟡
95-99%— жёлтый - 🟠
90-95%— оранжевый - 🔴
<90%— красный
- 🟢
- Процент — справа от прогресс-бара, крупный шрифт
- Sparkline — мини-график из 7 точек под прогресс-баром, тренд стрелкой (📈/📉/➡️)
- Сводка падений — одна строка:
N падений, макс. даунтайм Xч Yмин - Время последнего падения — если <24ч: "X минут/часов назад"
Переключатель периода — вверху дашборда: [24ч] [7д] [30д]. По умолчанию 7д.
Карточка: сводка
┌─────────────────────────────────────────┐
│ 📊 Доступность за 7 дней │
│ │
│ ██████████████████████░ Средняя: 96.4% │
│ │
│ 🔴 Проблемных (<95%): 4 │
│ 💡 Бра в спальне — 87.2% │
│ 🔌 Б.колодец насос — 91.3% │
│ 🌡️ Ванна температура — 82.7% │
│ 🔒 Замок входной — 94.1% │
│ │
│ 📉 Хуже чем прошлую неделю: 3 │
│ 📈 Лучше чем прошлую неделю: 8 │
└─────────────────────────────────────────┘
Функциональность:
- Проблемные (<95%) — отдельная сворачиваемая секция
- Тренд: сравнение с предыдущим периодом (предыдущие 7 дней)
- Клик на проблемное устройство — скролл к нему в таблице
- Цветовая индикация средней доступности (прогресс-бар в сводке)
- Кнопка "Обновить" — принудительный пересчёт
Технические детали
Путь приложения AppDaemon
/addon_configs/a0d7b954_appdaemon/apps/
├── apps.yaml # регистрация приложений
├── availability.py # основной модуль расчёта доступности
└── availability_utils.py # вспомогательные функции (фильтрация, форматирование)
Источник данных
- HA History API:
/api/history/period/<start>?filter_entity_id=<ids>&minimal_response&no_attributes - Возвращает массив пар
(state, last_changed)— достаточно для расчёта времени - AppDaemon подключён к HA через WebSocket — использует
self.get_entity_history()или REST API черезself.hass
Расчёт (алгоритм)
def calc_availability(history_entries, period_start, period_end):
unavailable_seconds = 0
for entry in history_entries:
state = entry['state']
changed = parse_datetime(entry['last_changed'])
if state in ('unavailable', 'unknown'):
# Сколько времени устройство было в этом статусе
# до следующего изменения или до period_end
next_change = get_next_change(entry) or period_end
unavailable_seconds += (next_change - changed).total_seconds()
total_seconds = (period_end - period_start).total_seconds()
availability = (1 - unavailable_seconds / total_seconds) * 100
return round(availability, 1)
Хранение результатов
- Каждый результат —
sensor.avail_<sanitized_id>(один sensor, период в атрибуте) - Атрибуты:
entity_id,period,availability_pct,area,down_count,max_downtime_minutes,sparkline,trend,last_downtime,color,last_updated - Группировка по комнатам:
sensor.avail_area_<sanitized_name> - Прогресс расчёта:
sensor.avail_calc_progress("47/180"или"idle")
Дашборд: имеющиеся кастомные карточки
Установлены через HACS:
- mini-graph-card — для sparkline
- auto-entities — автоподхват устройств
- card-mod — кастомный CSS (мобильная адаптация)
- stack-in-card — группировка карточек
Нужно доустановить:
- button-card — для строк устройств с прогресс-баром
- custom:hui-element — для input_select на дашборде (или использовать стандартный entities card)
AppDaemon: реализация
Модули:
availability.py— классAvailability(hass.Hass), основная логикаavailability_utils.py— чистые функции (фильтрация, расчёт, форматирование)
Ключевые решения при реализации:
self.sleep()в AppDaemon 4.x — coroutine, нельзя вызывать синхронно → заменён наtime.sleep()(блокирует только worker thread)log_level: infoв apps.yaml вызываетValueError: Unknown level→ убрать, AppDaemon использует INFO по умолчанию- HA Registry API (area/entity/device) недоступен через REST — только WebSocket (
config/area_registry/listи т.д.) - SUPERVISOR_TOKEN работает для REST через supervisor proxy, но не для прямого WS к HA
- Entity ID не может содержать кириллицу →
sanitize_area_name()с транслитерацией (а→a, б→b, ...)
apps.yaml:
hello_world:
module: hello
class: HelloWorld
availability:
module: availability
class: Availability
ha_token: <Long-Lived Access Token> # для WebSocket registry
⚠️ Секреты: ha_token хранится в apps.yaml на HA. Не дублировать в других файлах.
Нагрузка
- ~180 устройств
- History API batch-запрос (можно передать несколько entity_id через запятую)
- За день: ~180 × 3 = 540 точек данных (при batch — 3 API-вызова)
- За неделю/месяц: данные уже рассчитаны, обновляются реже
Ограничения
purge_keep_daysв recorder — по умолчанию 10 дней. Для месячной статистики нужно увеличить до 35 дней- Если устройство добавлено недавно — показывать доступность с момента добавления (не с начала периода)
- Если устройство удалено — перестать показывать на дашборде
Что нужно от Славы
Подтвердить вариант реализации→ ✅ AppDaemon (Вариант A)- Увеличить
purge_keep_daysдо 35 (иначе не будет данных за месяц) Установить AppDaemon→ ✅ Установлен (4.5.13)Подтвердить список исключений→ ✅ Согласовано (см. выше)- Доустановить button-card через HACS
Создать→ ✅ Создан (опции: 24h, 7d, 30d, по умолчанию 7d)input_select.avail_period- Назначить areas устройствам в HA (Settings → Areas) — многие показывают «Без комнаты»
- Построить Lovelace-дашборд (после установки button-card)
Статус деплоя
✅ Готово
- AppDaemon 4.5.13 установлен и работает
- availability.py + availability_utils.py задеплоены
- 21 device sensor создан (
sensor.avail_*) - 2 area sensor создан (
sensor.avail_area_*) - input_select.avail_period создан
- WebSocket registry работает (9 areas, 729 entity mappings)
- Расчёт за все 3 периода (24h/7d/30d)
⏳ TODO
- Назначить areas устройствам в HA
- Увеличить purge_keep_days до 35
- Установить button-card через HACS
- Построить Lovelace-дашборд
- Расширить на другие домены (после обкатки)