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 исключены из версионирования.
This commit is contained in:
153
docs/PLAN_ESPHOME.md
Normal file
153
docs/PLAN_ESPHOME.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# План: внешний компонент 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, релиз |
|
||||
Reference in New Issue
Block a user