Files
fgl-aircon/docs/PLAN_ESPHOME.md
Petr Polezhaev 157df4e795 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

154 lines
8.4 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.
# План: внешний компонент ESPHome (`fglair`)
Аудитория — агенты-реализаторы. Протокол — `docs/PROTOCOL.md`; ядро —
`docs/PLAN_CORE_LIBRARY.md` (`fglair-core`, C++20). Компонент строится по
образцу штатных climate-модулей ESPHome (midea, hisense-ac, tuya), но протокол
вынесен в переиспользуемое C++-ядро.
Требования к среде: **только ESP-IDF framework** (Arduino-фреймворк ESPHome
считается устаревшим и не поддерживается). Ядро — C++20 без исключений/RTTI,
что совместимо с дефолтными флагами сборки ESPHome для IDF.
## 1. Распределение кода
```
esphome-fglair/ (external component, установка через
components/fglair/ external_components: - source: github://...)
__init__.py # FglairHub: Component; владеет FglSession ядра
climate.py # FglairClimate : climate::Climate
sensor.py # комнатная температура, error_code, op_status-флаги
switch.py # economy/powerful/coil_dry/min_heat/...
select.py # положения заслонок (v2)
binary_sensor.py # connectivity
config_validation.py
const.py
core/ # git-subtree/symlink fglair-core (src+include+platform/esp-idf)
CMakeLists.txt # STATIC LIBRARY; REQUIRES lwip esp_timer mbedtls
translations/
```
Ядро компилируется как статическая библиотека через CMakeLists компонента;
платформенный слой — `platform/esp-idf` (lwip-сокеты, esp_timer, esp_random).
## 2. YAML-конфигурация
```yaml
external_components:
- source: github://<user>/esphome-fglair@main
components: [fglair]
fglair:
devices:
- id: ac_living
ip_address: 192.168.88.3 # либо dsn + mdns: true (запрос на :10276)
dsn: AC000W002879281
lanip_key: 1uWOP3nGbSz2ysEfjJJVu+P0fxAQTg==
lanip_key_id: 62888
template: A # A|B|F
# port: 10275 # локальный порт сервера (default)
# keepalive: 15s
climate:
- platform: fglair
device_id: ac_living
name: "Кондиционер"
# swing: both # off|vertical|horizontal|both
sensor:
- platform: fglair
device_id: ac_living
room_temperature: {name: "Температура в комнате"}
error_code: {name: "Код ошибки"}
switch:
- platform: fglair
device_id: ac_living
economy: {name: "Эко"}
powerful: {name: "Мощный"}
coil_dry: {name: "Осушка змеевика"}
min_heat: {name: "Мин. обогрев"}
outdoor_low_noise: {name: "Тихий наружный блок"}
wifi_led: {name: "LED Wi-Fi"}
```
**Откуда брать ключ**: у пользователя обычно уже есть HA-интеграция (или
запускается `fglair-discover` CLI из репозитория ядра). HA-диагностика
устройства показывает `lanip_key`/`lanip_key_id` — значения копируются в YAML
вручную. Ключ статичен (зашит в модуль), автоматическая синхронизация не
предусмотрена.
**Поведение при смене ключа**: ядро отвечает 412 и переходит в `key_error`;
компонент логирует ошибку с текстом «lanip_key устарел, обновите конфиг» и
останавливает сессию (не долбит модуль). Обновление — правка YAML вручную.
## 3. Компонент `fglair` (hub, `__init__.py`)
* `FglairHub : public Component` — на каждое `device` создаёт `FglSession`
(API ядра) при `setup()`; колбэки ядра приходят из его внутренней задачи —
мост в main-loop ESPHome через `Component::defer()`.
* `dump_config()`: версия ядра, состояние, статистика (re-keys, команды,
lost-push), измеренный возраст re-key.
* `loop()`: поллинг mailbox (транзакции из main-loop в ядро — тоже через
mailbox ядра, ядро однопоточное внутри).
* Зависимости: `network`; старт сессии только после `network::is_connected()`;
при смене IP самой ESP ядро перерегистрируется само (local_reg с новым ip).
* Доступность: таймаут watchdog 60 с без push и без успешного local_reg →
entities в NaN/unavailable; восстановление ядром (backoff) возвращает.
## 4. `climate.py`
* `FglairClimate : public climate::Climate, public Component`:
* `traits()`: modes OFF/COOL/HEAT/DRY/FAN_ONLY/AUTO (фильтр по
`device_capabilities`); fan quiet/low/medium/high/auto; swing off/vertical/
horizontal/both; step 0.5 °C (шаблон B — 1.0 °C); min/max 16–30 °C;
* `control(const ClimateCall&)`: все изменения вызова — в ОДИН
`batch_begin()/batch_commit()` ядра (один local_reg notify на действие);
OFF → `operation_mode=0`; turn_on → `operation_mode=1`;
* `current_temperature` ← `display_temperature`; остальные значения из кэша
ядра; push-колбэк ядра → `publish_state()`;
* записи не эхируются (PROTOCOL.md §5.3) — optimistic update в кэше ядра,
state публикуется сразу;
* presets: `CLIMATE_PRESET_ECO` / `CLIMATE_PRESET_BOOST` → economy_mode /
powerful_mode.
## 5. Остальные платформы
* `sensor.py`: `room_temperature` (°C, точность 0.25), `error_code`,
`op_status` (text-флаги defrost/oil_recovery/pump_down/maintenance/
check_operation/запреты — по битовой маске PROTOCOL.md §8.4).
* `switch.py`: bool-свойства; `write_state` → `set_bool` ядра.
* `select.py` (v2): `af_vertical_direction`/`af_horizontal_direction`, N опций
из `af_*_num_dir`.
* `binary_sensor.py`: `connectivity` = ONLINE.
## 6. Ограничения и требования
* Платы: esp32/esp32s3/esp32c3 (lwip + ≥ 25–30 КБ свободной RAM под сессию
и буферы ядра; для c3 проверить стек задачи ядра).
* Порт 10275 (или настраиваемый) должен быть свободен; конфликт с `api`
(6053)/`ota` исключён.
* **Одно устройство на ESP** в v1 (мульти-устройства — M6 ядра/FglHub).
* Колбэки ядра — не в main-loop; мост через `defer()` обязателен.
* OTA-обновление ESPHome поверх живой сессии: `stop()` в `on_shutdown`-
триггере не гарантируется — слот на модуле освободится сам по таймауту
(< 2 мин); ядро переживает это штатно (проверено на приборе).
## 7. Тесты и приёмка
1. CI: `esphome compile` для тестовых конфигов (esp32-idf, esp32s3-idf).
2. Mock-модуль ядра + локальная сборка компонента — smoke: старт, online,
одна запись, публикация state.
3. На приборе: чек-лист M5 ядра + OTA-ребут поверх сессии + 24 ч uptime
под управлением из HA через native API.
4. Приёмка: 24 ч без рассинхронов; параллельно телефон (2 слота); изменение
с пульта отражается в HA < 1 с.
## 8. Этапы
| # | Содержимое |
|---|-----------|
| E1 | каркас external component, компиляция ядра (esp-idf), hub + connectivity |
| E2 | climate (mode/fan/temp/swing, batch-запись, optimistic state) |
| E3 | sensor/switch, capabilities-фильтры, translations, README с YAML |
| E4 | select (заслонки), mdns-опция (:10276), CI, релиз |