- major: diagnostics больше не содержит полный lanip_key (esp_home — маска, entry_data redacted); ключ для ESPHome — через reconfigure/CLI; план §3 синхронизирован; дамп при незагруженной записи не падает - reconfigure: сохраняет listen_port/keepalive_ms и текущие overrides/ temp_step; превью-дефолты подхватывают значения записи - repair fix-flow: подтверждение перезагружает запись (issue снимается только когда сессия вышла из key_error; иначе создаётся заново) - key_issue_id helper, использование async_remove_issue (мёртвый код), CONF_EMAIL/PASSWORD в TO_REDACT, placeholders в fix_flow, тест плейсхолдеров ru - тесты обновлены (47 components)
167 lines
12 KiB
Markdown
167 lines
12 KiB
Markdown
# План: интеграция Home Assistant (`custom_components/fglair`)
|
||
|
||
Аудитория — агенты-реализаторы. Протокол — `docs/PROTOCOL.md`; ядро —
|
||
`docs/PLAN_CORE_LIBRARY.md` (`fgl-aircon`, C++20, монорепо: библиотека в
|
||
корне, компонент HA здесь).
|
||
|
||
## 1. Архитектура
|
||
|
||
```
|
||
Home Assistant (custom component `fglair`)
|
||
│ использует python-пакет pyfglair (cffi-bindings к libfgl-aircon.so)
|
||
▼
|
||
pyfglair (wheel: linux x86_64/aarch64; cffi; собирает библиотеку из корня
|
||
репозитория через cmake)
|
||
│
|
||
▼
|
||
fgl-aircon (C++, корень монорепо) ←—— тот же код, что и на ESP32 (ESP-IDF)
|
||
```
|
||
|
||
Компонент — тонкий: переводит API библиотеки в сущности HA. Вся протокольная
|
||
логика (сессия, шифрование, pacing, re-key, восстановление) — в C-ядре.
|
||
|
||
Обоснование: переиспользование библиотеки (требование владельца); cffi-wheel
|
||
— рабочий путь (HA-контейнеры x86_64/aarch64 debian; cibuildwheel).
|
||
Fallback, если сборка wheel станет блокером: сборка .so при старте в docker
|
||
(dev-режим). Чисто-Python повторная реализация протокола — запрещена.
|
||
|
||
## 2. Состав
|
||
|
||
1. **`pyfglair`** (python-пакет):
|
||
* `pyfglair/_corebuild.py` — cmake-сборка при упаковке wheel;
|
||
* `pyfglair/_cffi.py` — cffi-декларации поверх `include/fgl-aircon/c_api.h`;
|
||
* `pyfglair/session.py` — обёртка `Session(cfg)`; колбэки ядра → asyncio
|
||
через `loop.call_soon_threadsafe`. Транспорт событий — C-очередь
|
||
(`fgl_session_wait_events`) и выделенный python-поток: прямые cffi-колбэки
|
||
из потоков ядра небезопасны (у session-потока стек 8 КБ — CPython
|
||
падает по stack overflow);
|
||
* `pyfglair/templates.py` — интроспекция шаблонов через cffi-вызовы
|
||
`fgl_template_info_get` / `fgl_convert_to_display` (для превью в
|
||
config flow; единый источник данных — C-таблицы, дублей нет);
|
||
* `pyfglair/provision.py` — облачный discovery (перенос логики
|
||
`docs/legacy/aircon/discovery.py` на современный aiohttp): e-mail/
|
||
пароль/регион → dsn, host/ip, oem_model, lanip_key, lanip_key_id;
|
||
* CLI: `python -m pyfglair discover` / `monitor` / `--format esphome-secrets`
|
||
(печать блока для secrets.yaml ESPHome).
|
||
2. **`custom_components/fglair/`**:
|
||
```
|
||
manifest.json # requirements: ["pyfglair>=1.0.0"], config_flow: true
|
||
config_flow.py # flow + options + repair
|
||
fglair_client.py # фоновый поток с FglSession
|
||
coordinator.py # push-driven coordinator
|
||
climate.py sensor.py switch.py select.py binary_sensor.py
|
||
diagnostics.py # lanip_key/key_id/dsn видны для копирования в ESPHome
|
||
translations/{en,ru}.json
|
||
```
|
||
|
||
## 3. Config flow
|
||
|
||
Шаг 1 — **подключение** (один из вариантов):
|
||
* A (облачный): e-mail/пароль FGLair + регион → список устройств
|
||
(name, model, host) → выбор.
|
||
* B (ручной): host/dsn/lanip_key/lanip_key_id по полям, либо импорт
|
||
`config_*.json` (миграция с legacy).
|
||
|
||
Шаг 2 — **пробное подключение**: старт сессии, ожидание ONLINE ≤10 с,
|
||
чтение базовых свойств. Ошибка → назад с сообщением.
|
||
|
||
Шаг 3 — **выбор шаблона С ПРЕВЬЮ РЕЗУЛЬТАТОВ** (требование): после выбора
|
||
шаблона (обычно определён по oem_model автоматически) — форма-предпросмотр
|
||
рассчитанных значений через `pyfglair.templates` (вызовы в C-ядро):
|
||
|
||
| Поле превью | Пример значения |
|
||
|---|---|
|
||
| hvac-режимы | off, cool, dry, fan, heat, auto (operation_mode 0–6) |
|
||
| fan-режимы | quiet/low/medium/high/auto (0–4) |
|
||
| Диапазон уставки | 16.0–30.0 °C, шаг 0.5 (adjust_temperature raw 160–300, ×0.1) |
|
||
| Текущая температура | display_temperature 7000 → 20.0 °C |
|
||
| Заслонки | vertical 0–4 (af_vertical_num_dir), horizontal 0–6 |
|
||
| Битмаска capabilities | heat, cool, economy, powerful, min_heat, swing … |
|
||
|
||
Пользователь оценивает корректность (сверяет с приложением FGLair) и
|
||
подтверждает → создаётся `ConfigEntry`. При расхождении — возможность
|
||
выбрать другой шаблон или задать конверсию коэффициентами (linear:
|
||
num/den/offset) и диапазон вручную на этом же шаге.
|
||
|
||
Repair (теоретический): `key_error` → «Ключ устройства изменён —
|
||
перепровижинируйте» (повторный облако-вход по требованию). Облако в рантайме
|
||
не используется, ключ статичен.
|
||
|
||
**Диагностика**: device-страница показывает `dsn`, `lanip_key_id`, `host` и
|
||
маску `lanip_key` (дамп прикладывают в issues — секретов в нём нет). Полный
|
||
ключ для secrets ESPHome берётся явным действием: reconfigure («Настроить
|
||
заново») на карточке интеграции или CLI `python -m pyfglair discover` на
|
||
хосте HA.
|
||
|
||
## 4. Сущности
|
||
|
||
Как раньше (climate: hvac/fan/swing/preset ECO-BOOST; current_temperature ←
|
||
display_temperature; sensors: room temp, error_code, op_status-флаги;
|
||
switch: economy/powerful/coil_dry/min_heat/outdoor_low_noise/
|
||
human_det_auto_save/wifi_led/indoor_fan_control; select: заслонки,
|
||
demand_control; binary_sensor: connectivity). capabilities фильтруют
|
||
режимы/пресеты. Записи — один `batch_commit()` на действие пользователя.
|
||
Demand_control: семантика значений в PROTOCOL §8.3 не зафиксирована
|
||
(§10) — сущность не создаётся до уточнения; в H3 вместо неё
|
||
диагностический sensor состояния связи.
|
||
|
||
## 5. Runtime
|
||
|
||
Один `FglairClient` на `ConfigEntry` (daemon-thread, сессия ядра); колбэки →
|
||
asyncio → push-coordinator. Состояния: `online` → available;
|
||
`recovering` → доступны (последние значения) + diagnostic-сенсор;
|
||
`offline` → unavailable; `key_error` → unavailable + repair. Keep-alive 15 с
|
||
(самолечение десинхрона ≤15 с). Выгрузка: `stop()` (delete_session).
|
||
|
||
## 6. README компонента (после реализации; для людей, коротко)
|
||
|
||
Структура (`screenshots/step-N.png` — заглушки-плейсхолдеры, владелец заменит
|
||
реальными скриншотами; рядом с каждой — описание что должно быть видно):
|
||
|
||
1. **Установка через HACS**:
|
||
* HACS → ⋮ → Custom repositories → URL репозитория, категория
|
||
Integration → Add. Скриншот: диалог добавления custom repository с
|
||
заполненным URL и выбранной категорией Integration.
|
||
* FGLair → Download → перезапуск HA. Скриншот: страница загрузки
|
||
интеграции с кнопкой Download (версия видна).
|
||
2. **Добавление устройства**: Settings → Devices & Services → Add
|
||
Integration → «FGLair». Скриншот: диалог поиска интеграции с введённым
|
||
«FGLair» и выделенным результатом.
|
||
3. **Вход в облако** (шаг 1A): e-mail/пароль/регион. Скриншот: форма с
|
||
заполненными регионом EU и e-mail (пароль скрыт).
|
||
4. **Выбор устройства**: список найденных кондиционеров. Скриншот: список
|
||
с одним устройством (имя, модель, host).
|
||
5. **Проверка шаблона с превью** (шаг 3): Скриншот: форма превью — таблица
|
||
рассчитанных значений (режимы, диапазон температур, пример конверсии
|
||
температуры), кнопки Confirm/Change template.
|
||
6. **Готово**: карточка устройства со списком сущностей. Скриншот: страница
|
||
устройства с созданными climate/sensor/switch сущностями.
|
||
7. **Где взять ключ для ESPHome**: диагностика устройства. Скриншот: страница
|
||
Diagnostics с полями dsn/lanip_key/lanip_key_id.
|
||
8. Troubleshooting: 503 (оба слота заняты — телефон+ESP?), key_error,
|
||
недоступность.
|
||
|
||
## 7. Приёмка (полуавтоматическая, `tests/acceptance/test_esphome_ha.py`)
|
||
|
||
Совместно с ESPHome-компонентом (топология и детали — PLAN_ESPHOME §8).
|
||
Со стороны HA скрипт использует **long-lived access token** (профиль →
|
||
Security → Long-lived access tokens) и REST API: вызов сервисов
|
||
`/api/services/climate/set_temperature|set_hvac_mode|set_fan_mode|set_swing_mode`
|
||
и чтение `/api/states/<entity_id>`. Возможность подтверждена: это
|
||
стандартный документированный механизм HA REST API, отдельная авторизация
|
||
(OAuth-флоу) не нужна — пользователь просто создаёт токен и передаёт
|
||
скрипту (`--ha-url`, `--ha-token`).
|
||
Режимы: quick (матрица burst/не-burst, обе стороны, с возвратом) и `--long`
|
||
(24 ч, ежечасное изменение с проверкой и возвратом, CSV-отчёт). Скрипт НЕ
|
||
открывает собственную сессию к кондиционеру (оба слота заняты HA+ESP).
|
||
|
||
## 8. Этапы
|
||
|
||
| # | Содержимое |
|
||
|---|-----------|
|
||
| H1 ✅ | wheel `pyfglair` (сборка из корня монорепо), cffi-обёртки, CLI discover/monitor |
|
||
| H2 ✅ | компонент: manifest, config flow (облако/ручной/импорт) + пробное подключение |
|
||
| H3 ✅ | шаг «превью шаблона» с ручными конверсиями; climate + сущности |
|
||
| H4 ✅ | repair, диагностика (ключ для ESPHome), translations |
|
||
| H5 | README с HACS-инструкцией и заглушками скриншотов (§6), скрипт приёмки (§7), HACS-релиз; публикация `pyfglair` на PyPI (manylinux x86_64/aarch64, cibuildwheel) — штатный installer HA резолвит `requirements` только оттуда |
|