From 2925fd8e0cdcaeb74d5c8acce36e1e67f0851e16 Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 15 Apr 2026 00:40:01 +0300 Subject: [PATCH] auto-sync: 2026-04-15 00:40:01 --- tasks/ha-availability-dashboard/TZ.md | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 tasks/ha-availability-dashboard/TZ.md diff --git a/tasks/ha-availability-dashboard/TZ.md b/tasks/ha-availability-dashboard/TZ.md new file mode 100644 index 0000000..bf3d307 --- /dev/null +++ b/tasks/ha-availability-dashboard/TZ.md @@ -0,0 +1,156 @@ +# ТЗ: Дашборд доступности устройств в Home Assistant + +## Цель +Дашборд, показывающий **uptime устройств** в процентах за три периода: день, неделю, месяц. С сортировкой по убыванию доступности. + +## Метрика +**Доступность** = время в статусе «доступен» / общее время периода × 100% + +- «Доступен» — любой статус, кроме `unavailable` и `unknown` +- «Недоступен» — статус `unavailable` или `unknown` +- Результат: процент (0%–100%), один знак после точки + +## Периоды +| Период | Формула | Обновление | +|--------|---------|------------| +| День | за последние 24 часа | каждые 5 мин | +| Неделя | за последние 7 дней | каждые 30 мин | +| Месяц | за последние 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. Вычисление доступности — через长期的 sensor +Для каждого отслеживаемого устройства создать **template sensor** (или один Python-скрипт через REST), который: +1. Запрашивает историю из `/api/history/period/?filter_entity_id=&minimal_response&no_attributes` +2. Считает суммарное время в `unavailable`/`unknown` +3. Вычисляет процент доступности +4. Записывает результат как sensor state + +### 2. Варианты реализации + +#### Вариант A: Utility Meter + Template Sensor (нативный HA) +- На каждое устройство — `binary_sensor` доступности (доступен/нет) +- `utility_meter` считает время в каждом статусе +- Template sensor вычисляет процент +- ❌ **Минус:** нужен `custom:utility_meter` или `history_stats` — много entity на каждое устройство, сложная конфигурация при 180+ устройствах + +#### Вариант B: Python-скрипт + AppDaemon (рекомендуемый) +- Один Python-скрипт, работающий по расписанию +- Читает историю через HA API для всех устройств сразу +- Вычисляет доступность за все 3 периода +- Создаёт/обновляет `sensor.availability__` через `states` API +- ✅ **Плюсы:** один скрипт, легко масштабировать, минимальная нагрузка на HA +- ⚠️ **Требует:** AppDaemon или HA Python script / custom component + +#### Вариант C: Custom HA Integration (HACS) +- Кастомная интеграция, регистрирует sensor-ы через `async_setup_platform` +- Берёт данные из recorder напрямую (SQL) +- ✅ **Плюсы:** максимально нативно, работает через recorder без API +- ⚠️ **Минусы:** сложнее разработка, нужен доступ к БД recorder + +#### Вариант D: SQL Sensor + Jinja (самый простой) +- `sql` integration — прямые запросы к MariaDB/SQLite recorder +- Один sensor на устройство×период с SQL-запросом +- ✅ **Плюсы:** без внешних зависимостей, работает из коробки +- ❌ **Минусы:** 180 устройств × 3 периода = 540 SQL-запросов по расписанию — нагрузка на БД + +### Рекомендация: Вариант B (AppDaemon / Python-скрипт) +Оптимальный баланс сложности и производительности. Один скрипт, batch-обработка, минимальная нагрузка. + +## Дашборд + +### Карточка: таблица доступности +``` +┌──────────────────────────┬────────┬────────┬────────┐ +│ Устройство │ День │ Неделя │ Месяц │ +├──────────────────────────┼────────┼────────┼────────┤ +│ 💡 Бра в спальне │ 99.8% │ 98.5% │ 95.2% │ +│ 🔌 Лесной колодец насос │ 100.0% │ 99.1% │ 97.8% │ +│ 🌡️ Улица температура │ 85.3% │ 92.1% │ 88.7% │ +│ ... │ ... │ ... │ ... │ +└──────────────────────────┴────────┴────────┴────────┘ +``` + +**Функциональность:** +- Сортировка по любому столбцу (по умолчанию — по месячной доступности, убывание) +- Цветовая индикация: + - 🟢 99-100% — зелёный + - 🟡 95-99% — жёлтый + - 🟠 90-95% — оранжевый + - 🔴 <90% — красный +- Фильтр по домену (switch, sensor, light...) +- Обновление: при загрузке дашборда + по расписанию + +### Карточка: сводка +``` +┌──────────────────────────────────┐ +│ 📊 Доступность устройств │ +│ │ +│ Средняя за день: 97.3% │ +│ Средняя за неделю: 96.1% │ +│ Средняя за месяц: 94.8% │ +│ │ +│ Устройств <95% (месяц): 5 │ +│ Устройств <90% (месяц): 2 │ +└──────────────────────────────────┘ +``` + +## Технические детали + +### Источник данных +- HA History API: `/api/history/period/?filter_entity_id=&minimal_response&no_attributes` +- Возвращает массив пар `(state, last_changed)` — достаточно для расчёта времени + +### Расчёт (алгоритм) +```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__` +- Атрибуты: `entity_id`, `period`, `availability_%`, `last_updated` +- Группировка: `group.availability_day`, `group.availability_week`, `group.availability_month` + +### Нагрузка +- ~180 устройств +- History API batch-запрос (можно передать несколько entity_id через запятую) +- За день: ~180 × 3 = 540 точек данных (при batch — 3 API-вызова) +- За неделю/месяц: данные уже рассчитаны, обновляются реже + +## Ограничения +- `purge_keep_days` в recorder — по умолчанию 10 дней. Для месячной статистики нужно **увеличить до 35 дней** +- Если устройство добавлено недавно — показывать доступность с момента добавления (не с начала периода) +- Если устройство удалено — перестать показывать на дашборде + +## Что нужно от Славы +1. Подтвердить вариант реализации (рекомендую B — AppDaemon/Python) +2. Увеличить `purge_keep_days` до 35 (иначе не будет данных за месяц) +3. Установить AppDaemon (если выбран вариант B) — или подтвердить другой вариант +4. Подтвердить список исключений (какие entity не отслеживать)