WI-FI provision via BLE

The advantage of dual-core chip (OPL series), QuickDev-Framework provide a process to provision Wi-Fi credentials of the Home AP via the Bluetooth low energy (BLE). This setion can help the developer to quickly understand and implement the protocol into the application.

This block diagram introduces the architecture of the provision Wi-Fi credentials via BLE. The phone (BLE central) will transmitting the required message to the device. When the device received the message, it will process the Wi-Fi connection base on the given information.

Opulinks also provide a reference code-base for Phone APP, the front-end developer can use the needed code-base to enhance on their project/product.

  • Android APP :
  • IOS APP :
  • WeChat mini APP :

Provision Process

QuickDev-Framework had provided the Wi-Fi provision via BLE protocol, for a quick look to the example which can demonstrate the Wi-Fi provision via BLE protocol, please check on QD_APP section.

In below diagram, it present a simplify steps that how's the provision done.

When the phone (BLE central) wants to provision the Wi-Fi credential and connect to AP, the phone (BLE central) will send the command and required message to the device, e.g. Wi-Fi scan request or Wi-Fi connect request.

And the device is responsible to receive and parsing the required message, then progress the Network manager to processing according to different events.

While Network manager had done with indicate or unsolicited callback returns, then the device will transit the corresponding response or result to the phone (BLE central) for notify the status.

The focusing of provision process is to create a BLE data command list to letting the phone (BLE central) and the device can recognize the data and handling in such.

OPL Service

OPL service is one of the profile service collection in BLE which designed by Opulinks that including the Wi-Fi provision command & handler needed.

Info

To understand the BLE manager and BLE service can refer to BluetoothLE

The OPL service UUID defined in quick_dev/app_ref/ble_data_prot/opl_data/opl_svc.h

Service/Characteristic UUID
OPL service 0xAAAA
DATA IN character 0xBBB0
DATA OUT character 0xBBB1

Initialize

To using this service on your application, please follow in below items.

Initialize the OPL service in your application

Add OPL_Svc_Init() in app_main.c

void APP_BleInit(void)
{
    // assign unsolicited callback function
    Opl_Ble_Uslctd_CB_Reg(&APP_BleUnsolicitedCallback);

    // register service
    GAP_Svc_Init();
    GATT_Svc_Init();

    // register opl service
    OPL_Svc_Init();  // <- initialize OPL service

    // initialize the ble manager (auto-adv)
    Opl_Ble_Init_Req(true);

    // user implement
}
Generate a data passthrough

When the data received, the OPL_SVC_DATA_IN characteristic contents in OPL service will execute a context switch to passing the received data from BLE manager to host application, so we need to create a catcher on application side, and let it process the data parsing and progress.

Add the APP_EVT_BLE_DATA_IND event and handler in app_main.c.

static T_AppEvtHandlerTbl g_tAppEvtHandlerTbl[] = 
{
    ...

    {APP_EVT_BLE_DATA_IND,                  APP_EvtHandler_BleDataInd},

    ...

    {0xFFFFFFFF,                            NULL},
};

Then add OPL_DataRecvHandler() to let the handler do parsing and progress.

static void APP_EvtHandler_BleDataInd(uint32_t u32EventId, void *pData, uint32_t u32DataLen)
{
    OPL_DataRecvHandler(pData, (uint16_t)u32DataLen);
}

At final step, we must enable the protocol which defined in `qd_module.h"

//==========================================================
// <h> OPL Data protocol
//==========================================================

// <e> OPL_DATA_ENABLED - Opulinks BLE WI-FI data protocol
//==========================================================
#ifndef OPL_DATA_ENABLED
#define OPL_DATA_ENABLED                        (1)
#endif

And that's the wrap, the Wi-Fi provision via BLE function has been activated in your application. It's recommend to using our standard phone APP by searching "Opulinks Wireless Utilities" at APP store to try with.


How to...

How to create an user defined service (e.g. quick_dev\app_ref\ble_data_prot\opl_svc.c) in BLE Manager

Please refer to BluetoothLE

How to receive BLE data in and then export to BLE Application in application

  • Create a gatt dispatch handler (e.g. OPL_Svc_GattDispatchHandler()) in user defined service
static T_OplErr OPL_Svc_GattDispatchHandler(MESSAGEID tId, MESSAGE tMsg)
{
    switch(tId)
    {
        case LE_GATT_MSG_ACCESS_READ_IND:
        {
            OPL_Svc_GattDispatchReadHandler((LE_GATT_MSG_ACCESS_READ_IND_T *)tMsg);
            break;
        }

        case LE_GATT_MSG_ACCESS_WRITE_IND:
        {
            OPL_Svc_GattDispatchWriteHandler((LE_GATT_MSG_ACCESS_WRITE_IND_T *)tMsg);
            break;
        }

        case LE_GATT_MSG_NOTIFY_CFM:
        {
            Opl_Ble_Send_Message(OPL_SVC_EVT_SEND_TO_PEER_CFM, NULL, 0, 0);
            break;
        }

        default:
        {
            return OPL_ERR_CASE_INVALID;
        }
    }

    return OPL_OK;
}

This example is receiving LE_GATT_MSG_ACCESS_WRITE_IND event to handle BLE data via OPL_Svc_GattDispatchWriteHandler()

tatic void OPL_Svc_GattDispatchWriteHandler(LE_GATT_MSG_ACCESS_WRITE_IND_T *ind)
{
    // process the write access activity in each characteristic
    uint8_t u8AttErr = 0;
    uint16_t u16AttrId = ind->handle - g_tOplSvcHandle.ptSvcDef->startHdl;

    switch(u16AttrId)
    {
        case OPL_SVC_IDX_DATA_IN_VAL:
        {
            APP_SendMessage(APP_EVT_BLE_DATA_IND, ind->pVal, ind->len);

            break;
        }

        case OPL_SVC_IDX_DATA_OUT_CFG:
        {
            uint16_t u16Enable = *((uint16_t *)ind->pVal);

            if ((ind->len == 2) && (u16Enable <= 1))
            {
                LeGattChangeAttrVal(g_tOplSvcHandle.ptSvcDef, OPL_SVC_IDX_DATA_OUT_CFG, sizeof(u16Enable), &u16Enable);
            }
            else
            {
                u8AttErr = LE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
            }

            break;
        }

        default:
        {
            u8AttErr = LE_ATT_ERR_WRITE_NOT_PERMITTED;
            break;
        }
    }

If receiving OPL_SVC_IDX_DATA_IN_VAL, OPL_Svc_GattDispatchWriteHandler() could send BLE data to application via APP_SendMessage()

In application, developer could create a function (e.g. APP_EvtHandler_BleDataInd()) to send BLE data to BLE Application.

static void APP_EvtHandler_BleDataInd(uint32_t u32EventId, void *pData, uint32_t u32DataLen)
{
    OPL_DataRecvHandler(pData, (uint16_t)u32DataLen);
}

How to handle Wi-Fi provision message via BLE in BLE Application

BLE Application is responsible for parsing the messages from BLE, and then processing these events by category. Wi-Fi provision messages include Wi-Fi scan, Wi-Fi connect, Wi-Fi disconnect, Wi-Fi reconnect, read device Wi-Fi information, write Wi-Fi device information, get device Wi-Fi status and reset Wi-Fi. It is a BLE Application event handler table g_tOplDataEventHandlerTbl to handle these events.

static T_OplDataEventTable g_tOplDataEventHandlerTbl[] =
{
    {OPL_DATA_REQ_SCAN,                      OPL_DataProtocol_Scan},
    {OPL_DATA_REQ_CONNECT,                   OPL_DataProtocol_Connect},
    {OPL_DATA_REQ_DISCONNECT,                OPL_DataProtocol_Disconnect},
    {OPL_DATA_REQ_RECONNECT,                 OPL_DataProtocol_Reconnect},
    {OPL_DATA_REQ_READ_DEVICE_INFO,          OPL_DataProtocol_ReadDeviceInfo},
    {OPL_DATA_REQ_WRITE_DEVICE_INFO,         OPL_DataProtocol_WriteDeviceInfo},
    {OPL_DATA_REQ_WIFI_STATUS,               OPL_DataProtocol_WifiStatus},
    {OPL_DATA_REQ_RESET,                     OPL_DataProtocol_Reset},

For example, if receiving OPL_DATA_REQ_SCAN, OPL_DataProtocol_Scan handler will send Wi-Fi scan message to Network Manager to execute Wi-Fi scan.

static void OPL_DataProtocol_Scan(uint16_t type, uint8_t *data, int len)
{
    OPL_LOG_DEBG(OPL, "OPL_DATA_REQ_SCAN");

    // reset connection config table
    memset(&g_tOplDataConnCfg, 0, sizeof(T_OplDataConnCfg));

    g_tOplDataConnCfg.u8ConnectType = OPL_DATA_CONN_TYPE_BSSID;

    // trigger scan request
    APP_NmWifiScanReq(OPL_DataHandler_WifiScanDoneIndCb);
}

How to respnose Wi-Fi process result to phone

BLE Application send Wi-Fi provision message to Network Manager, and then Network Manager will create a callback function to handle indicate callback.

For example, if Wi-Fi scan done, the Wi-FI scan done callback OPL_DataHandler_WifiScanDoneIndCb will follow up on scan done.

void OPL_DataHandler_WifiScanDoneIndCb(T_OplErr tEvtRst)
{
    OPL_LOG_DEBG(OPL, "Wifi scan done ind %d", tEvtRst);

    if(OPL_DATA_CONN_TYPE_BSSID == g_tOplDataConnCfg.u8ConnectType)
    {
        // CK_DATA_REQ_SCAN cmd, just do report scan list
        _OPL_DataHandler_SendScanReport();
        OPL_DataSendResponse(OPL_DATA_RSP_SCAN_END, 0);
    }
}

_OPL_DataHandler_SendScanReport will obtain the information of APs via Wi-Fi Manager in the environment, and sort out the AP list.

static int _OPL_DataHandler_SendScanReport(void)
{
    wifi_scan_info_t *pstAPList = NULL;
    wifi_auto_connect_info_t *info = NULL;
    T_WmScanInfo *pstWifiAPList = NULL;

    uint8_t u8APPAutoConnectGetApNum = 0;
    // uint8_t u8IsUpdate = false;
    uint16_t u16apCount = 0;

    int32_t i = 0, j = 0;

    // TODO: get ap number
    Opl_Wifi_ApNum_Get(&u16apCount);

    OPL_LOG_INFO(OPL, "AP num = %d", u16apCount);

    pstAPList = (wifi_scan_info_t *)malloc(sizeof(wifi_scan_info_t) * u16apCount);

    // TODO: get ap record
    Opl_Wifi_ApRecord_Get(&u16apCount, pstAPList);

    pstWifiAPList = (T_WmScanInfo *)malloc(sizeof(T_WmScanInfo) * u16apCount);

    memset(pstWifiAPList , 0 , sizeof(T_WmScanInfo) * u16apCount);

    // TODO: get auto connect ap number
    Opl_Wifi_AutoConnectApNum_Get(&u8APPAutoConnectGetApNum);

    if (u8APPAutoConnectGetApNum)
    {
        info = (wifi_auto_connect_info_t *)malloc(sizeof(wifi_auto_connect_info_t) * u8APPAutoConnectGetApNum);

        memset(info, 0, sizeof(wifi_auto_connect_info_t) * u8APPAutoConnectGetApNum);

        for (i = 0; i < u8APPAutoConnectGetApNum; i++)
        {

            // TODO: get auto connected ap info
            Opl_Wifi_AutoConnectApInfo_Get(i, info + i);
    }

    /* build blewifi ap list */
    for (i = 0; i < u16apCount; ++i)
    {
        memcpy(pstWifiAPList[i].ssid, pstAPList[i].ssid, sizeof(pstAPList[i].ssid));
        memcpy(pstWifiAPList[i].bssid, pstAPList[i].bssid, WIFI_MAC_ADDRESS_LENGTH);
        pstWifiAPList[i].rssi = pstAPList[i].rssi;
        pstWifiAPList[i].auth_mode = pstAPList[i].auth_mode;
        pstWifiAPList[i].ssid_length = strlen((const char *)pstAPList[i].ssid);
        pstWifiAPList[i].connected = 0;
#if (1 == FLITER_STRONG_AP_EN)
        pstWifiAPList[i].u8IgnoreReport = false;
#endif
        for (j = 0; j < u8APPAutoConnectGetApNum; j++)
        {
            if ((info+j)->ap_channel)
            {
                if(!memcmp(pstWifiAPList[i].ssid, (info+j)->ssid, sizeof((info+j)->ssid)) && !memcmp(pstWifiAPList[i].bssid, (info+j)->bssid, sizeof((info+j)->bssid)))
                {
                    pstWifiAPList[i].connected = 1;
                    break;
                }
            }
        }
    }

    /* Send Data to BLE */
    /* Send AP inforamtion individually */
    for (i = 0; i < u16apCount; ++i)
    {
#if (1 == FLITER_STRONG_AP_EN)
        if(true == pstWifiAPList[i].u8IgnoreReport)
        {
            continue;
        }
#endif
        if(pstWifiAPList[i].ssid_length != 0)
        {
            _OPL_DataHandler_SendSignalScanReport(1, &pstWifiAPList[i]);
            osDelay(100);
        }
    }

    if (pstAPList)
        free(pstAPList);

    if (pstWifiAPList)
        free(pstWifiAPList);

    if (info)
        free(info);

    return ubAppErr;
}

_OPL_DataHandler_SendSignalScanReport will organize the AP list

static void _OPL_DataHandler_SendSignalScanReport(uint16_t apCount, T_WmScanInfo *ap_list)
{
    uint8_t *data;
    int data_len;
    uint8_t *pos;
    int malloc_size = sizeof(T_WmScanInfo) * apCount;

    pos = data = malloc(malloc_size);
    if (data == NULL)
    {
        OPL_LOG_ERRO(OPL, "malloc fail");
        return;
    }

    for (int i = 0; i < apCount; ++i)
    {
        uint8_t len = ap_list[i].ssid_length;

        data_len = (pos - data);

        *pos++ = len;
        memcpy(pos, ap_list[i].ssid, len);
        pos += len;
        memcpy(pos, ap_list[i].bssid,6);
        pos += 6;
        *pos++ = ap_list[i].auth_mode;
        *pos++ = ap_list[i].rssi;
#ifdef CK_DATA_USE_CONNECTED
        *pos++ = ap_list[i].connected;
#else
        *pos++ = 0;
#endif
    }

    data_len = (pos - data);

    /* create scan report data packet */
    OPL_DataSendEncap(OPL_DATA_RSP_SCAN_REPORT, data, data_len);

    free(data);
}

OPL_DataSendEncap is responsible for packing data in accordance with BLE packet format.

void OPL_DataSendEncap(uint16_t u16Type, uint8_t *pu8Data, uint32_t u32TotalDataLen)
{
    T_OplDataHdrTag *tHdrTag = NULL;
    int remain_len = u32TotalDataLen;

    /* 1.fragment data packet to fit MTU size */

    /* 2.Pack blewifi header */
    tHdrTag = malloc(sizeof(T_OplDataHdrTag) + remain_len);
    if (tHdrTag == NULL)
    {
        OPL_LOG_ERRO(OPL, "malloc fail");
        return;
    }

    tHdrTag->u16EventId = u16Type;
    tHdrTag->u16DataLen = remain_len;
    if (tHdrTag->u16DataLen)
        memcpy(tHdrTag->au8Data, pu8Data, tHdrTag->u16DataLen);

    /* 3.send app data to BLE stack */

    Opl_Ble_Send_Message(OPL_SVC_EVT_SEND_DATA, (uint8_t *)tHdrTag, (tHdrTag->u16DataLen + sizeof(T_OplDataHdrTag)), 0);

    free(tHdrTag);
}

Opl_Ble_Send_Message is responsible for sending scan done result (e.g. AP list) to phone via BLE Manager.