esp_blufi

Flutter plugin for provisioning ESP32 Wi-Fi over BLE using Espressif's BLUFI protocol on Android and iOS. BLE provisioning requires a physical device with Bluetooth enabled.

Official BLUFI baseline

The native code is based on Espressif's Android library 2.5.1 and iOS library 2.2.0. Android discovers the device's protocol version before reporting GATT_SUCCESS and supports the official BLUFI 1.4 communication mode. The official iOS baseline uses the earlier communication mode; BLUFI 1.4 mode is currently Android-only.

See upstream versions and port adaptations for the pinned source commits and retained Flutter integration behavior.

Requirements

Platform Minimum Build configuration
Android API 21 compileSdk 36, AGP 9.1, Java 17
iOS iOS 13 Source builds for devices and simulators; BLE needs real hardware
Flutter 3.27 Dart 3.6 or later

Installation

Add the package from pub.dev:

dependencies:
  esp_blufi: ^0.2.0

Run flutter pub get.

Platform setup

On iOS, add Bluetooth usage descriptions to the application's Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to configure Wi-Fi on ESP32 devices.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to configure Wi-Fi on ESP32 devices.</string>

On Android 12 and later, scanning requests BLUETOOTH_SCAN and BLUETOOTH_CONNECT. The manifest uses neverForLocation, so BLUFI scanning does not require location permission. Android 11 and earlier request ACCESS_FINE_LOCATION; location services must be enabled. Permission denial throws PlatformException. Allow the user to retry after granting access.

The iOS Bluetooth prompt is deferred until the first scan, and the package includes a privacy manifest.

Usage

Register the event callback before scanning. Events are JSON strings; ble_scan_result includes a device address and name in its value object. Pass an address returned by the current scan to connectPeripheral.

import 'dart:async';
import 'dart:convert';

import 'package:esp_blufi/esp_blufi.dart';

final blufi = EspBlufi();
Completer<void>? connectionReady;

void startListening() {
  blufi.onMessageReceived(successCallback: (data) {
    if (data == null) return;
    final event = jsonDecode(data) as Map<String, dynamic>;
    if (event['key'] == 'GATT_SUCCESS' && event['value'] == '1') {
      final ready = connectionReady;
      if (ready != null && !ready.isCompleted) ready.complete();
    }
    if (event['key'] == 'device_wifi_connect') {
      // '1': connected to Wi-Fi; '0': not connected when status was reported.
      print('Wi-Fi status: ${event['value']}');
    }
    // Display ble_scan_result and wifi_info events in your device picker.
  });
}

Future<void> scan() async {
  startListening();
  await blufi.scanDeviceInfo(); // Optional: filterString: 'ESP'.
}

Future<void> provision(String scannedAddress, String ssid, String password) async {
  await blufi.stopScan();
  final ready = Completer<void>();
  connectionReady = ready;
  await blufi.connectPeripheral(peripheralAddress: scannedAddress);
  await ready.future.timeout(const Duration(seconds: 30));

  // Automatically negotiates BLUFI encryption before submitting credentials.
  await blufi.configProvision(username: ssid, password: password);
}

connectPeripheral completes when connection setup starts. Wait for GATT_SUCCESS before provisioning or requesting device data. username is the Wi-Fi SSID.

configProvision negotiates encryption if necessary and sends no credentials when negotiation fails. Its future completes when configuration is submitted, while configure_params reports transmission success ('1') or failure ('0'). Wi-Fi connection status arrives separately as device_wifi_connect; call requestDeviceStatus() as needed while waiting for the device to connect. Close the BLE connection with requestCloseConnection() when finished.

Call requestDeviceVersion() to request a device_version event with the device protocol version.

To negotiate explicitly after GATT_SUCCESS, call await blufi.negotiateSecurity(). It completes only after the handshake succeeds. Both negotiation and provisioning report connection/security failures as PlatformException; handle these in the application and reconnect before retrying a failed handshake. A new BLE connection requires a new handshake.

License

See LICENSE. Repository access is restricted to authorized users.