# ТЗ: Дашборд доступности устройств в 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-дашборд
- Расширить на другие домены (после обкатки)
