Pairing Wireless Gateways in EZ Mode

Last Updated on : 2022-11-24 09:20:25download

This topic describes how to implement wireless gateway pairing in EZ mode by using the integrated SDK.

Background

The EZ mode is also known as Wi-Fi Easy Connect. In the EZ mode, the mobile phone encodes information such as the token, the connected router’s SSID and password and transmits the encoded data over a network by using multicast or broadcast. The wireless gateway that operates in sniffer mode can capture and parse the packets over the network and extract the information to complete the pairing process.

There are two encoding methods available:

  • Data is encoded with the length of the user datagram protocol (UDP) packet and transmitted via UDP broadcast.
  • Data is encoded with the MAC address and transmitted via UDP multicast.

Compared with the AP mode, pairing in EZ mode is easy and fast. But it might fail due to the incompatibility between the mobile phone and router or radio interference.

Implementation

The SDK provides a suite of standard APIs for pairing a wireless gateway in EZ mode. To enable wireless gateways to be paired, you only need to achieve the function adaptation.

The following sequence diagram shows the interaction in EZ mode:

Pairing Wireless Gateways in EZ Mode

The sample code uses the Realtek RTL8197F as an example to describe how to implement network adaptation. The operations can vary depending on chips or systems. To make the demonstration straightforward, we run the shell commands without considering the performance.

  1. Set the wireless pairing mode to EZ mode only during the SDK initialization.

    // ...
    
    int main(int argc, char **argv)
    {
        // ...
    #if defined(GW_SUPPORT_WIRED_WIFI) && (GW_SUPPORT_WIRED_WIFI==1)
        TUYA_CALL_ERR_RETURN(tuya_iot_wired_wf_sdk_init(IOT_GW_NET_WIRED_WIFI, GWCM_OLD, WF_START_SMART_ONLY, PID, USER_SW_VER, NULL, 0));
    #elif defined(WIFI_GW) && (WIFI_GW==1)
        TUYA_CALL_ERR_RETURN(tuya_iot_wf_sdk_init(GWCM_OLD, WF_START_SMART_ONLY, PID, USER_SW_VER, NULL, 0));
    #else
        // The wired connection SDK that does not support pairing over a wireless connection.
        return OPRT_COM_ERROR;
    #endif
        // ...
    }
    
  2. Implement the function to get the MAC address of the wireless network adapter.

    OPERATE_RET tuya_adapter_wifi_get_mac(CONST WF_IF_E wf, NW_MAC_S *mac)
    {
        CHAR_T buf[256] = {0};
        CHAR_T *pstart = NULL;
        FILE *fp = NULL;
    
        fp = popen("ifconfig " WLAN_DEV, "r");
        if (fp == NULL) {
            return OPRT_COM_ERROR;
        }
    
        while (fgets(buf, SIZEOF(buf), fp) != NULL) {
            pstart = strstr(buf, "HWaddr ");
            if (pstart != NULL) {
                INT_T x1, x2, x3, x4, x5, x6;
                sscanf(pstart + strlen("HWaddr "), "%x:%x:%x:%x:%x:%x", &x1, &x2, &x3, &x4, &x5, &x6);
                mac->mac[0] = x1 & 0xFF;
                mac->mac[1] = x2 & 0xFF;
                mac->mac[2] = x3 & 0xFF;
                mac->mac[3] = x4 & 0xFF;
                mac->mac[4] = x5 & 0xFF;
                mac->mac[5] = x6 & 0xFF;
                break;
            }
        }
    
        pclose(fp);
    
        PR_DEBUG("MAC Addr: %02X-%02X-%02X-%02X-%02X-%02X", mac->mac[0], mac->mac[1], mac->mac[2], mac->mac[3], mac->mac[4], mac->mac[5]);
    
        return OPRT_OK;
    }
    
  3. Implement the function to set the wireless operation mode based on the specified parameter.

    OPERATE_RET tuya_adapter_wifi_set_work_mode(CONST WF_WK_MD_E mode)
    {
        /**
         * Configuring the sniffer mode for Realtek RTL8197F is not required. Save the value to a global variable.
         */
        g_cur_mode = mode;
    
        switch (mode) {
            case WWM_STATION:
            {
                system("iwpriv " WLAN_DEV " set_mib opmode=0x8");
                break;
            }
            case WWM_SOFTAP:
            {
                system("iwpriv " WLAN_DEV " set_mib opmode=0x10");
                break;
            }
            default:
            {
                break;
            }
        }
    
        return OPRT_OK;
    }
    
  4. Implement the function to scan the area for available access points (APs). The discovered APs will be listed with their information such as the channel, SSID, and BSSID and sent to the SDK. The SDK will only poll the listed channels. If the AP list is empty, the SDK will poll all the channels.

    OPERATE_RET tuya_adapter_wifi_all_ap_scan(AP_IF_S **aps, UINT_T *num)
    {
        INT_T idx = -1;
        FILE *fp = NULL;
        CHAR_T buf[128] = {0};
        CHAR_T *pstart = NULL;
        STATIC AP_IF_S s_aps[MAX_SCAN_NUM];
    
        memset(s_aps, 0, SIZEOF(s_aps));
        *aps = s_aps;
        *num = 0;
    
        system("iwpriv wlan0 at_ss && sleep 2");
        fp = fopen("/proc/wlan0/SS_Result", "r");
        if (fp == NULL) {
            return OPRT_OK;
        }
    
        while (1) {
            if (fgets(buf, SIZEOF(buf), fp) == NULL) {
                break;
            }
            pstart = strstr(buf, "HwAddr");
            if (pstart == NULL) {
                continue;
            }
    
            idx++;
    
            /* BSSID */
            INT_T x1, x2, x3, x4, x5, x6;
            sscanf(pstart + strlen("HwAddr: "), "%02x%02x%02x%02x%02x%02x", &x1, &x2, &x3, &x4, &x5, &x6);
            s_aps[idx].bssid[0] = x1 & 0xFF;
            s_aps[idx].bssid[1] = x2 & 0xFF;
            s_aps[idx].bssid[2] = x3 & 0xFF;
            s_aps[idx].bssid[3] = x4 & 0xFF;
            s_aps[idx].bssid[4] = x5 & 0xFF;
            s_aps[idx].bssid[5] = x6 & 0xFF;
    
            /* Channel */
            memset(buf, 0, SIZEOF(buf));
            if (fgets(buf, SIZEOF(buf), fp) == NULL) {
                break;
            }
            pstart = strstr(buf, "Channel");
            if (pstart == NULL) {
                break;
            }
            sscanf(pstart + strlen("Channel: "), "%d", &(s_aps[idx].channel));
    
            /* SSID */
            memset(buf, 0, SIZEOF(buf));
            if (fgets(buf, SIZEOF(buf), fp) == NULL) {
                break;
            }
            pstart = strstr(buf, "SSID");
            if (pstart == NULL) {
                break;
            }
            sscanf(pstart + strlen("SSID: "), "%s", s_aps[idx].ssid);
            s_aps[idx].s_len = strlen(s_aps[idx].ssid);
        }
    
        *num = idx + 1;
    
        return OPRT_OK;
    }
    
  5. Implement the function to turn on or off the sniffer mode.

    /**
     * a) en == TRUE: Turn on sniffer mode. You need to start a thread to capture wireless packets and send the captured packet to the SDK for parsing.
     *        To reduce workload, filter out the packets with the frame type `Management` or `Control` and only send the packets with the frame type `Data` to the SDK for processing.
     * b) en == FALSE: Turn off sniffer mode. You need to destroy the thread. To make sure the function for connecting to the router is triggered, force the wireless mode to be switched to AP mode.
     */
    OPERATE_RET tuya_adapter_wifi_sniffer_set(CONST BOOL_T en, CONST SNIFFER_CALLBACK cb)
    {
        PR_DEBUG("WiFi Set Sniffer, en: %d", en);
    
        if (en == g_sniffer_en_flag) {
            PR_WARN("sniffer status not changed, en: %d, en_flag: %d", en, g_sniffer_en_flag);
            return OPRT_OK;
        }
    
        g_sniffer_en_flag = en;
    
        if (en) {
            PR_DEBUG("enable sniffer");
            g_sniffer_cb = cb;
            tuya_adapter_wifi_set_work_mode(WWM_SNIFFER);
            if (0 != pthread_create(&g_sniffer_tid, NULL, sniffer_run, NULL)) {
                PR_ERR("pthread_create error");
                return OPRT_COM_ERROR;
            }
        } else {
            PR_DEBUG("disable sniffer");
            pthread_join(g_sniffer_tid, NULL);
            tuya_hal_wifi_set_work_mode(WWM_SOFTAP);
        }
    
        return OPRT_OK;
    }
    
    STATIC OPERATE_RET wlan_80211_packet_parse(BYTE_T *buf, UINT_T len)
    {
        WLAN_80211_HEAD_S *wbuf = (WLAN_80211_HEAD_S *)buf;
    
        /* version must be 0x00 */
        if ((wbuf->frmae_ctl1 & 0x03) != 0x00) {
            return OPRT_COM_ERROR;
        }
    
        switch ((wbuf->frmae_ctl1) & 0x0c) {
            case 0x08: {
                /* Only Receive Data Frame */
                break;
            }
            default: {
                return OPRT_COM_ERROR;
            }
        }
    
        /* From DS = 1 */
        if ((wbuf->frmae_ctl2 & 0x3) == 0x2) {
            /**
             * Filter out data by destination address.
             *   1. For Broadcast, FF:FF:FF:FF:FF:FF is valid
             *   2. For Multicast, prefix with 01:00:5E is valid
             */
            if (!memcmp(wbuf->addr1, "\xff\xff\xff\xff\xff\xff", 6) || \
                !memcmp(wbuf->addr1, "\x01\x00\x5e", 3)) {
                return OPRT_OK;
            }
        }
    
        return OPRT_COM_ERROR;
    }
    
    STATIC VOID *sniffer_run(VOID *arg)
    {
        INT_T sock = 0;
        struct ifreq ifr;
        size_t recv_len = 0;
        BYTE_T recv_buf[1024] = {0};
    
        PR_DEBUG("sniffer task started");
    
        sock = socket(PF_PACKET, SOCK_RAW, htons(0x03)); //ETH_P_ALL
        if (sock < 0) {
            PR_ERR("socket error");
            return (VOID *)0;
        }
    
        memset(&ifr, 0x00, SIZEOF(ifr));
        strncpy(ifr.ifr_name, WLAN_DEV , SIZEOF(ifr.ifr_name) - 1);
        setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (VOID *)&ifr, SIZEOF(ifr));
    
        while (g_sniffer_en_flag && (g_sniffer_cb != NULL)) {
            recv_len = recvfrom(sock, recv_buf, SIZEOF(recv_buf), 0, NULL, NULL);
            if (recv_len <= 0) {
                continue;
            }
    
            struct rtl_wifi_header *rh = (struct rtl_wifi_header *)recv_buf;
            if ((rh->pkt_len > 0) && (rh->data[0] != 0xff)) {
                if (wlan_80211_packet_parse(rh->data, rh->pkt_len) == 0) {
                    g_sniffer_cb(rh->data, rh->pkt_len, 99);
                }
            }
        }
    
        g_sniffer_cb = NULL;
    
        close(sock);
    
        return (void *)0;
    }
    
  6. Implement the function to set the channels to sniff. This enables the wireless network adapter to capture packets on the specified channel. If setting the fixed channels in sniffer mode is not needed, implementing this function is not required.

    OPERATE_RET tuya_adapter_wifi_set_cur_channel(CONST UCHAR_T channel)
    {
        /**
         * For Realtek RTL8197F, setting a fixed channel to receive packets is not required. Save the value to a global variable.
         */
        g_cur_channel = channel;
    
        return OPRT_OK;
    }
    
  7. Implement the function to get the current channel. When the SDK receives the preamble from a channel, it will stay in that channel until all the packets are received. You can use this function to get this specific channel. If setting the fixed channels in sniffer mode is not needed, implementing this function is not required.

    OPERATE_RET tuya_adapter_wifi_get_cur_channel(UCHAR_T *chan)
    {
        /**
         *  For Realtek RTL8197F, setting a fixed channel is not required so you can take the value from the global variable.
         */
        *chan = g_cur_channel;
    
        return OPRT_OK;
    }
    
  8. Implement the function to connect the gateway to the router. The SDK uses the obtained SSID and password to connect the gateway to a specific router.

    OPERATE_RET tuya_adapter_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd)
    {
        CHAR_T cmd_buf[128] = {0};
    
        /**
         * Turn off udhcpd service.
         */
        system("killall udhcpd");
    
        system("ifconfig " WLAN_DEV " down");
    
        system("iwpriv " WLAN_DEV " set_mib opmode=0x8");
        system("iwpriv " WLAN_DEV " set_mib band=11");
        system("iwpriv " WLAN_DEV " set_mib deny_legacy=0");
        system("iwpriv " WLAN_DEV " set_mib use40M=1");
        system("iwpriv " WLAN_DEV " set_mib 802_1x=0");
        snprintf(cmd_buf, SIZEOF(cmd_buf), "iwpriv %s set_mib ssid=\"%s\"", WLAN_DEV, ssid);
        system(cmd_buf);
    
        if (!passwd || (strlen(passwd) == 0)) {
            system("iwpriv " WLAN_DEV " set_mib authtype=0");
            system("iwpriv " WLAN_DEV " set_mib encmode=0");
        } else {
            system("iwpriv " WLAN_DEV " set_mib authtype=2");
            system("iwpriv " WLAN_DEV " set_mib psk_enable=3");
            system("iwpriv " WLAN_DEV " set_mib encmode=2");
            system("iwpriv " WLAN_DEV " set_mib wpa2_cipher=10");
            system("iwpriv " WLAN_DEV " set_mib wpa_cipher=10");
            snprintf(cmd_buf, SIZEOF(cmd_buf), "iwpriv %s set_mib passphrase=\"%s\"", WLAN_DEV, passwd);
            system(cmd_buf);
        }
    
        system("ifconfig " WLAN_DEV " up");
    
        system("ps | grep 'udhcpc -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");    
        system("ps | grep 'udhcpc -b -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");
        system("busybox udhcpc -b -i wlan0 -A 3 -T 3 -s /tmp/tuya/8197wlan0_udhcpc.script&");
    
        return OPRT_OK;
    }
    
  9. Implement the function to get the current operation mode of the wireless network adapter.

    OPERATE_RET tuya_adapter_wifi_get_work_mode(WF_WK_MD_E *mode)
    {
        CHAR_T buf[256] = {0};
        CHAR_T *pstart = NULL;
        CHAR_T tmp[16] = {0};
        FILE *fp = NULL;
    
        /**
         * Configuring the sniffer mode for Realtek RTL8197F is not required so you can take the value from the global variable.
         */
        if (g_cur_mode == WWM_SNIFFER) {
            *mode = WWM_SNIFFER;
            return OPRT_OK;
        }
    
        fp = popen("iwconfig " WLAN_DEV, "r");
        if (fp == NULL) {
            return OPRT_COM_ERROR;
        }
    
        while (fgets(buf, SIZEOF(buf), fp) != NULL) {
            pstart = strstr(buf, "Mode:");
            if (pstart != NULL) {
                sscanf(pstart + strlen("Mode:"), "%s ", tmp);
                break;
            }
        }
        pclose(fp);
    
        if (!strncasecmp(tmp, "Managed", strlen("Managed"))) {
            *mode = WWM_STATION;
        } else if (!strncasecmp(tmp, "Master", strlen("Master"))) {
            *mode = WWM_SOFTAP;
        } else {
            *mode = WWM_UNKNOWN;
        }
    
        return OPRT_OK;
    }
    
  10. Implement the function to get the IP address of the wireless network adapter to achieve LAN communication.

    OPERATE_RET tuya_adapter_wifi_get_ip(CONST WF_IF_E wf, NW_IP_S *ip)
    {
        CHAR_T buf[256] = {0};
        CHAR_T *pstart = NULL;
        FILE *fp = NULL;
    
        fp = popen("ifconfig " WLAN_DEV, "r");
        if (fp == NULL) {
            return OPRT_COM_ERROR;
        }
    
        while (fgets(buf, SIZEOF(buf), fp) != NULL) {
            pstart = strstr(buf, "inet ");
            if (pstart != NULL) {
                break;
            }
        }
    
        pclose(fp);
    
        pstart = strstr(buf, "inet addr:");
        if (pstart == NULL) {    
            return OPRT_COM_ERROR;
        }
        sscanf(pstart + strlen("inet addr:"), "%s", ip->ip);
    
        if (wf == WF_STATION) {
            /**
             * Make sure the IP address you obtained is the one assigned by the router instead of the default IP address.
             */
            if (!strncmp(ip->ip, AP_DEFAULT_IP, SIZEOF(ip->ip))) {
                PR_TRACE("%s is default ip of AP", ip->ip);
                return OPRT_COM_ERROR;
            }
        }
    
        PR_TRACE("IP Addr: %s", ip->ip);
    
        return OPRT_OK;
    }
    
  11. Implement the function to get the wireless status to get the connection status in station mode. With a router connected, the SDK will regularly check for the wireless connection status. The status value WSS_GOT_IP indicates the gateway is connected to a wireless network and assigned an IP address.

    OPERATE_RET tuya_adapter_wifi_station_get_status(WF_STATION_STAT_E *stat)
    {
        NW_IP_S ip = {0};
    
        if (stat == NULL) {
            PR_ERR("invalid param");
            return OPRT_INVALID_PARM;
        }
    
        if (OPRT_OK == tuya_adapter_wifi_get_ip(WF_STATION, &ip)) {
            *stat = WSS_GOT_IP;
        } else {
            *stat = WSS_CONN_FAIL;
        }
    
        return OPRT_OK;
    }
    

Things to note

This section describes the things you should take care of when you implement wireless network adaptation.

Function implementation

Do not block any of the functions you have adapted. The time for task processing should not be too long. Otherwise, issues such as pairing timeout might occur. For tasks that are processed for more than 10 seconds, an asynchronous approach is recommended.

Return value

Except for the function used to get the IP address, when the adapted functions return non-zero values, the pairing operation will be terminated. If possible, make sure to set the return value to 0.

Wireless status

After the SDK gets the SSID and password, it will check if the gateway has connected to the upstream router. If not, the SDK will call tuya_adapter_wifi_station_connect to notify the application to connect to the upstream router. To determine the connection to the upstream router, the SDK calls tuya_adapter_wifi_station_get_status to check for wireless connection status. If the return value is WSS_GOT_IP, it means the gateway has connected to the upstream router.

Before switching to the station mode, the SDK will check for the wireless connection status. If the wireless network adapter has the default IP address configured, the status WSS_GOT_IP will be returned even if this is not the IP address assigned by the router. In this case, the returned WSS_GOT_IP is false positive, and a pairing timeout will occur.

The SDK regularly calls tuya_adapter_wifi_station_get_status to check for wireless connection status. This function should return a response within one second.

Appendix

main.c

#include <unistd.h>

#include "uni_log.h"
#include "base_os_adapter.h"
#include "tuya_iot_base_api.h"
#include "tuya_iot_com_api.h"

#include "tuya_iot_sdk_api.h"
#include "tuya_iot_sdk_defs.h"

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
#include "tuya_os_adapt_bt.h"
#endif

#define PID       "fljmamufiym5fktz"                  // Replace it with your own PID.
#define UUID      "tuya461dbc63aeeb991f"              // Replace it with your own UUID.
#define AUTHKEY   "c8X4PR4wx1gMFaQlaZu5dfgVvVRwB8Ug"  // Replace it with your own AUTHKEY.

STATIC VOID __gw_reset_cb(GW_RESET_TYPE_E type)
{
    PR_DEBUG("gw reset callback, type: %d", type);

    if (GW_RESET_DATA_FACTORY != type) {
        exit(0);
    }

    return;
}

STATIC VOID __gw_upgrade_cb(CONST FW_UG_S *fw)
{
    PR_DEBUG("gw upgrade callback");

    if (fw == NULL) {
        PR_ERR("invalid param");
        return;
    }

    PR_DEBUG("        tp: %d", fw->tp);
    PR_DEBUG("    fw_url: %s", fw->fw_url);
    PR_DEBUG("    sw_ver: %s", fw->sw_ver);
    PR_DEBUG("   fw_hmac: %s", fw->fw_hmac);
    PR_DEBUG(" file_size: %u", fw->file_size);

    return;
}

STATIC VOID __gw_active_stat_cb(GW_STATUS_E status)
{
    PR_DEBUG("gw active stat callback, status: %d", status);

    return;
}

STATIC VOID __gw_reboot_cb(VOID)
{
    PR_DEBUG("gw reboot callback");

    exit(0);

    return;
}

STATIC VOID __nw_stat_cb(IN CONST SDK_NW_STAT_T stat)
{
    PR_DEBUG("network stat: %d", stat);

    return;
}

STATIC VOID __wired_stat_cb(IN CONST SDK_WIRED_NW_STAT_T stat)
{
    PR_DEBUG("wired stat: %d", stat);

    return;
}

int main(int argc, char **argv)
{
    OPERATE_RET rt = OPRT_OK;
    GW_PROD_INFO_S prod_info = {0};

    /* gw base callback */
    TY_GW_INFRA_CBS_S gw_cbs = {
        .gw_reset_cb       = __gw_reset_cb,
        .gw_upgrade_cb     = __gw_upgrade_cb,
        .gw_active_stat_cb = __gw_active_stat_cb,
        .gw_reboot_cb      = __gw_reboot_cb,
    };

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
    tuya_os_adapt_reg_bt_intf();
#endif

    /* initiate os-layer service*/
    tuya_os_intf_init();

    /* initiate iot-layer service */
    TUYA_CALL_ERR_RETURN(tuya_iot_init("./"));

    /* set the logging level to debug */
    SET_PR_DEBUG_LEVEL(TY_LOG_LEVEL_DEBUG);

    PR_DEBUG("SDK INFO: %s", tuya_iot_get_sdk_info());

    /* set uuid and authkey */
    prod_info.uuid     = UUID;
    prod_info.auth_key = AUTHKEY;
    TUYA_CALL_ERR_RETURN(tuya_iot_set_gw_prod_info(&prod_info));

    /* pre-initiate sdk service */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_pre_init(TRUE));  

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));

    /* initiate sdk service */
#if defined(GW_SUPPORT_WIRED_WIFI) && (GW_SUPPORT_WIRED_WIFI==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wired_wf_sdk_init(IOT_GW_NET_WIRED_WIFI, GWCM_OLD, WF_START_SMART_ONLY, PID, USER_SW_VER, NULL, 0));
#elif defined(WIFI_GW) && (WIFI_GW==1)
    TUYA_CALL_ERR_RETURN(tuya_iot_wf_sdk_init(GWCM_OLD, WF_START_SMART_ONLY, PID, USER_SW_VER, NULL, 0));
#else
    // The wired connection SDK that does not support pairing over a wireless connection.
    return OPRT_COM_ERROR;
#endif

    /* register net stat notification callback */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_reg_netstat_cb(__nw_stat_cb, __wired_stat_cb, NULL));

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    while (1) {
        sleep(10);
    }

    return 0;
}

hal_wifi.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>

#include "uni_log.h"
#include "tuya_os_adapt_wifi.h"

#define WLAN_DEV       "wlan0"
#define AP_DEFAULT_IP  "192.168.133.1"
#define MAX_SCAN_NUM   35

STATIC WF_WK_MD_E       g_cur_mode = WWM_UNKNOWN;
STATIC UCHAR_T          g_cur_channel = 0;
STATIC BOOL_T           g_sniffer_en_flag = FALSE;
STATIC pthread_t        g_sniffer_tid;
STATIC SNIFFER_CALLBACK g_sniffer_cb = NULL;

/**
 * Realtek RTL8197F wireless data frame.
 */
struct rtl_wifi_header {
    uint16_t pkt_len;
    uint16_t payload_len;
    uint8_t data[252];
} __attribute__((packed));

/**
 * IEEE 802.11 wireless protocol header.
 */
typedef struct wlan_80211_head {
    uint8_t frmae_ctl1;
    uint8_t frmae_ctl2;
    uint16_t duration;
    uint8_t addr1[6];
    uint8_t addr2[6];
    uint8_t addr3[6];
    uint16_t seq_ctl;
    uint8_t addr4[6];
} WLAN_80211_HEAD_S;

/**
 * frame_ctl1 (uint8_t)
 *     Verion: mask 0x03
 *     Type: mask 0x0C
 *         00: Management Frame
 *         01: Control Frame
 *         10: Data Frame
 *         11: Reserved
 *     Subtype: mask 0xF0
 *         0000: Association Request
 *         0100: Probe Request
 *         1000: Beacon
 *         1010: Disassociation
 *
 * frame_ctl2 (uint8_t) - Frame Control Flags
 *     To DS: mask 0x01
 *     From DS: mask 0x02
 *         +-----------------------------------------------------------------+
 *         | To Ds | From Ds | Address 1 | Address 2 | Address 3 | Address 4 |
 *         +-----------------------------------------------------------------+
 *         | 0     | 0       | dest      | src       | bssid     | N/A       |
 *         +-----------------------------------------------------------------+
 *         | 0     | 1       | dest      | bssid     | src       | N/A       |
 *         +-----------------------------------------------------------------+
 *         | 1     | 0       | bssid     | src       | dest      | N/A       |
 *         +-----------------------------------------------------------------+
 *         | 1     | 1       | RA        | TA        | DA        | SA        |
 *         +-----------------------------------------------------------------+
 *     More Frag: mask 0x04
 *     Retry: mask 0x08
 *     Power Mgmt: mask 0x10
 *     More Data: mask 0x20
 *     Protected Frame: mask 0x40
 *     Order: mask 0x80
 *
 */
STATIC OPERATE_RET wlan_80211_packet_parse(BYTE_T *buf, UINT_T len)
{
    WLAN_80211_HEAD_S *wbuf = (WLAN_80211_HEAD_S *)buf;

    /* version must be 0x00 */
    if ((wbuf->frmae_ctl1 & 0x03) != 0x00) {
        return OPRT_COM_ERROR;
    }

    switch ((wbuf->frmae_ctl1) & 0x0c) {
        case 0x08: {
            /* Only Receive Data Frame */
            break;
        }
        default: {
            return OPRT_COM_ERROR;
        }
    }

    /* From DS = 1 */
    if ((wbuf->frmae_ctl2 & 0x3) == 0x2) {
        /**
         * Filter out data by destination address.
         *   1. For Broadcast, FF:FF:FF:FF:FF:FF is valid
         *   2. For Multicast, prefix with 01:00:5E is valid
         */
        if (!memcmp(wbuf->addr1, "\xff\xff\xff\xff\xff\xff", 6) || \
            !memcmp(wbuf->addr1, "\x01\x00\x5e", 3)) {
            return OPRT_OK;
        }
    }

    return OPRT_COM_ERROR;
}

STATIC VOID *sniffer_run(VOID *arg)
{
    INT_T sock = 0;
    struct ifreq ifr;
    size_t recv_len = 0;
    BYTE_T recv_buf[1024] = {0};

    PR_DEBUG("sniffer task started");

    sock = socket(PF_PACKET, SOCK_RAW, htons(0x03)); //ETH_P_ALL
    if (sock < 0) {
        PR_ERR("socket error");
        return (VOID *)0;
    }

    memset(&ifr, 0x00, SIZEOF(ifr));
    strncpy(ifr.ifr_name, WLAN_DEV , SIZEOF(ifr.ifr_name) - 1);
    setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (VOID *)&ifr, SIZEOF(ifr));

    while (g_sniffer_en_flag && (g_sniffer_cb != NULL)) {
        recv_len = recvfrom(sock, recv_buf, SIZEOF(recv_buf), 0, NULL, NULL);
        if (recv_len <= 0) {
            continue;
        }

        struct rtl_wifi_header *rh = (struct rtl_wifi_header *)recv_buf;
        if ((rh->pkt_len > 0) && (rh->data[0] != 0xff)) {
            if (wlan_80211_packet_parse(rh->data, rh->pkt_len) == 0) {
                g_sniffer_cb(rh->data, rh->pkt_len, 99);
            }
        }
    }

    g_sniffer_cb = NULL;

    close(sock);

    return (void *)0;
}

/**
 * Get the IP address of the wireless network.
 */
OPERATE_RET tuya_adapter_wifi_get_ip(CONST WF_IF_E wf, NW_IP_S *ip)
{
    CHAR_T buf[256] = {0};
    CHAR_T *pstart = NULL;
    FILE *fp = NULL;

    /**
     * System error.
     */
    fp = popen("ifconfig " WLAN_DEV, "r");
    if (fp == NULL) {
        return OPRT_COM_ERROR;
    }

    while (fgets(buf, SIZEOF(buf), fp) != NULL) {
        pstart = strstr(buf, "inet ");
        if (pstart != NULL) {
            break;
        }
    }

    pclose(fp);

    pstart = strstr(buf, "inet addr:");
    if (pstart == NULL) {    
        return OPRT_COM_ERROR;
    }
    sscanf(pstart + strlen("inet addr:"), "%s", ip->ip);

    if (wf == WF_STATION) {
        /**
         * Make sure the IP address you obtained is the one assigned by the router instead of the default IP address.
         */
        if (!strncmp(ip->ip, AP_DEFAULT_IP, SIZEOF(ip->ip))) {
            PR_TRACE("%s is default ip of AP", ip->ip);
            return OPRT_COM_ERROR;
        }
    }

    PR_TRACE("IP Addr: %s", ip->ip);

    return OPRT_OK;
}

/**
 * Get the MAC address of the wireless network.
 */
OPERATE_RET tuya_adapter_wifi_get_mac(CONST WF_IF_E wf, NW_MAC_S *mac)
{
    CHAR_T buf[256] = {0};
    CHAR_T *pstart = NULL;
    FILE *fp = NULL;

    /**
     * System error.
     */
    fp = popen("ifconfig " WLAN_DEV, "r");
    if (fp == NULL) {
        return OPRT_COM_ERROR;
    }

    while (fgets(buf, SIZEOF(buf), fp) != NULL) {
        pstart = strstr(buf, "HWaddr ");
        if (pstart != NULL) {
            INT_T x1, x2, x3, x4, x5, x6;
            sscanf(pstart + strlen("HWaddr "), "%x:%x:%x:%x:%x:%x", &x1, &x2, &x3, &x4, &x5, &x6);
            mac->mac[0] = x1 & 0xFF;
            mac->mac[1] = x2 & 0xFF;
            mac->mac[2] = x3 & 0xFF;
            mac->mac[3] = x4 & 0xFF;
            mac->mac[4] = x5 & 0xFF;
            mac->mac[5] = x6 & 0xFF;
            break;
        }
    }

    pclose(fp);

    PR_DEBUG("MAC Addr: %02X-%02X-%02X-%02X-%02X-%02X", mac->mac[0], mac->mac[1], mac->mac[2], mac->mac[3], mac->mac[4], mac->mac[5]);

    return OPRT_OK;
}

/**
 * Set the wireless operation mode.
 */
OPERATE_RET tuya_adapter_wifi_set_work_mode(CONST WF_WK_MD_E mode)
{
    /**
     * Configuring the sniffer mode for Realtek RTL8197F is not required. Save the value to a global variable.
     */
    g_cur_mode = mode;

    switch (mode) {
        case WWM_STATION:
        {
            system("iwpriv " WLAN_DEV " set_mib opmode=0x8");
            break;
        }
        case WWM_SOFTAP:
        {
            system("iwpriv " WLAN_DEV " set_mib opmode=0x10");
            break;
        }
        default:
        {
            break;
        }
    }

    return OPRT_OK;
}

/**
 * Get the wireless operation mode.
 */
OPERATE_RET tuya_adapter_wifi_get_work_mode(WF_WK_MD_E *mode)
{
    CHAR_T buf[256] = {0};
    CHAR_T *pstart = NULL;
    CHAR_T tmp[16] = {0};
    FILE *fp = NULL;

    /**
     * Configuring the sniffer mode for Realtek RTL8197F is not required so you can take the value from the global variable.
     */
    if (g_cur_mode == WWM_SNIFFER) {
        *mode = WWM_SNIFFER;
        return OPRT_OK;
    }

    /**
     * System error.
     */
    fp = popen("iwconfig " WLAN_DEV, "r");
    if (fp == NULL) {
        return OPRT_COM_ERROR;
    }

    while (fgets(buf, SIZEOF(buf), fp) != NULL) {
        pstart = strstr(buf, "Mode:");
        if (pstart != NULL) {
            sscanf(pstart + strlen("Mode:"), "%s ", tmp);
            break;
        }
    }
    pclose(fp);

    if (!strncasecmp(tmp, "Managed", strlen("Managed"))) {
        *mode = WWM_STATION;
    } else if (!strncasecmp(tmp, "Master", strlen("Master"))) {
        *mode = WWM_SOFTAP;
    } else {
        *mode = WWM_UNKNOWN;
    }

    return OPRT_OK;
}

/**
 * Connect the station to the router.
 */
OPERATE_RET tuya_adapter_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd)
{
    CHAR_T cmd_buf[128] = {0};

    /**
     * Turn off udhcpd service.
     */
    system("killall udhcpd");

    system("ifconfig " WLAN_DEV " down");

    system("iwpriv " WLAN_DEV " set_mib opmode=0x8");
    system("iwpriv " WLAN_DEV " set_mib band=11");
    system("iwpriv " WLAN_DEV " set_mib deny_legacy=0");
    system("iwpriv " WLAN_DEV " set_mib use40M=1");
    system("iwpriv " WLAN_DEV " set_mib 802_1x=0");
    snprintf(cmd_buf, SIZEOF(cmd_buf), "iwpriv %s set_mib ssid=\"%s\"", WLAN_DEV, ssid);
    system(cmd_buf);

    if (!passwd || (strlen(passwd) == 0)) {
        system("iwpriv " WLAN_DEV " set_mib authtype=0");
        system("iwpriv " WLAN_DEV " set_mib encmode=0");
    } else {
        system("iwpriv " WLAN_DEV " set_mib authtype=2");
        system("iwpriv " WLAN_DEV " set_mib psk_enable=3");
        system("iwpriv " WLAN_DEV " set_mib encmode=2");
        system("iwpriv " WLAN_DEV " set_mib wpa2_cipher=10");
        system("iwpriv " WLAN_DEV " set_mib wpa_cipher=10");
        snprintf(cmd_buf, SIZEOF(cmd_buf), "iwpriv %s set_mib passphrase=\"%s\"", WLAN_DEV, passwd);
        system(cmd_buf);
    }

    system("ifconfig " WLAN_DEV " up");

    system("ps | grep 'udhcpc -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");    
    system("ps | grep 'udhcpc -b -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");
    system("busybox udhcpc -b -i wlan0 -A 3 -T 3 -s /tmp/tuya/8197wlan0_udhcpc.script&");

    return OPRT_OK;
}

/**
 * Disconnect the station from the network.
 */
OPERATE_RET tuya_adapter_wifi_station_disconnect(VOID_T)
{
    system("ifconfig " WLAN_DEV " 0.0.0.0");
    system("ps | grep 'udhcpc -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");    
    system("ps | grep 'udhcpc -b -i wlan0' | grep -v grep | awk '{print $1}' | xargs kill -9");

    return OPRT_OK;
}

/**
 * Get the connection status of the station.
 */
OPERATE_RET tuya_adapter_wifi_station_get_status(WF_STATION_STAT_E *stat)
{
    NW_IP_S ip = {0};

    if (stat == NULL) {
        PR_ERR("invalid param");
        return OPRT_INVALID_PARM;
    }

    if (OPRT_OK == tuya_adapter_wifi_get_ip(WF_STATION, &ip)) {
        *stat = WSS_GOT_IP;
    } else {
        *stat = WSS_CONN_FAIL;
    }

    return OPRT_OK;
}

/**
 * Scan the area for available APs. The discovered APs will be listed and sent to the SDK. The SDK will only poll the listed channels.
 * The success rate. If `num` is 0, the SDK will poll all the channels.
 */
OPERATE_RET tuya_adapter_wifi_all_ap_scan(AP_IF_S **aps, UINT_T *num)
{
    INT_T idx = -1;
    FILE *fp = NULL;
    CHAR_T buf[128] = {0};
    CHAR_T *pstart = NULL;
    STATIC AP_IF_S s_aps[MAX_SCAN_NUM];

    memset(s_aps, 0, SIZEOF(s_aps));
    *aps = s_aps;
    *num = 0;

    system("iwpriv wlan0 at_ss && sleep 2");
    fp = fopen("/proc/wlan0/SS_Result", "r");
    if (fp == NULL) {
        return OPRT_OK;
    }

    while (1) {
        if (fgets(buf, SIZEOF(buf), fp) == NULL) {
            break;
        }
        pstart = strstr(buf, "HwAddr");
        if (pstart == NULL) {
            continue;
        }

        idx++;

        /* BSSID */
        INT_T x1, x2, x3, x4, x5, x6;
        sscanf(pstart + strlen("HwAddr: "), "%02x%02x%02x%02x%02x%02x", &x1, &x2, &x3, &x4, &x5, &x6);
        s_aps[idx].bssid[0] = x1 & 0xFF;
        s_aps[idx].bssid[1] = x2 & 0xFF;
        s_aps[idx].bssid[2] = x3 & 0xFF;
        s_aps[idx].bssid[3] = x4 & 0xFF;
        s_aps[idx].bssid[4] = x5 & 0xFF;
        s_aps[idx].bssid[5] = x6 & 0xFF;

        /* Channel */
        memset(buf, 0, SIZEOF(buf));
        if (fgets(buf, SIZEOF(buf), fp) == NULL) {
            break;
        }
        pstart = strstr(buf, "Channel");
        if (pstart == NULL) {
            break;
        }
        sscanf(pstart + strlen("Channel: "), "%d", &(s_aps[idx].channel));

        /* SSID */
        memset(buf, 0, SIZEOF(buf));
        if (fgets(buf, SIZEOF(buf), fp) == NULL) {
            break;
        }
        pstart = strstr(buf, "SSID");
        if (pstart == NULL) {
            break;
        }
        sscanf(pstart + strlen("SSID: "), "%s", s_aps[idx].ssid);
        s_aps[idx].s_len = strlen(s_aps[idx].ssid);
    }

    *num = idx + 1;

    return OPRT_OK;
}

/*  Free the heap memory allocated for scanning for APs. */
OPERATE_RET tuya_adapter_wifi_release_ap(AP_IF_S *ap)
{
    return OPRT_OK;
}

/* Set the channels to sniff, which is used to switch between channels for capturing packets */
OPERATE_RET tuya_adapter_wifi_set_cur_channel(CONST UCHAR_T channel)
{
    /**
     * For Realtek RTL8197F, setting a fixed channel to receive packets is not required. Save the value to a global variable.
     */
    g_cur_channel = channel;

    return OPRT_OK;
}

/* Get the channel, used to lock a specific channel to receive data */
OPERATE_RET tuya_adapter_wifi_get_cur_channel(UCHAR_T *chan)
{
    /**
     * For Realtek RTL8197F, setting a fixed channel is not required so you can take the value from the global variable.
     */
    *chan = g_cur_channel;

    return OPRT_OK;
}

/**
 * To set the sniffer mode, implement the following logic:
 *   a) When the sniffer mode is turned on, you need to create a thread to capture packets and send the captured packets to `SNIFFER_CALLBACK` for processing.
 *   b) When the sniffer mode is turned off, you need to destroy the thread.
 */
OPERATE_RET tuya_adapter_wifi_sniffer_set(CONST BOOL_T en, CONST SNIFFER_CALLBACK cb)
{
    PR_DEBUG("WiFi Set Sniffer, en: %d", en);

    if (en == g_sniffer_en_flag) {
        PR_WARN("sniffer status not changed, en: %d, en_flag: %d", en, g_sniffer_en_flag);
        return OPRT_OK;
    }

    g_sniffer_en_flag = en;

    if (en) {
        PR_DEBUG("enable sniffer");
        g_sniffer_cb = cb;
        tuya_adapter_wifi_set_work_mode(WWM_SNIFFER);
        if (0 != pthread_create(&g_sniffer_tid, NULL, sniffer_run, NULL)) {
            PR_ERR("pthread_create error");
            return OPRT_COM_ERROR;
        }
    } else {
        PR_DEBUG("disable sniffer");
        pthread_join(g_sniffer_tid, NULL);
        tuya_hal_wifi_set_work_mode(WWM_SOFTAP);
    }

    return OPRT_OK;
}

/**
 * Get the signal strength of the wireless network.
 */
OPERATE_RET tuya_adapter_wifi_station_get_conn_ap_rssi(SCHAR_T *rssi)
{
    *rssi = 99;

    return OPRT_OK;
}

/* Function for pairing in AP mode */
OPERATE_RET tuya_adapter_wifi_ap_start(CONST WF_AP_CFG_IF_S *cfg)
{
    return OPRT_OK;
}

/* Function for pairing in AP mode */
OPERATE_RET tuya_adapter_wifi_ap_stop(VOID_T)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_assign_ap_scan(CONST SCHAR_T *ssid, AP_IF_S **ap)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_mac(CONST WF_IF_E wf, CONST NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_get_bssid(UCHAR_T *mac)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_country_code(CONST COUNTRY_CODE_E code)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_send_mgnt(CONST UCHAR_T *buf, CONST UINT_T len)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_register_recv_mgnt_callback(CONST BOOL_T enable, CONST WIFI_REV_MGNT_CB recv_cb)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_lp_mode(CONST BOOL_T en, CONST UCHAR_T dtim)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_get_connected_ap_info_v2(FAST_WF_CONNECTED_AP_INFO_V2_S **fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_fast_station_connect_v2(CONST FAST_WF_CONNECTED_AP_INFO_V2_S *fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* Implementation is not needed. */
BOOL_T tuya_adapter_wifi_rf_calibrated(VOID_T)
{
    return FALSE;
}
​```urn OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_assign_ap_scan(CONST SCHAR_T *ssid, AP_IF_S **ap)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_mac(CONST WF_IF_E wf, CONST NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_get_bssid(UCHAR_T *mac)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_country_code(CONST COUNTRY_CODE_E code)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_send_mgnt(CONST UCHAR_T *buf, CONST UINT_T len)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_register_recv_mgnt_callback(CONST BOOL_T enable, CONST WIFI_REV_MGNT_CB recv_cb)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_set_lp_mode(CONST BOOL_T en, CONST UCHAR_T dtim)
{
    return OPRT_OK;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_get_connected_ap_info_v2(FAST_WF_CONNECTED_AP_INFO_V2_S **fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* Implementation is not needed. */
OPERATE_RET tuya_adapter_wifi_fast_station_connect_v2(CONST FAST_WF_CONNECTED_AP_INFO_V2_S *fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* Implementation is not needed. */
BOOL_T tuya_adapter_wifi_rf_calibrated(VOID_T)
{
    return FALSE;
}