- 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 исключены из версионирования.
8.4 KiB
План: внешний компонент 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-конфигурация
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. Тесты и приёмка
- CI:
esphome compileдля тестовых конфигов (esp32-idf, esp32s3-idf). - Mock-модуль ядра + локальная сборка компонента — smoke: старт, online, одна запись, публикация state.
- На приборе: чек-лист M5 ядра + OTA-ребут поверх сессии + 24 ч uptime под управлением из HA через native API.
- Приёмка: 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, релиз |