Wi-Fi Provisioning for IoT Devices via Mobile Applications
Wi-Fi Provisioning — transmitting network credentials (SSID, password) from a mobile application to an IoT device that doesn't yet know which network to connect to. Sounds simple. However, Android fragmentation, differences in ConnectivityManager behavior across versions, and peculiarities of specific chips make this a non-trivial task.
Wi-Fi Provisioning Methods
Soft AP (Access Point Mode). The device creates its own Wi-Fi hotspot. The phone connects to it and sends credentials via HTTP/UDP. Downside: on Android 10+, the system automatically switches traffic to cellular if the AP has no internet.
SmartConfig / EZ Connect. The phone sends SSID and password through encrypted UDP broadcast packets. The device in Wi-Fi monitoring mode intercepts and decodes them. Espressif ESP-TOUCH and Ti SmartConfig are implementations of this approach. Works without physical connection to the device but is unstable with routers that have AP client isolation enabled.
BLE + Wi-Fi combo. BLE for credentials transmission, Wi-Fi for network operation. The most reliable UX: doesn't require switching Wi-Fi networks on the phone. Details — in the separate BLE Provisioning service.
QR-code with credentials. Credentials are embedded in the QR code during manufacturing. Suitable for enterprise deployment, not for consumer devices.
Soft AP Provisioning on Android 10+
The main challenge is explicitly binding HTTP requests to the desired network. Without this, OkHttp uses the default route (cellular data):
val specifier = WifiNetworkSpecifier.Builder()
.setSsid("MyDevice_AP")
.setWpa2Passphrase("provisioning_key")
.build()
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(specifier)
.build()
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
val client = OkHttpClient.Builder()
.socketFactory(network.socketFactory)
.dns { hostname ->
network.getAllByName(hostname).toList()
}
.build()
// Send credentials to 192.168.4.1 (default ESP32 AP IP)
postWifiCredentials(client, ssid, password)
}
override fun onUnavailable() { onProvisioningFailed("Failed to connect to device") }
}
connectivityManager.requestNetwork(request, callback, 30_000) // 30 sec timeout
removeCapability(NET_CAPABILITY_INTERNET) is critical. Without it, Android 10 will reject the connection request to an AP without internet.
On Android 9 and below — use WifiManager.enableNetwork() + WifiManager.disconnect(). The API is deprecated but functional.
ESP-IDF SoftAP Provisioning
For Espressif devices — use esp_wifi_prov_mgr component + official provisioning-android SDK:
// Initialize manager
val wifiProvisioningManager = ESPProvisionManager.getInstance(context)
// Find device AP
wifiProvisioningManager.createESPDevice(
ESPConstants.TransportType.TRANSPORT_SOFTAP,
ESPConstants.SecurityType.SECURITY_1
).let { device ->
device.connectWiFiDevice() { isConnected ->
if (isConnected) {
// Scan available networks through device
device.scanNetworks { networks, _ ->
// Show network list to user
}
}
}
}
The SDK encrypts transport using sec1 (Curve25519 key exchange). No need to implement encryption manually.
Network scanning through device is a valuable feature: instead of asking the user to enter SSID manually, the device scans the environment and returns the list. Users select from the list, reducing input errors.
SmartConfig: When It Works, When It Doesn't
Ti SmartConfig and Espressif ESP-TOUCH only work if:
- Router hasn't enabled AP client isolation
- Phone is connected to 2.4 GHz (not 5 GHz)
- Device is in good signal range
In corporate networks, AP isolation is almost always enabled. SmartConfig doesn't work there. Mildly put, it's a poor choice for B2B devices.
User Errors and How to Intercept Them
Users often enter a 5 GHz network password while ESP32 only supports 2.4 GHz. Check beforehand:
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val currentNetwork = wifiManager.connectionInfo
val is5Ghz = currentNetwork.frequency > 4000 // 5 GHz > 4000 MHz
if (is5Ghz) showWarning("Your device is on a 5 GHz network. Ensure the IoT device supports it.")
On Android 10+: WifiInfo is only available with ACCESS_FINE_LOCATION permission — request it explicitly.
Implementing Wi-Fi Provisioning (single method, ESP32/ESP8266): 2–3 weeks. Multi-method approach with fallback and complete UX flow: 4–6 weeks.







