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) exampleapplications/bluetooth/blufi; the code can be found directly in the local SDK.
Open a terminal and enter the official blufi example project directory:
cd ~/Ai-Thinker-WB2/applications/bluetooth/blufi
Note:
cdis the “change directory” command — entering the blufi example project directory; all subsequentmakebuild andmake flashflash 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 |
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_initstarts provisioning,blufi_deinitexits provisioning (back to normal mode); usable with the CLI when debugging.
Build in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program flashed into the board) the board can run;-j8builds 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 — runcd toolchain/riscv/Linux && . chmod755.shfirst, then rebuild.
Keep the board connected via USB, confirm the serial device, and flash:
make flash p=/dev/ttyUSB0 b=921600
Note:
make flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (change it to your computer’s actual one — check withls /dev/ttyUSB*),b=921600is 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.
- 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).
- After flashing, the board automatically enters provisioning mode (BLE advertising); the app’s home page should show a device named BLUFI — tap Connect.
- 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.
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_startis a custom interface of this project (defined inblufi_main.c);axk_hal_blufi_init,_blufi_host_and_cb_init,axk_blufi_adv_*,blufi_security_*come from the SDK'scomponents/network/blufi/component;wifi_interface_initcomes 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; passNULLusuallyarg: callback argument; passNULL
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_tstruct pointer, required (this exampleexample_callbacks, containingevent_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, shapedvoid cb(int event, void *param), required (this exampleblufi_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 example3000(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, shapedvoid task(void *arg), requiredname: task name string (for debugging); this example"main_entry"/"free_task"stack: task stack size (in words); this example1024/750param: parameter pointer passed to the entry function; passNULLif noneprio: task priority, values:0(lowest) ~19(highest, per SDK config); this example15/1handle: task handle output pointer; passNULLif 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
#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.

