diff --git a/memory/2026-04-15.md b/memory/2026-04-15.md new file mode 100644 index 0000000..2498c17 --- /dev/null +++ b/memory/2026-04-15.md @@ -0,0 +1,24 @@ + +## HA Availability Dashboard — деплой (11:00-11:40 UTC) + +### Что сделано: +- AppDaemon 4.5.13 установлен как HA addon +- Написан availability.py + availability_utils.py (Dev-агент GLM 5.1) +- Создан input_select.avail_period (24h/7d/30d) +- Пофикшены баги: + - self.sleep() coroutine → time.sleep() + - Кириллица в entity_id → транслитерация (sanitize_area_name) + - Area registry: REST API не работает → WebSocket (ws://homeassistant:8123/api/websocket) + - SUPERVISOR_TOKEN не подходит для прямого WS → нужен HA Long-Lived Access Token (передан через apps.yaml args) +- 21 device sensor создан, 2 area sensors +- 9 areas найдено в HA, 729 entity-area mappings + +### Осталось: +- Назначить areas устройствам в HA (многие "Без комнаты") +- Доустановить button-card через HACS для дашборда +- Построить Lovelace-дашборд +- Увеличить purge_keep_days до 35 +- Расширить на другие домены (после обкатки) + +### Токен HA для AppDaemon: +- ha_token в apps.yaml (Long-Lived Access Token) diff --git a/tasks/ha-availability-dashboard/PROJECT.md b/tasks/ha-availability-dashboard/PROJECT.md new file mode 100644 index 0000000..5282c2c --- /dev/null +++ b/tasks/ha-availability-dashboard/PROJECT.md @@ -0,0 +1,131 @@ +# HA Availability Dashboard + +## Описание +Дашборд доступности устройств в Home Assistant — показывает uptime устройств в % за три периода (24ч / 7д / 30д) с группировкой по комнатам, цветовой индикацией и sparkline. + +## Статус +🟡 **В разработке** — бэкенд работает, дашборд не построен + +## Ссылки +- **ТЗ:** `TZ.md` +- **Исходники AppDaemon:** `appdaemon/` (локальная копия, деплой → HA) +- **HA:** `https://ha.homenet542.keenetic.pro` + +## Архитектура + +``` +HA History API ──► AppDaemon (availability.py) ──► sensor.avail_* ──► Lovelace Dashboard + │ + ├── REST API (supervisor/core/api) — история, states + └── WebSocket (homeassistant:8123) — area/entity/device registry +``` + +## Файловая структура + +### На HA (`/addon_configs/a0d7b954_appdaemon/apps/`) +``` +apps/ +├── apps.yaml # регистрация: hello_world + availability +├── availability.py # основной модуль (Availability class) +├── availability_utils.py # утилиты (фильтрация, расчёт, форматирование) +└── hello.py # тестовое приложение (оставлено) +``` + +### Локально (`tasks/ha-availability-dashboard/`) +``` +├── TZ.md # полное ТЗ +├── PROJECT.md # этот файл +└── appdaemon/ # локальная копия для редактирования + ├── availability.py + ├── availability_utils.py + └── apps.yaml +``` + +## Компоненты + +### AppDaemon +- **Версия:** 4.5.13 +- **Addon slug:** `a0d7b954_appdaemon` +- **Python:** 3.12.13 +- **Конфиг:** `/addon_configs/a0d7b954_appdaemon/appdaemon.yaml` +- **Таймзона:** Europe/Moscow + +### availability.py +Класс `Availability(hass.Hass)`: +- **Cold-start:** полный пересчёт через 30 сек после запуска +- **Расписания:** 24ч/5мин, 7д/15мин, 30д/2ч +- **Подписка:** `input_select.avail_period` — пересчёт при переключении +- **REST API:** `http://supervisor/core/api` с SUPERVISOR_TOKEN +- **WebSocket:** `ws://homeassistant:8123/api/websocket` с ha_token из args + +Методы: +| Метод | Назначение | +|-------|-----------| +| `_fetch_entities()` | Получение списка устройств + фильтрация | +| `_fetch_areas()` | WebSocket → area/entity/device registry → маппинг | +| `_fetch_history()` | Batch-запросы по 20 entity, пауза 1 сек | +| `_calc_period()` | Полный цикл расчёта для периода | +| `_set_progress()` | Обновление sensor.avail_calc_progress | + +### availability_utils.py +| Функция | Назначение | +|---------|-----------| +| `is_excluded()` | Фильтрация entity по правилам исключения | +| `sanitize_entity_id()` | `light.bra` → `light_bra` | +| `sanitize_area_name()` | `Без комнаты` → `bez_komnaty` (транслитерация) | +| `compute_availability()` | Расчёт pct, down_count, max_downtime, last_downtime | +| `compute_sparkline()` | Ежедневные точки за N дней | +| `get_color()` | ≥99→green, 95-99→yellow, 90-95→orange, <90→red | +| `calc_trend()` | Сравнение с предыдущим периодом (up/down/stable) | + +### Sensors +- `sensor.avail_` — доступность устройства (state = %) +- `sensor.avail_area_` — средняя доступность по комнате +- `sensor.avail_calc_progress` — прогресс расчёта (`"1/2"` или `"idle"`) + +### input_select +- `input_select.avail_period` — переключатель периода (24h / 7d / 30d) + +## Аутентификация + +| Канал | URL | Токен | +|-------|-----|-------| +| REST API | `http://supervisor/core/api` | SUPERVISOR_TOKEN (авто) | +| WebSocket | `ws://homeassistant:8123/api/websocket` | ha_token (Long-Lived Access Token из apps.yaml) | + +⚠️ SUPERVISOR_TOKEN **не подходит** для прямого WebSocket к HA — только для REST через supervisor proxy. + +## Известные баги / ограничения + +1. **Sparkline для 24ч** — даёт 1 точку, а не 7. Нужна логика по часам +2. **Area cache** — `_fetch_areas()` запрашивает registry при каждом расчёте. Можно кэшировать +3. **30д тренд** — запрашивает 60д истории, нужен `purge_keep_days ≥ 65` +4. **Новые устройства** — `compute_availability` вернёт 100% (нет истории = нет падений). Корректно, но не информативно +5. **`minimal_response`** — HA может не возвращать entity_id в каждой записи. Fallback на первый entity из батча + +## Деплой + +```bash +# Обновить файлы на HA +SKILL=~/.openclaw/skills/installer/scripts +$SKILL/ssh_exec.sh --host ha --cmd "cat > /addon_configs/a0d7b954_appdaemon/apps/availability.py << 'ENDOFFILE' +$(cat appdaemon/availability.py) +ENDOFFILE" + +$SKILL/ssh_exec.sh --host ha --cmd "cat > /addon_configs/a0d7b954_appdaemon/apps/availability_utils.py << 'ENDOFFILE' +$(cat appdaemon/availability_utils.py) +ENDOFFILE" + +# Перезапустить AppDaemon +$SKILL/ssh_exec.sh --host ha --cmd "ha addons restart a0d7b954_appdaemon" +``` + +## TODO + +- [ ] Назначить areas устройствам в HA +- [ ] Увеличить purge_keep_days до 35 +- [ ] Установить button-card через HACS +- [ ] Построить Lovelace-дашборд +- [ ] Расширить на другие домены (sensor, binary_sensor, climate, ...) +- [ ] Кэшировать area registry (не запрашивать каждый цикл) +- [ ] Исправить sparkline для 24ч (часовые точки вместо дневных) diff --git a/tasks/ha-availability-dashboard/TZ.md b/tasks/ha-availability-dashboard/TZ.md index 52b5517..9536e36 100644 --- a/tasks/ha-availability-dashboard/TZ.md +++ b/tasks/ha-availability-dashboard/TZ.md @@ -97,7 +97,10 @@ sensor.avail_light_bra_v_spalne: **Путь приложений:** `/addon_configs/a0d7b954_appdaemon/apps/` **Путь apps.yaml:** `/addon_configs/a0d7b954_appdaemon/apps/apps.yaml` **Slug аддона:** `a0d7b954_appdaemon` -**Подключение к HA:** автоматическое через SUPERVISOR_TOKEN (WebSocket), ha_url/token НЕ указывать +**Подключение к 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`. После обкатки расширяем на остальные. @@ -239,25 +242,33 @@ def calc_availability(history_entries, period_start, period_end): - **button-card** — для строк устройств с прогресс-баром - **custom:hui-element** — для input_select на дашборде (или использовать стандартный entities card) -### AppDaemon: как писать приложение +### AppDaemon: реализация -```python -import appdaemon.plugins.hass.hassapi as hass +**Модули:** +- `availability.py` — класс `Availability(hass.Hass)`, основная логика +- `availability_utils.py` — чистые функции (фильтрация, расчёт, форматирование) -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") +**Ключевые решения при реализации:** +- `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: # для WebSocket registry ``` +**⚠️ Секреты:** `ha_token` хранится в apps.yaml на HA. Не дублировать в других файлах. + ### Нагрузка - ~180 устройств - History API batch-запрос (можно передать несколько entity_id через запятую) @@ -275,4 +286,24 @@ class Availability(hass.Hass): 3. ~~Установить AppDaemon~~ → ✅ Установлен (4.5.13) 4. ~~Подтвердить список исключений~~ → ✅ Согласовано (см. выше) 5. Доустановить **button-card** через HACS -6. Создать `input_select.avail_period` (опции: 24ч, 7д, 30д) +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-дашборд +- Расширить на другие домены (после обкатки)