blifi 0.5.0
blifi: ^0.5.0 copied to clipboard
BLE-based Wi-Fi provisioning for ESP32: send Wi-Fi credentials over an encrypted (X25519 + AES-256-GCM) Bluetooth Low Energy session, no hotspot.
blifi #
BLE-based Wi-Fi provisioning for ESP32 - hand Wi-Fi credentials to a device over
an encrypted Bluetooth Low Energy session instead of a hotspot/captive
portal. This Dart package is the phone side; it interoperates byte-for-byte with
the blifi ESP-IDF firmware component.
- 🔒 Encrypted: X25519 key agreement + AES-256-GCM, with a Proof-of-Possession so credentials are never exposed and an active MITM can't succeed.
- 📶 Full flow: scan for devices, connect, list the device's Wi-Fi networks, send credentials, and stream live connection status.
- 🧩 Small, typed API with typed exceptions - no string-matching errors.
Install #
dependencies:
blifi: ^0.1.0
Permissions #
This package uses flutter_blue_plus
for BLE. Add the platform permissions and request them at runtime (e.g. with
permission_handler).
Android - android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11 and below -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
iOS - ios/Runner/Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Connect to your device to set up Wi-Fi.</string>
BlifiProvisioning throws BleUnavailableException if Bluetooth is off or
unsupported, so you never fail silently.
Usage #
import 'package:blifi/blifi.dart';
final blifi = BlifiProvisioning();
// 1. Discover a device.
final device = await blifi.scanForDevices().first;
// 2. Connect and complete the secure handshake with the device's PoP.
final session = await blifi.connect(device, proofOfPossession: 'K7M2QP9X');
// 3. List the device's visible Wi-Fi networks.
final networks = await session.scanWifiNetworks();
// 4. Send credentials and watch progress.
session.statusStream.listen((s) {
print('${s.state}${s.ipAddress != null ? ' @ ${s.ipAddress}' : ''}');
});
await session.sendCredentials('HomeWiFi', 'password');
// Or await a single terminal result instead of watching the stream:
final ip = await session.awaitProvisioned(); // throws if the link drops first
// 5. Done.
await session.disconnect();
Errors are typed: catch AuthenticationException (wrong PoP),
WifiConnectionException (with a ProvisioningState), BleConnectionException,
or ProvisioningTimeoutException - all subtypes of BlifiException.
After success #
A BLE disconnect that follows wifiConnected is treated as a clean end:
statusStream closes via onDone with no error (a disconnect before success
still raises BleConnectionException). This matches firmware built with
stop-BLE-after-provisioning, which drops the link once the phone has the IP.
session.ipAddress stays readable after the link drops - it is your last chance
to learn the address; after this the device is reachable only over the network.
disconnect() is idempotent, so it is safe to call from dispose().
See example/
for a runnable app.
License #
MIT.