Files
fgl-aircon/docs/PLAN_HOME_ASSISTANT.md
Petr Polezhaev 024b290b89 docs: реконструкция LAN-протокола FGLair, анализ legacy, планы fglair-core/HA/ESPHome
- PROTOCOL.md: полная спецификация (KDF, envelope/CBC-цепочка, local_reg,
  key exchange, commands.json 206/200, datapoint push, тайминги, таблицы
  свойств шаблонов A/B/F, облачный provisioning). Факты из APK помечены
  [APK], проверенные живыми экспериментами на AP-WC1E — [ПРОВЕРЕНО НА
  ПРИБОРЕ]: mDNS только :10276; лимит 2 LAN-сессии (3-я -> 503);
  принудительный re-key при возрасте сессии >= ~44с (единственный механизм
  самолечения десинхрона — 400/401 модуль игнорирует); записи не
  эхируются; delete_session освобождает слот.
- LEGACY_ANALYSIS.md: причины рассинхрона (keep-alive 1200с вместо 10-15с
  + seq_no-фильтр) и перегрузки модуля; требования к новой реализации.
- PLAN_CORE_LIBRARY.md: план C++20-библиотеки fglair-core (Linux + ESP-IDF),
  ключ lanip_key считается статичным, ротация — только ошибка + ручной
  перепровижининг.
- PLAN_HOME_ASSISTANT.md: pyfglair (cffi wheel) + custom component,
  облачный provisioning только в config flow, ключ виден в диагностике
  для копирования в ESPHome.
- PLAN_ESPHOME.md: external component, только ESP-IDF framework.
- tools/probe_reference.py: эталонный клиент протокола (проверен на
  приборе end-to-end); tools/probe_mdns.py — mDNS-проба.
- legacy/: снимок скрипта (апстрим gyro-labs/AirCon, вложенный клон не
  версионируется); apk/*.apk исключены из версионирования.
2026-09-17 19:44:49 +03:00

147 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# План: интеграция для Home Assistant (`fglair` custom component)
Аудитория — агенты-реализаторы. Протокол — `docs/PROTOCOL.md`, библиотека —
`docs/PLAN_CORE_LIBRARY.md` (`fglair-core`, C++20).
## 1. Архитектура
```
Home Assistant (custom component `fglair`)
│ использует python-пакет pyfglair (cffi-bindings к libfglair-core.so)
▼
pyfglair (wheel: linux x86_64/aarch64; cffi; собирает fglair-core через cmake)
│
▼
fglair-core (C++) ←—— mock-тесты; тот же код, что и на 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-веткой).
## 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 (перенос `legacy/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`.
## 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 с).
Вариант B — ручной: ввод ip/dsn/lanip_key/lanip_key_id по полям или импорт
существующего `config_*.json` (миграция с legacy-скрипта).
Ключ считается **статичным** (см. PLAN_CORE_LIBRARY §8): облако используется
только здесь, при настройке. В рантайме — никогда.
Repair-флоу (редкий, теоретический):
* Состояние ядра `key_error` (несовпадение `key_id`) → repair «Ключ устройства
изменён — перепровижинируйте». Повторный вход в облако по требованию
пользователя, обновление полей ConfigEntry. Никаких автоматических
перечитываний облака.
**Диагностика**: Device-страница HA показывает `lanip_key`, `lanip_key_id`,
`dsn`, ip — чтобы пользователь мог скопировать их в конфиг ESPHome (см.
PLAN_ESPHOME). В redacted-дамп lanip_key маскируется, в полном — виден.
## 4. Структура компонента
```
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
```
## 5. Маппинг сущностей (шаблон A; B/F — по capabilities)
**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` скрывают недоступные режимы.
**Sensors**: room temp, error_code (с текстами), op_status (флаги defrost /
oil recovery / pump down / maintenance / check operation / запреты).
**Binary sensor**: connectivity (ONLINE). **Switch/Select** — по списку §4.
Записи идут batch'ем на действие пользователя (одно `batch_commit()` на вызов
`set_temperature`/`set_hvac_mode`/…) — ядро само склеит в один local_reg notify.
## 6. Runtime-модель
* Один `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, освобождает слот на модуле).
## 7. Требования к надёжности (acceptance)
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, повторная регистрация.
## 8. Тесты
* `pytest` + mock `pyfglair` (fake session, scripted callbacks): flow, entities,
repair, unload.
* Интеграционный тест с mock-модулем (`test/integration/mock_ac.py` ядра) через
настоящий wheel: полный цикл в CI.
* Ручной чек-лист на реальном приборе (совпадает с M5 ядра).
## 9. Этапы
| # | Содержимое |
|---|-----------|
| 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) |