Files
wiki/tasks/ha-availability-dashboard/TZ.md
2026-04-15 14:10:01 +03:00

279 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ: Дашборд доступности устройств в 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_<sanitized_id>` с атрибутами
### 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/<start>?filter_entity_id=<ids>&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_<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: как писать приложение
```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д)