# ТЗ: Дашборд доступности устройств в 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`, `light` - `climate`, `cover`, `lock`, `media_player` - `device_tracker`, `vacuum`, `fan`, `humidifier` - `water_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): 1. Периодически (по расписанию для каждого периода) запрашивает историю из HA API 2. Batch-запрос: несколько entity_id через запятую (`filter_entity_id=id1,id2,id3`) 3. Считает для каждого устройства: - Процент доступности за выбранный период - Частоту падений (количество переходов в unavailable/unknown) - Максимальный непрерывный даунтайм - Sparkline данные (7 точек — по одной за день) - Сравнение с предыдущим периодом (тренд) 4. Записывает результаты как sensor-ы: `sensor.avail_` с атрибутами ### 2. Структура sensor-ов Каждый отслеживаемый device → один sensor с атрибутами: ```yaml 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_script` integration - Запускается по 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:** автоматическое через SUPERVISOR_TOKEN (WebSocket), ha_url/token НЕ указывать ### Первая фаза: только light + switch Начинаем с минимального набора — только домены `light` и `switch`. После обкатки расширяем на остальные. **Устройств после фильтрации:** ~32 (2 light + 30 switch) **Дополнительные исключения для switch (настройки реле — не основные устройства):** - `*_delayed_power_on_state` - `*_detach_relay_mode` - `*_network_indicator` - `*_turbo_mode` - `*_do_not_disturb` - `switch.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/?filter_entity_id=&minimal_response&no_attributes` - Возвращает массив пар `(state, last_changed)` — достаточно для расчёта времени - AppDaemon подключён к HA через WebSocket — использует `self.get_entity_history()` или REST API через `self.hass` ### Расчёт (алгоритм) ```python 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_` (один sensor, период в атрибуте) - Атрибуты: `entity_id`, `period`, `availability_pct`, `area`, `down_count`, `max_downtime_minutes`, `sparkline`, `trend`, `last_downtime`, `color`, `last_updated` - Группировка по комнатам: `sensor.avail_area_` - Прогресс расчёта: `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: как писать приложение ```python import appdaemon.plugins.hass.hassapi as hass class Availability(hass.Hass): def initialize(self): # Cold-start: сразу пересчёт self.run_in(self.calc_all, 30) # через 30 сек после старта # Расписания self.run_every(self.calc_24h, "now+30", 5 * 60) # каждые 5 мин self.run_every(self.calc_7d, "now+60", 15 * 60) # каждые 15 мин self.run_every(self.calc_30d, "now+120", 2 * 3600) # каждые 2 часа # Подписка на переключение периода self.listen_state(self.period_changed, "input_select.avail_period") ``` ### Нагрузка - ~180 устройств - History API batch-запрос (можно передать несколько entity_id через запятую) - За день: ~180 × 3 = 540 точек данных (при batch — 3 API-вызова) - За неделю/месяц: данные уже рассчитаны, обновляются реже ## Ограничения - `purge_keep_days` в recorder — по умолчанию 10 дней. Для месячной статистики нужно **увеличить до 35 дней** - Если устройство добавлено недавно — показывать доступность с момента добавления (не с начала периода) - Если устройство удалено — перестать показывать на дашборде ## Что нужно от Славы 1. ~~Подтвердить вариант реализации~~ → ✅ AppDaemon (Вариант A) 2. Увеличить `purge_keep_days` до 35 (иначе не будет данных за месяц) 3. ~~Установить AppDaemon~~ → ✅ Установлен (4.5.13) 4. ~~Подтвердить список исключений~~ → ✅ Согласовано (см. выше) 5. Доустановить **button-card** через HACS 6. Создать `input_select.avail_period` (опции: 24ч, 7д, 30д)