866 lines
40 KiB
Markdown
866 lines
40 KiB
Markdown
# 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`
|