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

310 lines
19 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:**
- 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_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: реализация
**Модули:**
- `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:**
```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 дней**
- Если устройство добавлено недавно — показывать доступность с момента добавления (не с начала периода)
- Если устройство удалено — перестать показывать на дашборде
## Что нужно от Славы
1. ~~Подтвердить вариант реализации~~ → ✅ AppDaemon (Вариант A)
2. Увеличить `purge_keep_days` до 35 (иначе не будет данных за месяц)
3. ~~Установить AppDaemon~~ → ✅ Установлен (4.5.13)
4. ~~Подтвердить список исключений~~ → ✅ Согласовано (см. выше)
5. Доустановить **button-card** через HACS
6. ~~Создать `input_select.avail_period`~~ → ✅ Создан (опции: 24h, 7d, 30d, по умолчанию 7d)
7. Назначить **areas** устройствам в HA (Settings → Areas) — многие показывают «Без комнаты»
8. Построить **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-дашборд
- Расширить на другие домены (после обкатки)