docs: корректировки планов — монорепо, слои ayla/aircon, конверсии, приёмка
- PLAN_CORE: монорепозиторий (CMakeLists в корне; components/fglair,
custom_components/fglair, include/fgl-aircon, src/{ayla,aircon},
src/ayla/platform); логическое разделение ayla (протокол+цикл) /
aircon (конверсии+шаблоны+API); тесты зеркалят слои (tests/{ayla,aircon});
конверсии: шаблон / линейные коэффициенты / функция-указатель
(лямбды ESPHome); оценка httpd/json (jsmn вендор, свой мини-httpd на
BSD-сокетах, conan не нужен); README библиотеки в M4.
- PLAN_ESPHOME: host вместо ip_address (DNS + Ayla-mDNS :10276),
секреты в примерах, кастомные конверсии через !lambda, advanced-пример
(триггеры режимов + LVGL с пропусками), README-план, скрипт приёмки
(aioesphomeapi + HA REST).
- PLAN_HOME_ASSISTANT: шаг config flow с превью рассчитанных значений
шаблона (через cffi в C-ядро, без дублей), README с HACS-инструкцией
и заглушками под скриншоты с описаниями, приёмка (long-lived token).
- Убраны реальные dsn/ip/lanip_key/key_id из примеров; probe_mdns.py
принимает DSN аргументом.
This commit is contained in:
@@ -1,146 +1,158 @@
|
||||
# План: интеграция для Home Assistant (`fglair` custom component)
|
||||
# План: интеграция Home Assistant (`custom_components/fglair`)
|
||||
|
||||
Аудитория — агенты-реализаторы. Протокол — `docs/PROTOCOL.md`, библиотека —
|
||||
`docs/PLAN_CORE_LIBRARY.md` (`fglair-core`, C++20).
|
||||
Аудитория — агенты-реализаторы. Протокол — `docs/PROTOCOL.md`; ядро —
|
||||
`docs/PLAN_CORE_LIBRARY.md` (`fgl-aircon`, C++20, монорепо: библиотека в
|
||||
корне, компонент HA здесь).
|
||||
|
||||
## 1. Архитектура
|
||||
|
||||
```
|
||||
Home Assistant (custom component `fglair`)
|
||||
│ использует python-пакет pyfglair (cffi-bindings к libfglair-core.so)
|
||||
│ использует python-пакет pyfglair (cffi-bindings к libfgl-aircon.so)
|
||||
▼
|
||||
pyfglair (wheel: linux x86_64/aarch64; cffi; собирает fglair-core через cmake)
|
||||
pyfglair (wheel: linux x86_64/aarch64; cffi; собирает библиотеку из корня
|
||||
репозитория через cmake)
|
||||
│
|
||||
▼
|
||||
fglair-core (C++) ←—— mock-тесты; тот же код, что и на ESP32 (ESP-IDF)
|
||||
fgl-aircon (C++, корень монорепо) ←—— тот же код, что и на ESP32 (ESP-IDF)
|
||||
```
|
||||
|
||||
Компонент — тонкий: переводит API библиотеки в сущности HA. Вся протокольная
|
||||
логика (сессия, шифрование, pacing, re-key, восстановление) — в C-ядре.
|
||||
|
||||
Обоснование: пользователь требует переиспользования библиотеки из п.2;
|
||||
cffi-wheel — рабочий путь (HA-контейнеры: x86_64/aarch64 debian; cibuildwheel
|
||||
покрывает). Fallback (если сборка wheel станет блокером): libfglair собирается
|
||||
в docker при старте интеграции один раз (dev-mode) — не рекомендуется для
|
||||
продакшна; чисто-Python повторная реализация протокола — запрещена (дубль
|
||||
логики, расползание с ESP32-веткой).
|
||||
Обоснование: переиспользование библиотеки (требование владельца); cffi-wheel
|
||||
— рабочий путь (HA-контейнеры x86_64/aarch64 debian; cibuildwheel).
|
||||
Fallback, если сборка wheel станет блокером: сборка .so при старте в docker
|
||||
(dev-режим). Чисто-Python повторная реализация протокола — запрещена.
|
||||
|
||||
## 2. Состав репозиториев
|
||||
## 2. Состав
|
||||
|
||||
1. **`pyfglair`** (python-пакет):
|
||||
* `pyfglair/_corebuild.py` — cmake-сборка при упаковке wheel;
|
||||
* `pyfglair/_cffi.py` — cffi-декларации поверх `include/fgl/c_api.h`
|
||||
(extern "C"-шейм ядра);
|
||||
* `pyfglair/session.py` — pythonic-обёртка: `Session(cfg)`, колбэки ядра
|
||||
мостятся в `asyncio` через `loop.call_soon_threadsafe`;
|
||||
* `pyfglair/provision.py` — облачный discovery (перенос `docs/legacy/aircon/discovery.py`
|
||||
на современный aiohttp): вход e-mail/пароль/регион → dsn, ip, oem_model,
|
||||
lanip_key, lanip_key_id;
|
||||
* CLI: `python -m pyfglair discover` / `python -m pyfglair monitor`.
|
||||
2. **`ha-fglair`** (custom component, HACS-совместимый):
|
||||
* `custom_components/fglair/…` (см. §4);
|
||||
* `manifest.json`: `requirements: ["pyfglair>=1.0.0"]`, `domain: fglair`,
|
||||
`config_flow: true`, `iot_class: local_push`.
|
||||
* `pyfglair/_cffi.py` — cffi-декларации поверх `include/fgl-aircon/c_api.h`;
|
||||
* `pyfglair/session.py` — обёртка `Session(cfg)`; колбэки ядра → asyncio
|
||||
через `loop.call_soon_threadsafe`;
|
||||
* `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` / `--output 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)
|
||||
## 3. Config flow
|
||||
|
||||
Вариант A — облачный (удобный):
|
||||
1. Пользователь вводит e-mail/пароль FGLair + регион EU/US/CN.
|
||||
2. Интеграция через `pyfglair.provision` получает список устройств
|
||||
(name, model, ip, dsn, lanip_key, lanip_key_id) и показывает их.
|
||||
3. Выбор устройства → `ConfigEntry` (те же поля, что `config_kata.json`)
|
||||
→ шаг «пробное подключение» (старт сессии, ожидание ONLINE ≤ 10 с).
|
||||
Шаг 1 — **подключение** (один из вариантов):
|
||||
* A (облачный): e-mail/пароль FGLair + регион → список устройств
|
||||
(name, model, host) → выбор.
|
||||
* B (ручной): host/dsn/lanip_key/lanip_key_id по полям, либо импорт
|
||||
`config_*.json` (миграция с legacy).
|
||||
|
||||
Вариант B — ручной: ввод ip/dsn/lanip_key/lanip_key_id по полям или импорт
|
||||
существующего `config_*.json` (миграция с legacy-скрипта).
|
||||
Шаг 2 — **пробное подключение**: старт сессии, ожидание ONLINE ≤10 с,
|
||||
чтение базовых свойств. Ошибка → назад с сообщением.
|
||||
|
||||
Ключ считается **статичным** (см. PLAN_CORE_LIBRARY §8): облако используется
|
||||
только здесь, при настройке. В рантайме — никогда.
|
||||
Шаг 3 — **выбор шаблона С ПРЕВЬЮ РЕЗУЛЬТАТОВ** (требование): после выбора
|
||||
шаблона (обычно определён по oem_model автоматически) — форма-предпросмотр
|
||||
рассчитанных значений через `pyfglair.templates` (вызовы в C-ядро):
|
||||
|
||||
Repair-флоу (редкий, теоретический):
|
||||
* Состояние ядра `key_error` (несовпадение `key_id`) → repair «Ключ устройства
|
||||
изменён — перепровижинируйте». Повторный вход в облако по требованию
|
||||
пользователя, обновление полей ConfigEntry. Никаких автоматических
|
||||
перечитываний облака.
|
||||
| Поле превью | Пример значения |
|
||||
|---|---|
|
||||
| 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 … |
|
||||
|
||||
**Диагностика**: Device-страница HA показывает `lanip_key`, `lanip_key_id`,
|
||||
`dsn`, ip — чтобы пользователь мог скопировать их в конфиг ESPHome (см.
|
||||
PLAN_ESPHOME). В redacted-дамп lanip_key маскируется, в полном — виден.
|
||||
Пользователь оценивает корректность (сверяет с приложением FGLair) и
|
||||
подтверждает → создаётся `ConfigEntry`. При расхождении — возможность
|
||||
выбрать другой шаблон или задать конверсию коэффициентами (linear:
|
||||
num/den/offset) и диапазон вручную на этом же шаге.
|
||||
|
||||
## 4. Структура компонента
|
||||
Repair (теоретический): `key_error` → «Ключ устройства изменён —
|
||||
перепровижинируйте» (повторный облако-вход по требованию). Облако в рантайме
|
||||
не используется, ключ статичен.
|
||||
|
||||
```
|
||||
custom_components/fglair/
|
||||
manifest.json
|
||||
config_flow.py # flow + options flow + repair
|
||||
const.py
|
||||
fglair_client.py # FglairClient: владеет FglSession (фоновый поток)
|
||||
coordinator.py # push-driven coordinator (polling-интервал только watchdog)
|
||||
climate.py # ClimateEntity
|
||||
sensor.py # комнатная температура, error_code, op_status-флаги
|
||||
switch.py # economy, powerful, coil_dry, min_heat, outdoor_low_noise,
|
||||
# human_det_auto_save, wifi_led, indoor_fan_control
|
||||
select.py # af_vertical/horizontal_direction (если N>1), demand_control
|
||||
binary_sensor.py # connectivity
|
||||
diagnostics.py
|
||||
translations/{en,ru,...}.json
|
||||
```
|
||||
**Диагностика**: device-страница показывает `dsn`, `lanip_key`, `lanip_key_id`,
|
||||
`host` — источник для копирования в secrets ESPHome. В redacted-дампе ключ
|
||||
маскируется.
|
||||
|
||||
## 5. Маппинг сущностей (шаблон A; B/F — по capabilities)
|
||||
## 4. Сущности
|
||||
|
||||
**Climate**:
|
||||
* HVAC: `OFF`↔`operation_mode=0`; `COOL/HEAT/DRY/FAN_ONLY/AUTO`↔3/6/4/5/2;
|
||||
`turn_on` → `operation_mode=1`.
|
||||
* `target_temperature` ↔ `adjust_temperature` (×0.1 °C, шаг 0.5 °C);
|
||||
* `current_temperature` ← `display_temperature` ((v−5000)/100);
|
||||
* `fan_mode` ↔ `fan_speed` (quiet/low/medium/high/auto);
|
||||
* `swing_mode` ↔ `af_vertical_swing`+`af_horizontal_swing` (off/vertical/
|
||||
horizontal/both);
|
||||
* presets: `ECO`→economy_mode, `BOOST`→powerful_mode (взаимоисключающие);
|
||||
* capabilities из `device_capabilities` скрывают недоступные режимы.
|
||||
Как раньше (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()` на действие пользователя.
|
||||
|
||||
**Sensors**: room temp, error_code (с текстами), op_status (флаги defrost /
|
||||
oil recovery / pump down / maintenance / check operation / запреты).
|
||||
**Binary sensor**: connectivity (ONLINE). **Switch/Select** — по списку §4.
|
||||
## 5. Runtime
|
||||
|
||||
Записи идут batch'ем на действие пользователя (одно `batch_commit()` на вызов
|
||||
`set_temperature`/`set_hvac_mode`/…) — ядро само склеит в один local_reg notify.
|
||||
Один `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. Runtime-модель
|
||||
## 6. README компонента (после реализации; для людей, коротко)
|
||||
|
||||
* Один `FglairClient` на `ConfigEntry`: фоновый daemon-thread с сессией ядра;
|
||||
колбэки → `asyncio` → coordinator push-update.
|
||||
* Состояния ядра транслируются: `online` → entities available; `recovering` —
|
||||
доступны (последние значения), помечено diagnostic-сенсором; `offline` →
|
||||
unavailable; `key_error` → unavailable + repair.
|
||||
* Keep-alive/rotация ключей/переподключение — полностью в ядре (15 с;
|
||||
самолечение десинхрона ≤15 с — PROTOCOL.md §4.4).
|
||||
* Выгрузка: `stop()` (delete_session, освобождает слот на модуле).
|
||||
Структура (`screenshots/step-N.png` — заглушки-плейсхолдеры, владелец заменит
|
||||
реальными скриншотами; рядом с каждой — описание что должно быть видно):
|
||||
|
||||
## 7. Требования к надёжности (acceptance)
|
||||
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,
|
||||
недоступность.
|
||||
|
||||
1. 24 ч непрерывной работы: 0 рассинхронов; в логе ядра — штатные re-key
|
||||
каждые 45–60 с; сущности обновляются < 1 с после изменений с пульта.
|
||||
2. Параллельная работа с приложением на телефоне: обе сессии живут (2 слота);
|
||||
без взаимных сбоев.
|
||||
3. Вкл/выкл питания модуля: восстановление ≤ 120 с (backoff).
|
||||
4. Burst 10 изменений уставки из UI: ≤ 2 local_reg notify.
|
||||
5. `key_error` (симуляция смены lanip_key_id): repair отрабатывает.
|
||||
6. Перезапуск HA: корректный delete_session, повторная регистрация.
|
||||
## 7. Приёмка (полуавтоматическая, `tests/acceptance/test_esphome_ha.py`)
|
||||
|
||||
## 8. Тесты
|
||||
Совместно с 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).
|
||||
|
||||
* `pytest` + mock `pyfglair` (fake session, scripted callbacks): flow, entities,
|
||||
repair, unload.
|
||||
* Интеграционный тест с mock-модулем (`test/integration/mock_ac.py` ядра) через
|
||||
настоящий wheel: полный цикл в CI.
|
||||
* Ручной чек-лист на реальном приборе (совпадает с M5 ядра).
|
||||
|
||||
## 9. Этапы
|
||||
## 8. Этапы
|
||||
|
||||
| # | Содержимое |
|
||||
|---|-----------|
|
||||
| H1 | wheel `pyfglair`: сборка, cffi-bindings, обёртка Session, CLI discover/monitor |
|
||||
| H2 | компонент: manifest, config_flow (облако + ручной + импорт), client/coordinator, climate |
|
||||
| H3 | sensor/switch/select/binary_sensor, capabilities, translations, диагностика с lanip_key |
|
||||
| H4 | repair-флоу, unload, тесты, публикация в HACS (custom) |
|
||||
| H1 | wheel `pyfglair` (сборка из корня монорепо), cffi-обёртки, CLI discover/monitor |
|
||||
| H2 | компонент: manifest, config flow (облако/ручной/импорт) + пробное подключение |
|
||||
| H3 | шаг «превью шаблона» с ручными конверсиями; climate + сущности |
|
||||
| H4 | repair, диагностика (ключ для ESPHome), translations |
|
||||
| H5 | README с HACS-инструкцией и заглушками скриншотов (§6), скрипт приёмки (§7), HACS-релиз |
|
||||
|
||||
Reference in New Issue
Block a user