
esp_provisioning_wifi
Library to provision WiFi on ESP32 devices over Bluetooth, using Bloc.
API Notes
- Import the package via the public barrel:
import 'package:esp_provisioning_wifi/esp_provisioning_wifi.dart';
- Most apps drive the flow through
EspProvisioningBloc(see Usage). For direct, non-Bloc use, instantiateEspProvisioningService()from the same import; it exposes all of the methods below plusgetPlatformVersion(). scanBleDevices(prefix)returnsFuture<List<String>>of matching device names and must run beforescanWifiNetworks/provisionWifi.scanWifiNetworks(...)returnsFuture<List<EspWifiNetwork>>.- Each network exposes
ssid(String),rssi(dBm,int?) andsecurity(a typedEspWifiSecurityenum mirroring Espressif'sWifiAuthMode), populated on both platforms.
- Each network exposes
scanWifiNetworks(...),provisionWifi(...)andfetchCustomData(...)acceptsecurity: EspSecurityScheme.security1(default) or.security2.- Security 2 (SRP6a) additionally requires the
usernameconfigured in the firmware; omitting it fails fast withE0. - The bloc events
EspProvisioningEventBleSelectedandEspProvisioningEventWifiSelectedtake the same optionalsecurityandusernameparameters.
- Security 2 (SRP6a) additionally requires the
provisionWifi(...)returnsFuture<bool>.- It resolves
trueon success and throws aPlatformExceptionwith a typed error code on failure:E_PROV_*for provisioning-phase failures, or a connect-phase code such asE_CONNECT,E_CONNECT_TIMEOUTorE_CANCELLED(see the Error Code Contract below).
- It resolves
cancelOperations()returnsFuture<bool>and cancels active native work.- In-flight scan/provision calls fail with
E_CANCELLED(EspProvisioningFailure.cancelled) on both platforms.
- In-flight scan/provision calls fail with
EspProvisioningState.failureexposes typed failures usingEspProvisioningFailure.none,permissionDenied,timeout,cancelled,deviceNotFound,invalidResponse,sessionFailed,authenticationFailed,networkNotFound,provisioningFailed,platform,unknown.
EspProvisioningState.errorCodeanderrorDetailsexpose raw platform diagnostics.scanWifiNetworks(...)andprovisionWifi(...)accept optionalconnectTimeout.- This timeout is propagated through Dart and native layers for BLE connection timing.
EspProvisioningBlocacceptsconnectTimeout(BLE connect phase, default 15s) andrequestTimeout(overall operation budget, defaultconnectTimeout+ 20s).- Dart-side request timeouts cancel the in-flight native operation and emit
status: EspProvisioningStatus.errorwithfailure == EspProvisioningFailure.timeout. fetchCustomData(deviceName, proofOfPossession, {endpoint = 'custom-data', payload = '', security, username, connectTimeout})returnsFuture<String?>and reads provisioning custom endpoint payloads.- Service-level only (there is no bloc event for it); failures throw
E_CUSTOM_DATA. - Useful for firmware-driven provisioning metadata such as lock state or SoftAP password hints.
- Service-level only (there is no bloc event for it); failures throw
Error Code Contract
The plugin reports stable error codes that the bloc maps into
EspProvisioningFailure. Most come from the native layers;
E_INVALID_RESPONSE, E_TIMEOUT and E_UNKNOWN are raised by the Dart layer:
E0(EspProvisioningErrorCodes.missingArgument)E1(EspProvisioningErrorCodes.wifiScanFailed)E_PERMISSIONE_BLE_SCAN_STARTE_BLE_SCANE_DEVICE_NOT_FOUNDE_INVALID_RESPONSEE_CONNECT_TIMEOUTE_CONNECTE_CUSTOM_DATAE_DEVICEE_PROV_SESSIONE_PROV_CONFIGE_PROV_AUTHE_PROV_NETWORK_NOT_FOUNDE_PROV_FAILEDDEVICE_DISCONNECTEDE_CANCELLEDE_TIMEOUTE_UNKNOWN
Import: package:esp_provisioning_wifi/esp_provisioning_error_codes.dart.
Platform note: both platforms emit the granular provisioning codes
(E_PROV_SESSION, E_PROV_CONFIG, E_PROV_AUTH,
E_PROV_NETWORK_NOT_FOUND), with E_PROV_FAILED as the fallback. On iOS an
incorrect proof of possession is typically rejected during the connect phase
(E_CONNECT/E_DEVICE, mapped to EspProvisioningFailure.platform) rather
than as E_PROV_SESSION. DEVICE_DISCONNECTED is iOS-only;
E_DEVICE_NOT_FOUND is Android-only.
Migration (0.2.x -> 0.3.0)
- Security 2 (SRP6a) support:
scanWifiNetworks,provisionWifi,fetchCustomData, and the bloc selection events accept optionalsecurity(EspSecurityScheme) andusernameparameters. Defaults are unchanged (Security 1), so existing call sites keep working. - iOS now populates
EspWifiNetwork.rssi/securityand emits the granularE_PROV_*codes; code that special-cased their absence on iOS can be simplified. - Any class that overrides
scanWifiNetworks,provisionWifi, orfetchCustomData— customFlutterEspBleProvPlatformimplementations, or test fakes extendingFlutterEspBleProv(e.g. injected intoEspProvisioningBloc) — must add the newsecurity/usernamenamed parameters to its overrides. Classes that don't override those methods are unaffected.
Migration (0.1.x -> 0.2.0)
- The method channel and native plugin package/classes were renamed, so this
plugin no longer conflicts with apps that also depend on
flutter_esp_ble_prov. No Dart-side changes are needed for this. scanWifiNetworks(...)andEspProvisioningState.wifiNetworksnow useEspWifiNetworkinstead ofString. Usenetwork.ssidwhere you previously used the string;rssiandsecurityare available on Android.- Provisioning failures now throw typed
PlatformExceptions (E_PROV_SESSION,E_PROV_CONFIG,E_PROV_AUTH,E_PROV_NETWORK_NOT_FOUND,E_PROV_FAILED) instead of resolvingfalse. The bloc maps them to newEspProvisioningFailurevalues (sessionFailed,authenticationFailed,networkNotFound,provisioningFailed); exhaustive switches overEspProvisioningFailuremust handle them. - Timeouts now emit
status: EspProvisioningStatus.error(previously the step status was kept withfailure: timeout). - The
TIMEOUTconstant was replaced bykEspDefaultConnectTimeoutandkEspDefaultOperationBudget;EspProvisioningBlocnow takesconnectTimeoutandrequestTimeoutparameters. - Minimums raised: Dart
^3.5.0, Flutter3.24+, flutter_bloc 9, permission_handler 12 (13.x is deferred until its AGP 9 / compileSdk 37 toolchain requirements are mainstream; pinningpermission_handler: ^13.0.0in your app will conflict with this plugin's^12.0.3constraint).
Migration (0.0.x -> 0.1.0)
- Replace
state.timedOutchecks withstate.failure == EspProvisioningFailure.timeout. - For error UX and telemetry, use both:
state.failurefor typed handlingstate.errorCodeandstate.errorDetailsfor diagnostics
- If you call service methods directly, invoke
cancelOperations()before starting a new scan/provision flow to cancel stale native operations. - Replace direct
srcimports with:import 'package:esp_provisioning_wifi/esp_provisioning_wifi.dart';
Usage
BlocProvider(
create: (_) => EspProvisioningBloc(),
child: BlocConsumer<EspProvisioningBloc, EspProvisioningState>(
listener: (_, state) {
if (state.failure != EspProvisioningFailure.none) {
// Use typed failure for user-facing behavior.
debugPrint('Failure: ${state.failure} | ${state.errorMsg}');
}
},
builder: (_, state) {
return Text('Status: ${state.status}');
},
),
)
Drive the flow by adding events (see example/lib/main.dart for a complete UI):
final bloc = context.read<EspProvisioningBloc>();
// 1. Scan for BLE devices advertising the given name prefix
// ('PROV_' in the Espressif demos).
bloc.add(const EspProvisioningEventStart('PROV_'));
// -> status == bleScanned; pick a name from state.bluetoothDevices.
// 2. Connect to the chosen device and scan its visible WiFi networks. The
// proof of possession must match the firmware ('abcd1234' in the demos).
bloc.add(const EspProvisioningEventBleSelected('PROV_XXXXXX', 'abcd1234'));
// -> status == wifiScanned; pick a network from state.wifiNetworks.
// 3. Provision the chosen network.
bloc.add(const EspProvisioningEventWifiSelected(
'PROV_XXXXXX', 'abcd1234', 'my-ssid', 'my-wifi-password'));
// -> status == wifiProvisioned with state.wifiProvisioned == true on success.
Device firmware requirements
The ESP32 must run Espressif's BLE provisioning scheme (e.g. wifi_prov_mgr
from ESP-IDF, or the Arduino WiFiProv demo) using Security 1 (the
default) or Security 2:
- Security 1 requires a proof-of-possession (PoP) string. Pass the same PoP
your firmware was configured with (the Espressif demos default to
abcd1234, with device names prefixedPROV_). - Security 2 (SRP6a) requires the firmware's
sec2username and PoP; passsecurity: EspSecurityScheme.security2andusernamealongside the PoP.
A wrong PoP surfaces as E_PROV_SESSION
(EspProvisioningFailure.sessionFailed) on Android; on iOS it fails during
connect. Security 0 (unauthenticated) firmware is not supported.
Requirements
- Dart
^3.5.0, Flutter3.24+. - If your app also depends on
permission_handlerdirectly, use^12.x— a^13.0.0pin conflicts with this plugin's^12.0.3constraint.
Android 6 (API level 23)+
Make sure your android/app/build.gradle has 23+ here:
defaultConfig {
minSdkVersion Math.max(23, flutter.minSdkVersion)
}
If your app enforces repositories via settings.gradle (dependencyResolutionManagement),
ensure jitpack.io is present:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Bluetooth permissions are automatically requested by the library.
iOS 13.0+
Add this in your ios/Runner/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Our app uses bluetooth to find, connect and transfer data between different devices</string>
This package requests Bluetooth permission through permission_handler, whose
iOS Bluetooth support is compiled out by default. Enable it in your
ios/Podfile post_install hook, otherwise the permission request always
fails and the provisioning flow never starts:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'PERMISSION_BLUETOOTH=1',
]
end
end
end
Notes
Origins
This library started as a Bloc wrapper over flutter_esp_ble_prov. The native Android and iOS provisioning implementations are now maintained inside this package.
Espressif provisioning libraries
- Android uses esp-idf-provisioning-android, resolved via JitPack.
- iOS uses the ESPProvision CocoaPod.