auto-sync: 2026-04-15 14:50:01
This commit is contained in:
131
tasks/ha-availability-dashboard/PROJECT.md
Normal file
131
tasks/ha-availability-dashboard/PROJECT.md
Normal file
@@ -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_<entity_id>` — доступность устройства (state = %)
|
||||
- `sensor.avail_area_<name>` — средняя доступность по комнате
|
||||
- `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ч (часовые точки вместо дневных)
|
||||
@@ -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: <Long-Lived Access 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-дашборд
|
||||
- Расширить на другие домены (после обкатки)
|
||||
|
||||
Reference in New Issue
Block a user