Skip to content

Overview

Provisioning (the process of delivering the Wi-Fi SSID and password to a device) is the first step of an IoT device going online: devices have no screen or keyboard, so they can't type in the router password by hand — a phone must "hand" it over. BluFi is the open-source provisioning protocol proposed by Espressif, which Ai-Thinker ported to the Ai-WB2: the device exposes a "provisioning entrance" through BLE advertising (the loudspeaker shout); after the phone app connects, it sends the Wi-Fi SSID (the Wi-Fi name) and password to the device over an encrypted channel, and the device automatically connects to the router. This tutorial covers the whole flow: flash the blufi example → provision with the phone app → the device automatically goes online.

In plain words: provisioning is like enrolling a fingerprint on a door lock. A newly installed lock (device) doesn't know your door (router); you press your phone against it and enroll the "opening info", after which it can open the door (go online) on its own. BluFi uses the BLE Bluetooth "close-range channel" for the enrollment — the password travels over Bluetooth, far safer than going naked over Wi-Fi.

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/bluetooth/blufi; the code can be found directly in the local SDK.

🎯Page GoalProvision the Ai-WB2 over BLE with the EspBlufi phone app; the device automatically connects to a 2.4GHz router — mastering "Bluetooth provisioning", a product-grade networking method.
🧰Prerequisites① An Ai-WB2 development board ② A 2.4GHz router (note the SSID and password) ③ The EspBlufi app on your phone (Android: [EspressifApp/EspBlufi](https://github.com/EspressifApp/EspBlufi); iOS: search "EspBlufi" in the App Store) ④ Development environment set up per [SDK Installation](../../sdk/sdk_intro).
🔗RelatedWi-Fi connection principles: [Connect Wi-Fi](../wifi/wifi_connect); BLE advertising concepts: [BLE Introduction](./ble_intro).

Enter the Example Project

Open a terminal and enter the official blufi example project directory:

cd ~/Ai-Thinker-WB2/applications/bluetooth/blufi

Note: cd is the “change directory” command — entering the blufi example project directory; all subsequent make build and make flash flash commands must run in this directory.

Project structure:

File Purpose
main/blufi_main.c Main program source: provisioning init, event callbacks, Wi-Fi connection logic — the main file this tutorial looks at
Makefile / proj_config.mk Project build config, usually no changes needed
Understand the Provisioning Code (No Changes Needed)

Open main/blufi_main.c — this example needs no parameter changes; flash it and it works. The code can be summarized as “register callbacks + start the service”:

Code Purpose
wifi_interface_init(blufi_wifi_event) Registers the Wi-Fi event callback — provisioning success/failure/IP-acquired all notify here
at_blufi_start() Starts the BluFi provisioning service: initializes BLE and registers the provisioning callbacks — this is the provisioning entrance
example_event_callback The provisioning core callback: the SSID/password from the app are received, saved and used to initiate the connection here
blufi_security_init() Starts encryption after the app connects (AES encrypt/decrypt + CRC checksum) — the Wi-Fi password travels over Bluetooth encrypted

💡 The official code also registers two serial commands: blufi_init starts provisioning, blufi_deinit exits provisioning (back to normal mode); usable with the CLI when debugging.

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program flashed into the board) the board can run; -j8 builds with 8 parallel CPU cores, faster.

On success a firmware build_out/blufi.bin is generated.

⚠️ If it reports riscv64-unknown-elf-gcc: command not found, the toolchain permissions aren’t configured — run cd toolchain/riscv/Linux && . chmod755.sh first, then rebuild.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board’s chip. After p= comes the serial device (change it to your computer’s actual one — check with ls /dev/ttyUSB*), b=921600 is the flash baud rate (serial transfer speed), keep the default.

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.

Provision with the Phone App
  1. Open the EspBlufi app on your phone (grant Bluetooth and location permissions) and confirm the phone is connected to your Wi-Fi (the app reads the phone’s current Wi-Fi name).
  2. After flashing, the board automatically enters provisioning mode (BLE advertising); the app’s home page should show a device named BLUFI — tap Connect.
  3. After connecting, fill in/confirm the SSID (Wi-Fi name) and password on the app screen, then tap Send/Provision.

💡 Note the distinction: during provisioning the phone connects to the board’s BLE, not the board’s Wi-Fi; after sending, wait a few seconds patiently — the device automatically connects to the router. Provisioning only supports 2.4GHz routers; 5GHz won’t connect.

Run and Verify

Open the serial assistant (baud rate 921600 — this example uses the system default log serial) and watch the logs of the whole provisioning process:

blufi demo test
...
BLUFI init finish
BLUFI ble connect          ← phone app connected to the board's BLE
BLUFI requset wifi connect to AP
Recv STA SSID my_wifi      ← received the Wi-Fi name from the phone
Recv STA PASSWORD ******** ← received the Wi-Fi password from the phone
BLUFI save ssid&&pwd       ← provisioning info saved
...
[WIFI] [EVT] CODE_WIFI_ON_GOT_IP  ← got an IP — online!

The serial printing BLUFI init finishBLUFI ble connectBLUFI save ssid&&pwd in order, and the app screen showing the device connected to the router (Wi-Fi status success), means provisioning succeeded; if the app can’t find the BLUFI device or the router connection never completes after sending, see the FAQ at the end.

💡 Advanced verification: after provisioning succeeds, power-cycle the board — the device automatically connects to the router with the saved SSID/password (that’s “remembering the password”), no re-provisioning needed. To provision again, hold reset to re-enter provisioning mode.


API Summary for This Tutorial

Note: at_blufi_start is a custom interface of this project (defined in blufi_main.c); axk_hal_blufi_init, _blufi_host_and_cb_init, axk_blufi_adv_*, blufi_security_* come from the SDK's components/network/blufi/ component; wifi_interface_init comes from the SDK's Wi-Fi interface layer.

bl_sys_init

Initializes basic resources such as the system clock and peripherals; must be called before using BLE/Wi-Fi (called in main() in this example).

Return: 0 on success; negative error code on failure

tcpip_init(callback, arg)

Initializes the lwIP TCP/IP protocol stack (socket, DNS and other network features depend on it) — needed for going online automatically after provisioning.

Parameters:

  • callback: init-complete callback function pointer; pass NULL usually
  • arg: callback argument; pass NULL

Return: none

at_blufi_start

Starts the BluFi provisioning service: calls axk_hal_blufi_init() to initialize the BLE hardware, then _blufi_host_and_cb_init() to register the provisioning callbacks (project-custom, defined in blufi_main.c).

Return: 0 on success; negative error code on failure

axk_hal_blufi_init

Initializes the BLE hardware BluFi depends on (from components/network/blufi/blufi_hal.h) — the first step of the provisioning service.

Return: 0 on success; negative error code on failure

_blufi_host_and_cb_init(callbacks)

Registers the BluFi callbacks (event callback, encrypt/decrypt, checksum functions) — all provisioning events are dispatched from here (from components/network/blufi/blufi_init.h).

Parameters:

  • callbacks: _blufi_callbacks_t struct pointer, required (this example example_callbacks, containing event_cb, encrypt_func, checksum_func, etc.)

Return: 0 on success; negative error code on failure

axk_blufi_adv_start

Starts BLE advertising — the phone app finds the "provisioning entrance" thanks to it (from components/network/blufi/axk_blufi.h).

Return: none

axk_blufi_adv_stop

Stops BLE advertising (called after the app connects, so the provisioning entrance isn't exposed all the time).

Return: none

blufi_security_init

Initializes provisioning encryption (AES + DH key exchange) — the Wi-Fi password travels encrypted over the Bluetooth channel (from blufi_security.h).

Return: 0 on success; negative error code on failure

blufi_security_deinit

Frees the provisioning encryption resources (called after the app disconnects).

Return: none

wifi_interface_init(event_cb)

Registers the Wi-Fi event callback (the notification entrance for provisioning results, connection status, IP acquisition, etc.).

Parameters:

  • event_cb: event callback function pointer, shaped void cb(int event, void *param), required (this example blufi_wifi_event)

Return: 0 on success; negative error code on failure

xPortGetFreeHeapSize

Returns the current available heap memory in bytes (for troubleshooting out-of-memory; this example prints it periodically).

Return: remaining heap bytes (never fails)

xPortGetMinimumEverFreeHeapSize

Returns the minimum-ever remaining heap bytes since the system started (for checking whether memory peaks grazed the limit).

Return: historical minimum remaining heap bytes (never fails)

vTaskDelay(ms)

Suspends the current task for the given milliseconds, yielding the CPU to other tasks.

Parameters:

  • ms: delay in milliseconds; this example 3000 (prints memory every 3 seconds)

Return: none

xTaskCreate(task, name, stack, param, prio, handle)

Creates a task and adds it to the ready queue; the scheduler runs it by priority.

Parameters:

  • task: task entry function pointer, shaped void task(void *arg), required
  • name: task name string (for debugging); this example "main_entry" / "free_task"
  • stack: task stack size (in words); this example 1024 / 750
  • param: parameter pointer passed to the entry function; pass NULL if none
  • prio: task priority, values: 0 (lowest) ~ 19 (highest, per SDK config); this example 15 / 1
  • handle: task handle output pointer; pass NULL if not needed

Return: pdPASS on success; pdFAIL on failure (e.g. out of memory)


Full Code

Below is the complete main/blufi_main.c source, identical to the official example (applications/bluetooth/blufi/main/blufi_main.c):

📜 Click to expand the full blufi_main.c code
c


#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <lwip/tcpip.h>

#include <bl602_glb.h>

#include <bl_sys.h>
#include <bl_uart.h>
#include <cli.h>

//////////////////////////////////
#include "wifi_interface.h"
#include <../wifi_mgmr.h>
#include "blufi.h"
#include "blufi_api.h"
#include "blufi_hal.h"
#include "blufi_init.h"
#include "axk_blufi.h"
#include "ble_interface.h"
#include "blufi_security.h"
//////////////////////////////////

static int scan_counter;
static bool ble_is_connected = false;
static bool gl_sta_connected = false;
blufi_config_t g_blufi_config = {0};

static void cb_scan_item_parse(wifi_mgmr_ap_item_t *env, uint32_t *param1, wifi_mgmr_ap_item_t *item)
{
    _blufi_ap_record_t *ap_list;

    ap_list = (_blufi_ap_record_t *)env;
    ap_list[scan_counter].rssi = item->rssi;
    memset(ap_list[scan_counter].ssid, 0, sizeof ap_list[scan_counter].ssid);
    memcpy(ap_list[scan_counter].ssid, item->ssid, item->ssid_len);

    scan_counter++;
}

static void cb_scan_complete(void *data, void *param)
{
    _blufi_ap_record_t *ap_list;

    ap_list = (_blufi_ap_record_t *)malloc(WIFI_MGMR_SCAN_ITEMS_MAX * sizeof(_blufi_ap_record_t));
    if (!ap_list)
    {
        printf("ap_list malloc fail");
        return;
    }

    scan_counter = 0;
    wifi_mgmr_scan_ap_all(ap_list, NULL, cb_scan_item_parse);
    printf("scan complete:%ld \r\n", scan_counter);

    if (ble_is_connected == true)
    {
        axk_blufi_send_wifi_list(scan_counter, ap_list);
    }
    else
    {
        printf("BLUFI BLE is not connected yet\n");
    }

    free(ap_list);
}

int wifi_scan_start(void)
{
    return wifi_mgmr_scan(NULL, cb_scan_complete);
}

static void blufi_wifi_event(int event, void *param)
{
    switch (event)
    {

    case BLUFI_STATION_CONNECTED:
        gl_sta_connected = true;
        break;
    case BLUFI_STATION_DISCONNECTED:
        gl_sta_connected = false;
        break;
    case BLUFI_STATION_GOT_IP:
    {
        axk_blufi_extra_info_t info;
        memset(&info, 0, sizeof(axk_blufi_extra_info_t));
        wifi_conn_ap_info_get(&g_blufi_config.wifi.sta);
        memcpy(info.sta_bssid, g_blufi_config.wifi.sta.cwjap_param.bssid, 6);
        info.sta_bssid_set = true;
        info.sta_ssid = (uint8_t *)g_blufi_config.wifi.sta.cwjap_param.ssid;
        info.sta_ssid_len = strlen(g_blufi_config.wifi.sta.cwjap_param.ssid);

        if (ble_is_connected == true)
        {
            axk_blufi_send_wifi_conn_report(g_blufi_config.wifi.cwmode, _BLUFI_STA_CONN_SUCCESS, 0, &info);
        }
        else
        {
            printf("BLUFI BLE is not connected yet\r\n");
        }

        printf("BLUFI save ssid&&pwd \r\n");
        g_blufi_config.wifi.cwmode = WIFIMODE_STA;
    }
    break;
    default:
        break;
    }
}

static void example_event_callback(_blufi_cb_event_t event, _blufi_cb_param_t *param)
{
    /* actually, should post to blufi_task handle the procedure,
     * now, as a example, we do it more simply */
    switch (event)
    {
    case AXK_BLUFI_EVENT_INIT_FINISH:
        printf("BLUFI init finish\n");

        axk_blufi_adv_start();
        break;
    case AXK_BLUFI_EVENT_DEINIT_FINISH:
        printf("BLUFI deinit finish\n");
        break;
    case AXK_BLUFI_EVENT_BLE_CONNECT:
        printf("BLUFI ble connect\n");
        ble_is_connected = true;
        axk_blufi_adv_stop();
        blufi_security_init();
        break;
    case AXK_BLUFI_EVENT_BLE_DISCONNECT:
        printf("BLUFI ble disconnect\n");
        ble_is_connected = false;
        blufi_security_deinit();
        axk_blufi_adv_start();
        break;
    case AXK_BLUFI_EVENT_SET_WIFI_OPMODE:
        printf("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode);
        // if (axk_hal_wifi_mode_set(WIFIMODE_STA, 0) != BLUFI_ERR_SUCCESS)
        // {
        //     printf("BLUFI axk_hal_wifi_mode_set fail\r\n");
        //     break;
        // }
        g_blufi_config.wifi.cwmode = WIFIMODE_STA;
        break;
    case AXK_BLUFI_EVENT_REQ_CONNECT_TO_AP:
    {
        cwjap_param_t cwjap_param = {0};
        printf("BLUFI requset wifi connect to AP\n");
        cwjap_param = g_blufi_config.wifi.sta.cwjap_param;
        if (axk_hal_conn_ap_info_set(&cwjap_param) != BLUFI_ERR_SUCCESS)
        {
            printf("BLUFI axk_hal_conn_ap_info_set fail\r\n");
            break;
        }
        g_blufi_config.wifi.sta.state = BLUFI_WIFI_STATE_CONNECTING;
    }

    break;
    case AXK_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
        printf("BLUFI requset wifi disconnect from AP\n");
        axk_hal_disconn_ap();
        break;
    case AXK_BLUFI_EVENT_REPORT_ERROR:
        printf("BLUFI report error, error code %d\n", param->report_error.state);
        axk_blufi_send_error_info(param->report_error.state);
        break;
    case AXK_BLUFI_EVENT_GET_WIFI_STATUS:
    {
        wifi_mode_t mode;
        mode = g_blufi_config.wifi.cwmode;

        if (gl_sta_connected)
        {
            axk_blufi_extra_info_t info;
            memset(&info, 0, sizeof(axk_blufi_extra_info_t));
            wifi_conn_ap_info_get(&g_blufi_config.wifi.sta);
            memcpy(info.sta_bssid, g_blufi_config.wifi.sta.cwjap_param.bssid, 6);
            info.sta_bssid_set = true;
            info.sta_ssid = (uint8_t *)g_blufi_config.wifi.sta.cwjap_param.ssid;
            info.sta_ssid_len = strlen(g_blufi_config.wifi.sta.cwjap_param.ssid);
            axk_blufi_send_wifi_conn_report(mode, _BLUFI_STA_CONN_SUCCESS, 0, &info);
        }
        else
        {
            axk_blufi_send_wifi_conn_report(mode, _BLUFI_STA_CONN_FAIL, 0, NULL);
        }
        printf("BLUFI get wifi status from AP\n");

        break;
    }
    case AXK_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:
        printf("blufi close a gatt connection\r\n");
        axk_blufi_disconnect();
        break;
    case AXK_BLUFI_EVENT_DEAUTHENTICATE_STA:
        /* TODO */
        break;
    case AXK_BLUFI_EVENT_RECV_STA_BSSID:
        memset(g_blufi_config.wifi.sta.cwjap_param.bssid, 0, 6);
        memcpy(g_blufi_config.wifi.sta.cwjap_param.bssid, param->sta_bssid.bssid, 6);
        // sta_config.sta.bssid_set = 1;
        // esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        printf("Recv STA BSSID %s\r\n", param->sta_bssid.bssid);
        break;
    case AXK_BLUFI_EVENT_RECV_STA_SSID:
        memset(g_blufi_config.wifi.sta.cwjap_param.ssid, 0, 33);
        strncpy(g_blufi_config.wifi.sta.cwjap_param.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);

        printf("Recv STA SSID %s\r\n", (char *)g_blufi_config.wifi.sta.cwjap_param.ssid);
        break;
    case AXK_BLUFI_EVENT_RECV_STA_PASSWD:
        memset(g_blufi_config.wifi.sta.cwjap_param.pwd, 0, 64);
        strncpy(g_blufi_config.wifi.sta.cwjap_param.pwd, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
        printf("Recv STA PASSWORD %s\r\n", (char *)g_blufi_config.wifi.sta.cwjap_param.pwd);
        break;
    case AXK_BLUFI_EVENT_RECV_SOFTAP_SSID:
        break;
    case AXK_BLUFI_EVENT_RECV_SOFTAP_PASSWD:
        break;
    case AXK_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM:
        break;
    case AXK_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE:
        break;
    case AXK_BLUFI_EVENT_RECV_SOFTAP_CHANNEL:
        break;
    case AXK_BLUFI_EVENT_GET_WIFI_LIST:
        wifi_scan_start();
        break;
    case AXK_BLUFI_EVENT_RECV_CUSTOM_DATA:
        printf("Recv Custom Data len:%d\r\n", param->custom_data.data_len);
        printf("Custom Data:%.*s\n", param->custom_data.data_len, param->custom_data.data);
        // echo
        axk_blufi_send_custom_data(param->custom_data.data, param->custom_data.data_len);
        break;
    case AXK_BLUFI_EVENT_RECV_USERNAME:
        /* Not handle currently */
        break;
    case AXK_BLUFI_EVENT_RECV_CA_CERT:
        /* Not handle currently */
        break;
    case AXK_BLUFI_EVENT_RECV_CLIENT_CERT:
        /* Not handle currently */
        break;
    case AXK_BLUFI_EVENT_RECV_SERVER_CERT:
        /* Not handle currently */
        break;
    case AXK_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY:
        /* Not handle currently */
        break;
        ;
    case AXK_BLUFI_EVENT_RECV_SERVER_PRIV_KEY:
        /* Not handle currently */
        break;
    default:
        break;
    }
}

static _blufi_callbacks_t example_callbacks = {
    .event_cb = example_event_callback,
    .negotiate_data_handler = blufi_dh_negotiate_data_handler,
    .encrypt_func = blufi_aes_encrypt,
    .decrypt_func = blufi_aes_decrypt,
    .checksum_func = blufi_crc_checksum,
};

int at_blufi_start(void)
{
    int ret = -1;
    axk_hal_blufi_init();

    ret = _blufi_host_and_cb_init(&example_callbacks);
    if (ret)
    {
        printf("%s initialise failed: %d\n", __func__, ret);
    }
    return ret;
}

static void cmd_blufi_init(char *buf, int len, int argc, char **argv)
{
    at_blufi_start();
}

static void cmd_blufi_deinit(char *buf, int len, int argc, char **argv)
{
    axk_blufi_profile_deinit();
    axk_hal_blufi_deinit();
    axk_blufi_adv_stop();
    axk_hal_ble_role_set(BLE_ROLE_DEINIT);
}

const static struct cli_command cmds_user[] STATIC_CLI_CMD_ATTRIBUTE = {
    {"blufi_init", "blufi deinit", cmd_blufi_init},
    {"blufi_deinit", "blufi deinit", cmd_blufi_deinit},
};

void free_task(void *param)
{
    uint8_t pcWriteBuffer[500];
    while (1)
    {
        printf("=================================================\r\n");
        printf("\r\nremaining memory = %ld,minimum memory = %ld\r\n", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize());
        vTaskDelay(3 * 1000);
    }
    vTaskDelete(NULL);
}

static void proc_main_entry(void *pvParameters)
{
    wifi_interface_init(blufi_wifi_event);
    at_blufi_start();
    vTaskDelete(NULL);
}

void main()
{
    static TaskHandle_t proc_main_task;
    bl_sys_init();
    xTaskCreate(proc_main_entry, (char *)"main_entry", 1024, NULL, 15, &proc_main_task);
    xTaskCreate(free_task, (char *)"free_task", 750, NULL, 1, NULL);
    tcpip_init(NULL, NULL);
    printf("blufi demo test\r\n");
}

FAQ & Troubleshooting

⚠️ The app can't find the BLUFI device
Cause: the device hasn't started provisioning advertising yet, distance too far, or the phone's Bluetooth/location permission is off
Fix: wait for the serial to print BLUFI init finish before scanning; Android phones must enable the "location permission" to scan BLE; move the phone close to the board (within 1 m); if the app was connected to another device, disconnect and rescan

⚠️ App connected, but the device can't connect to the router after sending
Cause: the router is on 5GHz, the password is wrong, or the signal is too weak
Fix: confirm the router broadcasts 2.4GHz (5GHz isn't supported); double-check the SSID/password (watch out for spaces); move the board near the router for testing

⚠️ After a successful provisioning, the device doesn't auto-connect after power-cycling
Cause: the provisioning info wasn't saved (powered off before the BLUFI save ssid&&pwd log)
Fix: wait for the serial to print BLUFI save ssid&&pwd and CODE_WIFI_ON_GOT_IP before powering off; rerun the provisioning flow

⚠️ Want to provision to a different Wi-Fi
Cause: the device saved the old password and auto-connects, so it no longer enters provisioning advertising
Fix: press and hold EN to reset and re-enter provisioning mode, or use the serial CLI to run blufi_deinit then blufi_init to restart provisioning

⚠️ No logs on the serial
Cause: wrong serial assistant baud rate (this example uses the system default log serial at 921600), or wrong serial number
Fix: set the serial assistant baud rate to 921600; confirm the serial number (ls /dev/ttyUSB*); after flashing, unplug/replug the USB once and reopen the serial

⚠️ Flashing keeps waiting, progress bar doesn't move
Cause: download mode wasn't entered, or the cable only charges and can't transfer data
Fix: press and hold EN during flashing to enter download mode as prompted; try a Type-C data-capable cable

Self-Check

The serial prints BLUFI init finishBLUFI ble connectBLUFI save ssid&&pwd in order, and the EspBlufi app shows the device successfully connected to the router — the blufi provisioning is verified.

Released under the MIT License. Build Time 2026-09-11 14:52:23