Реализация BLE Provisioning IoT-устройств через мобильное приложение
BLE Provisioning — передача Wi-Fi credentials и конфигурации IoT-устройству через Bluetooth Low Energy. Это лучший метод для пользовательского UX: не нужно переключать Wi-Fi сети на телефоне, работает надёжнее SmartConfig, не зависит от настроек роутера. Для ESP32, nRF52, Nordic-чипов — предпочтительный выбор.
GATT-архитектура для Provisioning
Устройство в режиме provisioning рекламирует BLE-сервис. Мобильное приложение подключается как GATT-клиент и пишет данные в характеристики сервиса. Стандартная схема для ESP-IDF:
-
Service UUID:
021a9004-0382-4aba-aa36-ec4d15d65e0e(Espressif Provisioning) - Характеристика конфигурации: write (SSID, password, auth mode)
- Характеристика статуса: notify (результат подключения устройства к сети)
После записи credentials устройство пытается подключиться к Wi-Fi и уведомляет телефон через notify-характеристику об успехе или ошибке.
Android BLE API: что идёт не так
BLE на Android — источник боли. Разные производители реализуют стек по-разному. BluetoothGatt.writeCharacteristic() может вернуть true при вызове, но onCharacteristicWrite придёт со статусом GATT_ERROR (133) — самая частая необъяснимая ошибка.
Правильный паттерн — очередь команд. BLE не поддерживает параллельные GATT-операции:
class BleCommandQueue {
private val queue: LinkedList<() -> Unit> = LinkedList()
private var isExecuting = false
fun enqueue(command: () -> Unit) {
queue.add(command)
if (!isExecuting) executeNext()
}
fun onCommandComplete() {
isExecuting = false
executeNext()
}
private fun executeNext() {
if (queue.isEmpty()) return
isExecuting = true
queue.poll()?.invoke()
}
}
Каждый writeCharacteristic, readCharacteristic, setNotification — через очередь. onCharacteristicWrite callback → queue.onCommandComplete(). Без этого при параллельных операциях GATT стек зависает и соединение разрывается.
MTU negotiation. По умолчанию MTU = 23 байта (20 байт payload). Credentials с длинным SSID и паролем могут не влезть. Сразу после подключения запрашивать расширение:
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.requestMtu(512) // до 517 байт
}
}
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
// Теперь можно писать данные размером mtu - 3 байта
startProvisioning()
}
Espressif provisioning-android SDK
Espressif предоставляет готовый SDK, который скрывает низкоуровневую работу с GATT:
val device = ESPProvisionManager.getInstance(context)
.createESPDevice(
ESPConstants.TransportType.TRANSPORT_BLE,
ESPConstants.SecurityType.SECURITY_1
)
device.connectBLEDevice(scanResult) { connected ->
if (!connected) return@connectBLEDevice
device.scanNetworks { networks, error ->
// networks — список Wi-Fi сетей, которые видит устройство
}
}
// После выбора сети пользователем
device.provision(selectedSsid, password) { status ->
when (status) {
ProvisioningStatus.SUCCESS -> navigateToSuccess()
ProvisioningStatus.FAILURE -> showError(status.toString())
}
}
SDK реализует шифрование канала через SRP6a (Security 2) или Curve25519+AES (Security 1). Credentials никогда не передаются в открытом виде.
iOS: CoreBluetooth + ESPProvision
На iOS — ESPProvision Swift Package от Espressif или нативный CoreBluetooth для кастомных протоколов.
import ESPProvision
ESPProvisionManager.shared.searchESPDevices(devicePrefix: "PROV_", transport: .ble, security: .secure) { devices, error in
guard let device = devices?.first else { return }
device.connect(delegate: self) { status in
if case .connected = status {
device.provision(ssid: selectedSSID, passPhrase: password) { status in
// handle result
}
}
}
}
На iOS нет фрагментации GATT-стека — CoreBluetooth работает одинаково на всех устройствах. Но есть ограничение: сканирование BLE в background режиме работает только для устройств с известными Service UUID, заранее прописанными в Info.plist.
Типичные ошибки provisioning UX
Нет обратной связи о прогрессе. Устройство подключается к Wi-Fi 5–15 секунд. Без прогресс-индикатора пользователи думают, что приложение зависло и нажимают назад.
Не обрабатывают ошибку неправильного пароля. Устройство вернёт статус AUTH_ERROR через notify-характеристику. Нужно показать «Неверный пароль Wi-Fi» — не «Ошибка подключения».
Не выходят из режима provisioning после успеха. Устройство после подключения к Wi-Fi перестаёт рекламировать BLE-сервисы — нормальное поведение. Приложение должно закрыть BLE-соединение и перейти к следующему шагу.
Реализация BLE Provisioning на базе Espressif SDK: 2–3 недели. Кастомный GATT-протокол с шифрованием под другой чип: 4–6 недель.







