Add WiZ provisioning wizard

This commit is contained in:
Artem Kokos
2026-05-16 17:24:28 +07:00
parent 0a635115d4
commit 866a074c03
19 changed files with 2668 additions and 0 deletions

View File

@@ -0,0 +1,865 @@
# WiZ Provisioning Master Plan
Статус: design / implementation brief
Актуально на: 2026-05-16
Основной проект: `ignis_app`
Связанный проект: `../ignis-core`
## Зачем нужен этот документ
Этот документ нужен как рабочий implementation brief для добавления в `Ignis` мастера первичной посадки новых WiZ-ламп на домашний Wi-Fi без официального приложения WiZ.
Цель документа:
- быстро восстановить контекст через недели или месяцы без повторного ресерча;
- понимать, что именно реализуем сначала, а что откладываем;
- иметь точную карту действий по Flutter, Android native и интеграции с `ignis-core`;
- заранее зафиксировать технические риски и места, где потребуются ручные проверки и апрувы.
## Краткий вывод
Сделать это **реально**, но нужно разделять минимум два поколения/режима onboarding:
- `SoftAP / manual setup`: лампа поднимает сеть вида `WiZConfig_xxxx`, телефон временно подключается к ней и передаёт домашние Wi-Fi credentials.
- `BLE setup`: часть более новых устройств рекламируется и настраивается по Bluetooth.
Дополнительно у части устройств есть `Matter`, но его не стоит брать как первый путь реализации внутри `Ignis`.
### Почему задача вообще выглядит решаемой
Официальные материалы WiZ подтверждают:
- есть режим `Manual setup` через Wi-Fi лампы `WiZConfig_xxxx`;
- есть Bluetooth-based setup для новых устройств;
- есть отдельный локальный commissioning-порт `UDP 18266`, который используется только во время ввода устройства в строй.
Это означает, что задача не упирается в принципиальную закрытость экосистемы. Главная неизвестная не в том, можно ли это сделать, а в том, **какой именно payload нужно передать лампе при commissioning**.
## Границы задачи
### Что считаем целевым результатом
Пользователь открывает `ignis_app`, запускает мастер, выбирает домашнюю Wi-Fi сеть, вводит пароль, переводит лампу в pairing mode, приложение временно подключается к лампе, передаёт credentials, ждёт появления лампы в домашней сети и затем вызывает `POST /devices/rescan` на локальном сервере `ignis-core`.
### Что не входит в первую фазу
- полноценный iOS onboarding;
- Matter commissioner внутри `Ignis`;
- cloud-интеграции WiZ;
- поддержка всех возможных поколений устройств одновременно;
- идеальный cross-platform UX.
### Что делаем в первую очередь
1. Android-only onboarding.
2. Сначала `SoftAP`-ветка.
3. После неё `BLE`-ветка.
4. `Matter` рассматривать только как отдельный fallback/будущее расширение.
## Текущий статус реализации
По состоянию на 2026-05-16 в `ignis_app` уже есть первая рабочая Android-first реализация мастера, но она закрывает только часть общей задачи.
### Что уже реализовано
- отдельный экран мастера в `ignis_app`;
- Android environment inspection через platform channel;
- проверка Wi-Fi контекста, системных permissions и активного дома;
- Android-first `smart pairing` flow на базе `esp_smartconfig`;
- post-provision `POST /devices/rescan` через уже существующий backend `ignis-core`;
- release build, `flutter analyze` и тесты проходят;
- entrypoint мастера перенесён в `SettingsScreen`, в секцию `Дом и подключение`.
### Что это означает на практике
Сейчас в проекте есть не "полноценный универсальный мастер WiZ", а первый технический клин в эту задачу: Android-only путь через smart pairing с последующим discovery в `Ignis`.
### Что критично ещё не реализовано
- нет `SoftAP`-ветки через `WiZConfig_xxxx`;
- нет reverse engineering и production-реализации commissioning-протокола `UDP 18266`;
- нет `BLE`-ветки для новых ламп;
- нет `Matter`-fallback;
- нет iOS-реализации;
- нет подтверждения на реальном железе, что текущий `smart pairing` путь покрывает нужные пользователю модели ламп.
### Честная оценка текущего состояния
Тема не закрыта. Закрыт только первый этап.
Если текущий `smart pairing` на реальных лампах пользователя не сработает, основной следующий шаг -- это не косметика и не polish, а возврат к базовому плану:
1. `SoftAP / WiZConfig_xxxx`
2. commissioning через `UDP 18266`
3. затем `BLE`-ветка
### Что ещё не добито по UX и диагностике
- нет выбора provisioning mode (`smart pairing` / `WiZConfig_xxxx` / `BLE`);
- нет явного fallback UX для ламп, которые не поддерживают текущий путь;
- нет финального успешного сценария "создать группу / мигнуть лампой / открыть пульт";
- нет отдельного protocol notes файла с результатами reverse engineering;
- нет полной ручной test matrix по реальным устройствам разных поколений.
## Почему основной объём живёт в `ignis_app`
Onboarding должен жить в `ignis_app`, а не в `ignis-core`, потому что именно телефон:
- видит nearby Wi-Fi/BLE устройства;
- может временно переключаться на AP лампы;
- может работать с Android Wi-Fi / Bluetooth API;
- может запросить системные permissions и показать пользователю platform dialogs.
`ignis-core` нужен только после успешного provisioning:
- сделать `POST /devices/rescan`;
- обнаружить лампу в домашней сети;
- дальше работать обычным локальным API, который уже реализован.
Связанные текущие точки в коде:
- `ignis_app/lib/main.dart`
- `ignis_app/lib/screens/homes_screen.dart`
- `ignis_app/lib/screens/remote_screen.dart`
- `ignis_app/android/app/src/main/kotlin/ru/akokos/ignis_app/MainActivity.kt`
- `ignis_app/lib/services/api_client.dart`
- `ignis-core/app/api/routes/devices.py`
## Подтверждённые факты из источников
### 1. У WiZ есть ручной setup через `WiZConfig_xxxx`
Официальная legacy-справка WiZ говорит, что если обычное pairing не сработало, нужно идти в `Manual setup`, где телефон подключается напрямую к Wi-Fi сети лампы.
Источник:
- https://faq.wizconnected.com/hc/en/3-wiz-legacy/faq/138-adding-a-wiz-light-in-the-system/
Дополнительный важный нюанс: WiZ отдельно пишет, что у некоторых ламп сеть `WiZConfig_xxxx` может отсутствовать вообще. Это значит, что SoftAP-путь нельзя считать универсальным.
Источник:
- https://faq.wizconnected.com/hc/en/3-wiz-legacy/faq/147-can-t-find-wizconfig-xxxx-in-the-wi-fi-settings-during-manual-setup/
### 2. У новых устройств есть Bluetooth-based setup
Новая справка WiZ V2 указывает, что Bluetooth permission нужен для Bluetooth-enabled products и что приложение может автоматически находить такие лампы после включения питания.
Источник:
- https://faq.wizconnected.com/hc/en/7-wiz-v2/faq/767-smart-lighting---how-to-get-started/
Дополнительное косвенное подтверждение: в даташитах WiZ/WiZ Pro встречается `Wi-Fi + BLE` и явная фраза про setup via Bluetooth.
Источники:
- https://assets.wizconnected.com/datasheets/WiZ_Pro_A60_B22_TW_8W_230V_929002383771_DS1022.pdf
- https://assets.wizconnected.com/datasheets/WiZ_Pro_A67_E27_RGBTW_13W_230V_929002449771_DS042023.pdf
### 3. У WiZ есть отдельный commissioning-порт `UDP 18266`
Это ключевой технический сигнал. В официальном документе по сети WiZ указано, что порт `18266/UDP` используется локально и только во время commissioning.
Источник:
- https://assets.wizconnected.com/manuals/WiZ-Network-Configuration-v2-01162024.pdf
Вывод: существует локальный commissioning-протокол, который, вероятно, и использует официальное приложение во время первичной посадки лампы в Wi-Fi.
### 4. Android официально поддерживает bootstrap к локальной Wi-Fi accessory network
Для Android 10+ есть штатный путь для peer-to-peer Wi-Fi bootstrap через `WifiNetworkSpecifier`.
Источник:
- https://developer.android.com/develop/connectivity/wifi/wifi-bootstrap
### 5. iOS тоже поддерживает accessory Wi-Fi setup, но не это наш первый приоритет
Для iOS есть `NEHotspotConfiguration` / `NEHotspotConfigurationManager`, а также accessory-oriented Wi-Fi configuration APIs. Это делает iOS-ветку возможной, но отдельной и более дорогой по времени.
Источники:
- https://developer.apple.com/documentation/networkextension/wi-fi_configuration
- https://developer.apple.com/documentation/networkextension/nehotspotconfigurationmanager
### 6. Matter есть не на всех WiZ-устройствах
WiZ пишет, что Matter поддерживают только lights/smart plugs, выпущенные после `Q2 2021`. Поэтому Matter нельзя использовать как универсальный onboarding-path.
Источник:
- https://faq.wizconnected.com/hc/en/7-wiz-v2/faq/531-do-all-wiz-devices-support-matter/
## Принятая стратегия реализации
### Основной план
1. Реализовать Android-only `SoftAP onboarding`.
2. Через reverse engineering добыть локальный commissioning payload для `UDP 18266`.
3. Встроить это в `ignis_app` как отдельный мастер.
4. После успешного provisioning вызывать existing `rescanNetwork()` через текущий `IgnisApi`.
5. Затем добавить `BLE onboarding` как вторую ветку.
### Почему не начинать с BLE
- SoftAP-path подтверждён официальной справкой WiZ.
- Android Wi-Fi bootstrap API проще и стабильнее, чем reverse engineering GATT-профиля с нуля.
- BLE вероятно потребуется для новых ламп, но SoftAP даст первый рабочий end-to-end flow быстрее.
### Почему не начинать с Matter
- не все устройства его поддерживают;
- внутри собственного Flutter-приложения Matter commissioner сильно увеличивает объём работ;
- для цели `Ignis` нужен именно практичный локальный onboarding WiZ-лампы, а не параллельный smart-home стек.
## Архитектурное решение по проекту
## High-Level Flow
```text
Flutter UI
-> MethodChannel
-> Android provisioning manager
-> connect to WiZConfig_xxxx AP
-> send commissioning payload to lamp over UDP 18266
-> wait for device to leave AP / join home Wi-Fi
-> call ignis-core POST /devices/rescan
-> show discovered device / success state
```
## Что добавляем в `ignis_app`
Новая feature-зона:
- `lib/features/provisioning/models/`
- `lib/features/provisioning/providers/`
- `lib/features/provisioning/services/`
Новый экран:
- `lib/screens/wiz_provisioning_screen.dart`
Новые Android native классы:
- `android/app/src/main/kotlin/ru/akokos/ignis_app/WizProvisioningManager.kt`
- `android/app/src/main/kotlin/ru/akokos/ignis_app/WizSoftApProvisioner.kt`
- `android/app/src/main/kotlin/ru/akokos/ignis_app/WizUdpCommissioningClient.kt`
- `android/app/src/main/kotlin/ru/akokos/ignis_app/WizProvisioningModels.kt`
Вторая очередь, не первая:
- `android/app/src/main/kotlin/ru/akokos/ignis_app/WizBleProvisioner.kt`
## Что не нужно делать в `ignis-core`
Не нужно переносить туда onboarding-логику. Сервер не должен:
- управлять Wi-Fi телефона;
- принимать пароль от домашней сети как основную часть provisioning;
- реализовывать platform-specific transport.
В `ignis-core` достаточно использовать уже существующий:
- `POST /devices/rescan`
Текущая клиентская точка входа:
- `ignis_app/lib/services/api_client.dart` -> `rescanNetwork()`
## Фактическая реализация против плана
Изначальный план первой полноценной фазы предполагал старт с `SoftAP onboarding`. В коде сейчас первой реализованной веткой стал `smart pairing`.
### Почему это важно помнить
- это ускорило появление первого рабочего мастера;
- но это не доказывает, что задача "подключение любых новых WiZ-ламп без официального приложения" уже решена;
- исходный `SoftAP + UDP 18266` путь по-прежнему остаётся вероятно более универсальным и по-прежнему нужен, если smart pairing не перекрывает нужные модели устройств.
## Предлагаемая модель состояния мастера
Нужна явная state machine, а не набор bool-флагов.
### Состояния
- `idle`
- `checkingCapabilities`
- `requestingPermissions`
- `waitingForHomeSelection`
- `waitingForPairingMode`
- `scanningForSoftAp`
- `connectingToLampAp`
- `connectedToLampAp`
- `commissioning`
- `waitingForLampToJoinHome`
- `rescanningIgnis`
- `success`
- `failure`
- `cancelled`
### Данные состояния
- `selectedHomeSsid`
- `selectedHomePassphrase`
- `selectedIgnisHomeId`
- `lampApSsid`
- `attempt`
- `lastError`
- `debugTimeline`
- `startedAt`
- `finishedAt`
### Почему нужен `debugTimeline`
Provisioning без timeline очень трудно дебажить. Минимум нужен список событий:
- permission granted/denied;
- lamp AP discovered;
- Wi-Fi request sent;
- Wi-Fi request accepted/rejected;
- UDP payload sent;
- ack received / timeout;
- device disappeared from AP;
- `rescan` started/finished.
## UX-структура мастера
### Экран 1. Введение
Показывает:
- что мастер пока Android-only;
- какие лампы могут не поддерживать SoftAP;
- что телефон временно переключится на сеть лампы;
- что нужен локальный сервер `Ignis` и доступный активный дом.
### Экран 2. Проверка контекста
Проверки:
- выбран ли активный `HomeConfig`;
- доступен ли `auth/me` текущего дома;
- есть ли Wi-Fi на телефоне;
- есть ли необходимые Android permissions;
- Android API level >= 29.
### Экран 3. Выбор домашней сети
Варианты:
- ручной ввод SSID и пароля;
- позже, если потребуется, попытка показать текущий SSID как подсказку.
Важно: не делать магии вокруг чтения текущего SSID как обязательного пути. На Android это permission-sensitive и OEM-dependent.
### Экран 4. Инструкция перевода лампы в pairing mode
Нужно явно объяснить:
- включить лампу;
- если не находится, несколько раз выключить/включить до мигания;
- если лампа не поднимает `WiZConfig_xxxx`, значит этот путь может не поддерживаться и нужен BLE-path.
### Экран 5. Подключение и provisioning
Показывать по шагам:
- поиск `WiZConfig_xxxx`;
- подключение;
- передача настроек;
- ожидание возврата лампы в домашнюю сеть;
- запрос `rescan`.
### Экран 6. Итог
Успех:
- показать, что лампа найдена;
- предложить перейти к созданию группы или вернуться на пульт.
Ошибка:
- показать, на каком шаге упало;
- дать actionable retry;
- предлагать BLE-path только когда он будет реализован.
## Точки встраивания в текущее приложение
Минимально логичное место входа:
- `HomesScreen`: отдельная кнопка "Добавить лампу WiZ"
- или `RemoteScreen` в overflow menu
Рекомендуемый вариант первой версии:
- вход из `HomesScreen`, потому что provisioning логически относится к конкретному дому/серверу.
Причина:
- мастер зависит от выбранного активного дома;
- после provisioning всё равно нужен `rescan` именно этого дома;
- onboarding новой лампы ближе по смыслу к инфраструктуре дома, чем к управлению существующими группами.
## Android implementation plan
## Phase 1: Platform Bridge Skeleton
### Цель
Подготовить безопасный мост Flutter <-> Android без реальной provisioning-логики.
### Изменяемые файлы
- `ignis_app/android/app/src/main/kotlin/ru/akokos/ignis_app/MainActivity.kt`
- новые Kotlin-файлы из секции выше
- новые Dart provider/service/screen файлы
### MethodChannel
Использовать отдельный channel, не смешивать с geofence.
Предлагаемое имя:
- `ignis/wiz_provisioning`
### Методы канала v1
- `getProvisioningCapabilities`
- `requestProvisioningPermissions`
- `startSoftApProvisioning`
- `cancelProvisioning`
### Формат `getProvisioningCapabilities`
Возвращать map:
- `platform`: `android`
- `androidApiLevel`
- `supportsWifiNetworkSpecifier`
- `supportsBle`
- `supportedModes`: `["softap"]` на первой фазе
### Формат `startSoftApProvisioning`
Вход:
- `homeSsid`
- `homePassphrase`
- `lampApPrefix` default `WiZConfig_`
- `timeoutSeconds`
- `activeHomeBaseUrl`
- `activeHomeApiKey`
На первой фазе `activeHomeBaseUrl` и `activeHomeApiKey` можно вообще не передавать в native и оставить `rescan` на Flutter-стороне.
## Phase 2: Android Wi-Fi Connect to Lamp AP
### Цель
Научиться гарантированно подключать телефон к `WiZConfig_xxxx`.
### Android APIs
- `WifiNetworkSpecifier`
- `ConnectivityManager.requestNetwork()`
- `NetworkCallback`
Источник:
- https://developer.android.com/develop/connectivity/wifi/wifi-bootstrap
### Требуемые permissions и manifest changes
Проверить и добавить по необходимости:
- `android.permission.NEARBY_WIFI_DEVICES` для Android 13+
- существующие location permissions уже частично есть
Текущий manifest:
- `ignis_app/android/app/src/main/AndroidManifest.xml`
### Что нужно реализовать
- запрос permissions из Flutter;
- поиск/выбор AP по префиксу `WiZConfig_`;
- запрос на временное подключение;
- ожидание `onAvailable()`;
- bind сокетов к этой `Network`, если потребуется;
- корректный release callback после завершения или отмены.
### Acceptance criteria
- приложение может подключиться к реальной сети `WiZConfig_xxxx`;
- есть надёжный таймаут и понятный error mapping;
- повторный запуск после неудачи не оставляет висящих network requests.
## Phase 3: Reverse Engineering Commissioning Payload
### Это главный риск проекта
Пока нет публичного официального описания payload для `UDP 18266`.
Нужно добыть его экспериментально.
### Практический план reverse engineering
1. Взять лампу, которая точно поддерживает `WiZConfig_xxxx`.
2. Перевести лампу в pairing mode.
3. Запустить официальный WiZ app и пройти `Manual setup`.
4. Снять трафик между телефоном и лампой во время provisioning.
5. Отделить трафик к лампе от фонового шума.
6. Найти обмен по `UDP 18266`.
7. Зафиксировать:
- формат payload;
- есть ли ответ/ack;
- есть ли checksum/nonce/session id;
- передаётся ли SSID/пароль в открытом виде, JSON, protobuf, бинарнике и т.д.
8. Повторить пару раз с разными SSID/password, чтобы понять структуру.
### Что именно нужно получить в результате
- пример сырого запроса;
- пример сырого ответа;
- описание полей;
- минимальный payload, достаточный для посадки лампы;
- список обязательных и необязательных параметров.
### Какие инструменты могут понадобиться
- Android device + официальный WiZ app;
- отдельная тестовая Wi-Fi сеть;
- Wireshark / tcpdump / mitm на уровне точки доступа;
- при необходимости второй Android с hotspot/sniffer схемой;
- возможно `adb bugreport` / network diagnostics как вспомогательный путь.
### Решение по хранению результата
После расшифровки протокола создать отдельный внутренний документ:
- `ignis_app/docs/wiz_commissioning_protocol_notes.md`
Если там будут чувствительные детали или сырые бинарные дампы, можно держать файл локально и не коммитить, но лучше иметь хотя бы структурированное описание в репозитории.
## Phase 4: Implement UDP Commissioning Client
### Цель
Собрать Kotlin-клиент, который:
- открывает UDP socket в сети лампы;
- отправляет commissioning payload на `18266`;
- ждёт подтверждение или фиксирует timeout;
- отдаёт во Flutter понятный result object.
### Предлагаемый класс
- `WizUdpCommissioningClient.kt`
### Предлагаемый API
- `suspend fun sendCredentials(network: Network, targetIp: InetAddress, payload: ByteArray): CommissioningResult`
### Что нужно предусмотреть
- bind сокета именно к `Network`, через которую подключились к AP лампы;
- configurable timeout;
- раздельные ошибки:
- `socket_open_failed`
- `payload_build_failed`
- `send_failed`
- `ack_timeout`
- `malformed_ack`
## Phase 5: End-to-End Flutter Flow
### Цель
Сделать законченный пользовательский мастер.
### Новые Dart сущности
Предлагаемые файлы:
- `lib/features/provisioning/models/wiz_provisioning_mode.dart`
- `lib/features/provisioning/models/wiz_provisioning_state.dart`
- `lib/features/provisioning/models/wiz_provisioning_failure.dart`
- `lib/features/provisioning/services/wiz_provisioning_platform_service.dart`
- `lib/features/provisioning/providers/wiz_provisioning_providers.dart`
- `lib/screens/wiz_provisioning_screen.dart`
### Поведение после успешного provisioning
1. Flutter получает успех от native-слоя.
2. Flutter ждёт короткое окно на переподключение лампы к домашней сети.
3. Flutter вызывает текущий `IgnisApi.rescanNetwork()`.
4. Flutter обновляет список устройств/групп.
5. Flutter показывает success и, если возможно, имя/IP/MAC найденной лампы.
Текущая клиентская точка:
- `ignis_app/lib/services/api_client.dart`
Текущий backend route:
- `ignis-core/app/api/routes/devices.py`
## Phase 6: BLE Branch
### Когда переходить к BLE
Только после того, как SoftAP-path уже рабочий end-to-end.
### Что нужно выяснить перед реализацией
- advertise name/service UUID лампы в pairing mode;
- GATT services/characteristics;
- какой transport и payload используются для передачи Wi-Fi credentials;
- есть ли там тот же commissioning payload, что и в SoftAP-path, или совсем другой протокол.
### Android tech stack для BLE
- `BluetoothManager`
- `BluetoothAdapter`
- `BluetoothLeScanner`
- `ScanCallback`
- `BluetoothGatt`
### Дополнительные permissions
- `android.permission.BLUETOOTH_SCAN`
- `android.permission.BLUETOOTH_CONNECT`
### Важное ограничение
Пока нет сведений, что BLE-path у WiZ можно поднять без reverse engineering. Официальные источники подтверждают наличие Bluetooth setup, но не описывают низкоуровневый протокол.
## Что менять в UI и навигации
### Первая версия
Добавить в `HomesScreen` вторую FAB-entry или secondary action:
- `Добавить дом`
- `Добавить лампу WiZ`
Если не хочется перегружать основной FAB, сделать extended bottom sheet / speed dial / отдельную кнопку в пустом состоянии.
### Более аккуратный вариант
Добавить на `HomesScreen` card/banner:
- "Новая лампа WiZ? Открыть мастер подключения"
### Что не делать
- не прятать этот flow глубоко в `SettingsScreen`;
- не запускать onboarding из `RemoteScreen` по умолчанию, если активный дом ещё не выбран и невалиден.
## Безопасность и хранение Wi-Fi credentials
### Минимальные правила
- пароль домашней Wi-Fi сети не хранить в `SharedPreferences`;
- не класть пароль в обычные debug-логи;
- не записывать пароль в event log приложения;
- держать пароль только в runtime memory на время onboarding-сессии;
- по завершении или ошибке занулять/очищать state.
### Что можно хранить
- только SSID как UX convenience, если это вообще нужно;
- debug timeline без секретов;
- machine-readable error codes.
## Тестовая стратегия
## Unit / Widget Tests
Покрыть:
- state machine;
- error mapping native -> Flutter;
- retry/cancel logic;
- post-success переход к `rescan`.
### Предлагаемые test-файлы
- `test/wiz_provisioning_state_test.dart`
- `test/wiz_provisioning_notifier_test.dart`
- `test/wiz_provisioning_screen_test.dart`
## Android Native Tests
Минимум:
- локальные unit tests на payload builder;
- по возможности instrumentation tests на permission/result mapping.
Но главное здесь всё равно manual verification на реальном устройстве.
## Manual Test Matrix
Обязательные ручные сценарии:
1. Успешная посадка лампы через `WiZConfig_xxxx`.
2. Таймаут при отсутствии pairing mode.
3. Ошибочный пароль домашней сети.
4. Повторный запуск мастера сразу после ошибки.
5. Отмена пользователем system dialog подключения к Wi-Fi.
6. Потеря Wi-Fi во время provisioning.
7. Успешный `rescan` после provisioning.
8. Provisioning success, но `rescan` не находит лампу.
9. Лампа без `WiZConfig_xxxx` и корректный UX fallback.
10. Проверка на Android 13+ с новыми permission flows.
## Acceptance Criteria для первой поставляемой версии
Первая версия считается завершённой, если:
- мастер доступен из `ignis_app`;
- Android 10+ устройство умеет подключиться к `WiZConfig_xxxx`;
- приложение умеет отправить commissioning payload и получить воспроизводимый результат;
- лампа уходит в домашнюю сеть;
- `Ignis` после `rescan` видит лампу;
- пользователь получает понятный success/failure UX;
- чувствительные данные не остаются в persistent storage;
- есть тесты на Dart state machine и минимум ручной regression checklist.
## Пошаговая карта действий
## Шаг 0. Подготовка перед кодом
1. Подтвердить наличие тестовой WiZ-лампы с `WiZConfig_xxxx`.
2. Подготовить Android-девайс для ручных прогонов.
3. Подготовить отдельную тестовую Wi-Fi сеть `2.4 GHz`.
4. Подготовить стенд с рабочим `ignis-core`.
5. Решить, где и как снимать provisioning traffic.
## Шаг 1. Скелет feature в `ignis_app`
1. Добавить `docs/` и этот план.
2. Создать feature-папки `lib/features/provisioning/*`.
3. Создать Dart-модели состояния.
4. Создать `WizProvisioningPlatformService`.
5. Создать новый `MethodChannel`.
6. Добавить пустой экран мастера и точку входа из `HomesScreen`.
## Шаг 2. Android bridge без реального provisioning
1. Добавить `WizProvisioningManager.kt`.
2. Подключить channel в `MainActivity.kt`.
3. Реализовать `getProvisioningCapabilities`.
4. Реализовать `requestProvisioningPermissions`.
5. Протянуть результат в UI и показать capability gate.
## Шаг 3. SoftAP connection layer
1. Добавить `WifiNetworkSpecifier` flow.
2. Сделать поиск/подключение к `WiZConfig_`.
3. Вернуть во Flutter structured result.
4. Обработать cancel/timeout.
5. Проверить ручным прогоном на тестовой точке доступа.
## Шаг 4. Reverse engineering commissioning payload
1. Снять трафик официального WiZ app.
2. Описать протокол.
3. Зафиксировать findings в отдельном notes-файле.
4. Только после этого писать production `WizUdpCommissioningClient`.
## Шаг 5. Реальный commissioning
1. Реализовать payload builder.
2. Реализовать отправку на `UDP 18266`.
3. Обработать ack/timeout.
4. Завести детальные internal error codes.
## Шаг 6. End-to-end flow с `Ignis`
1. После native success вызвать `rescanNetwork()`.
2. Дождаться ответа backend.
3. Показать найденное устройство или отдельную ошибку post-provision discovery.
4. Протестировать повторяемость.
## Шаг 7. Cleanup и hardening
1. Удалить лишние debug-логи.
2. Проверить, что пароль Wi-Fi нигде не сохраняется.
3. Добавить unit/widget tests.
4. Обновить `README.md`.
## Открытые вопросы
### Технические
- Как именно выглядит payload для `UDP 18266`?
- Нужен ли specific target IP на AP лампы или есть broadcast?
- Есть ли обязательный ack и как он кодируется?
- Нужен ли bind сокета к `Network` для всех Android OEM или только для части?
- Как определить успешность до `rescan`: по ack или по исчезновению AP?
### Продуктовые
- Где именно располагать entrypoint мастера в UI?
- Нужен ли в первой версии ручной ввод SSID, или можно сразу только auto-fill + редактирование?
- Нужен ли отдельный экран выбора provisioning mode, если BLE ещё не реализован?
### Операционные
- Есть ли в наличии лампа старого поколения с `WiZConfig_xxxx`?
- Нужны ли апрувы на Android permissions / реальные ручные прогоны / возможную установку вспомогательных инструментов для sniffing?
## Что проверять первым делом при возвращении к задаче
Если вернулись к задаче через долгое время, стартовать так:
1. Перечитать этот документ целиком.
2. Проверить, не появились ли новые официальные WiZ материалы про local commissioning.
3. Проверить, не изменились ли Android Wi-Fi/BLE permission требования.
4. Уточнить, какая именно тестовая лампа есть на руках.
5. Решить, можно ли сразу идти в reverse engineering или сначала поднимать только bridge/UI skeleton.
## Рекомендуемый следующий practically useful шаг
Когда будет время на апрувы и ручные проверки, не начинать сразу с большого рефактора. Самый выгодный порядок:
1. закоммитить UI skeleton и Android bridge;
2. проверить Wi-Fi connect к `WiZConfig_xxxx`;
3. только потом тратить время на reverse engineering `UDP 18266`.
Это минимизирует риск закопаться в неизвестный протокол до того, как станет ясно, что platform plumbing вообще работает на конкретном устройстве.
## Источники
### WiZ
- WiZ manual setup / `WiZConfig_xxxx`: https://faq.wizconnected.com/hc/en/3-wiz-legacy/faq/138-adding-a-wiz-light-in-the-system/
- WiZ: у части ламп `WiZConfig_xxxx` может отсутствовать: https://faq.wizconnected.com/hc/en/3-wiz-legacy/faq/147-can-t-find-wizconfig-xxxx-in-the-wi-fi-settings-during-manual-setup/
- WiZ V2 getting started / Bluetooth-enabled setup: https://faq.wizconnected.com/hc/en/7-wiz-v2/faq/767-smart-lighting---how-to-get-started/
- WiZ network configuration / commissioning port `UDP 18266`: https://assets.wizconnected.com/manuals/WiZ-Network-Configuration-v2-01162024.pdf
- WiZ Matter support coverage: https://faq.wizconnected.com/hc/en/7-wiz-v2/faq/531-do-all-wiz-devices-support-matter/
- WiZ Pro A60 datasheet / `Wi-Fi + BLE`: https://assets.wizconnected.com/datasheets/WiZ_Pro_A60_B22_TW_8W_230V_929002383771_DS1022.pdf
- WiZ Pro A67 datasheet / setup via Bluetooth: https://assets.wizconnected.com/datasheets/WiZ_Pro_A67_E27_RGBTW_13W_230V_929002449771_DS042023.pdf
### Android
- Wi-Fi bootstrap / `WifiNetworkSpecifier`: https://developer.android.com/develop/connectivity/wifi/wifi-bootstrap
- `WifiNetworkSpecifier.Builder` reference: https://developer.android.com/reference/android/net/wifi/WifiNetworkSpecifier.Builder.html
### Apple
- Wi-Fi configuration overview: https://developer.apple.com/documentation/networkextension/wi-fi_configuration
- `NEHotspotConfigurationManager`: https://developer.apple.com/documentation/networkextension/nehotspotconfigurationmanager
## Связанные локальные файлы
- `ignis_app/lib/main.dart`
- `ignis_app/lib/screens/homes_screen.dart`
- `ignis_app/lib/screens/remote_screen.dart`
- `ignis_app/lib/services/api_client.dart`
- `ignis_app/android/app/src/main/AndroidManifest.xml`
- `ignis_app/android/app/src/main/kotlin/ru/akokos/ignis_app/MainActivity.kt`
- `ignis-core/app/api/routes/devices.py`
- `ignis-core/README.md`