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 finish → BLUFI ble connect → BLUFI 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 finish → BLUFI ble connect → BLUFI 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-30 17:31:25