EZ 配网指南

更新时间:2022-11-24 09:20:25下载pdf

本文详细介绍使用综合 SDK 如何实现 EZ 配网功能,我们将会实现通过 EZ 配网方式来激活网关设备。

背景信息

EZ 配网也称 快速配网。其工作原理是,手机连接路由器,手机把令牌、路由器的 SSID 和密码等信息按一定方式进行编码,通过组播或广播方式传播。设备的无线接口处于监听模式,抓取无线报文并对其进行解析,提取出用于激活的令牌,以及路由器的 SSID 和密码,完成激活流程。

常用的编码方式有两种:

  • 数据用 UDP 数据包长度来编码,通过 UDP 广播方式发送。
  • 数据用 MAC 地址来编码,通过 UDP 组播方式发送。

相比于 AP 配网,EZ 配网更加便捷,但是路由器和手机存在一定的兼容性问题,以及无线干扰产生丢包问题,会导致 EZ 配网失败,所以 EZ 配网无法保证 100% 成功。

实现

SDK 已经实现 EZ 配网的底层逻辑,定义了一套无线适配接口,应用只需要完成接口适配即可。

EZ 配网的交互流程示意图:

EZ 配网指南

接下来,我们一步步来完成 EZ 配网的适配工作。

本示例仅供参考,要求开发者理解其实现逻辑。本示例代码适配的是 Realtek RTL8197F 平台,不同芯片或不同系统无线相关的操作可能都存在差异。为了方便演示,本示例代码不考虑性能,直接使用 Shell 命令操作。

  1. 在 SDK 初始化时指定无线配网方式为仅支持 EZ 配网。

    // ...
    
    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
        // 有线 SDK,不支持无线配网
        return OPRT_COM_ERROR;
    #endif
        // ...
    }
    
  2. 适配获取 MAC 地址接口,实现获取无线接口 MAC 地址。

    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. 适配设置无线模式接口,根据参数把无线设置到对应的模式。

    OPERATE_RET tuya_adapter_wifi_set_work_mode(CONST WF_WK_MD_E mode)
    {
        /**
         * Realtek RTL8197F SNIFFER 模式不需要设置,这里直接保存到全局变量
         */
        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. 适配扫描 AP 接口,搜索周围环境的 AP,把搜索到的 AP 信道、BSSID、SSID 等信息提供给 SDK,SDK 仅在这些 AP 列表中依次选择信道。当 AP 列表为空时,SDK 会轮询所有信道。

    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. 适配设置 SNIFFER 接口,开启或关闭 SNIFFER 功能。

    /**
     * a) en == TRUE: 开启 SNIFFER 功能,则创建线程抓取无线包,然后把无线包给 SDK 解析。为了提升效率,在把无线包给 SDK 解析
     *                之前,应用可以先做过滤,帧类型为 Data 的数据才给 SDK 处理,丢弃帧类型为 Management 和 Control 的数据。
     * b) en == FALSE: 关闭 SNIFFER 功能,则销毁线程,为了能保证触发连接路由器接口,可以强制把无线切换成 AP 模式。
     */
    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. 适配设置信道接口,实现设置信道功能。设置信道其实是为了让无线接口在该信道上接收无线包,可能有些无线驱动在监听模式抓包不需要固定信道,可以忽略不处理。

    OPERATE_RET tuya_adapter_wifi_set_cur_channel(CONST UCHAR_T channel)
    {
        /**
         * Realtek RTL8197F 接收无线包不需要固定信道,这里直接保存到全局变量
         */
        g_cur_channel = channel;
    
        return OPRT_OK;
    }
    
  7. 适配获取信道接口,实现获取信道功能。SDK 接收到前导码后,需要锁定信道接收剩余的数据,使用该接口获取当前的信道。可能有些无线驱动在监听模式抓包不需要固定信道,可以忽略不处理。

    OPERATE_RET tuya_adapter_wifi_get_cur_channel(UCHAR_T *chan)
    {
        /**
         * Realtek RTL8197F 不需要固定信道,这里直接从全局变量取值
         */
        *chan = g_cur_channel;
    
        return OPRT_OK;
    }
    
  8. 适配连接路由器接口,实现连接路由器功能。SDK 已经成功获取到路由器的 SSID 和密码,使用该接口连接路由器。

    OPERATE_RET tuya_adapter_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd)
    {
        CHAR_T cmd_buf[128] = {0};
    
        /**
         * 关闭 udhcpd 服务
         */
        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. 适配获取无线工作模式接口,实现获取无线接口当前的模式功能。

    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;
    
        /**
         * Realtek RTL8197F SNIFFER 模式不需要设置,直接从全局变量取值
         */
        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. 适配获取无线 IP 地址接口,实现获取无线接口的 IP 地址功能,用于局域网通讯。

    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) {
            /**
             * 避免获取到的是默认 IP 地址
             */
            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. 适配获取无线状态接口,实现获取 Station 的连接状态功能。连接路由器后,SDK 会定时获取无线的连接状态,用来判断网络的状态,以 WSS_GOT_IP 状态为判断标准。

    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;
    }
    

注意事项

在无线配网的适配中有些需要注意的地方,以下描述了无线配网适配的常见开发问题,开发应用时要重点关注。

接口实现

所有的适配接口都不允许阻塞,处理任务的时间也不允许过长,否则会出现配网超时等问题。耗时大于 10 秒的任务,建议使用异步方式来处理。

接口返回值

除了获取 IP 地址接口,其他无线配网相关的适配接口返回值为非 0,无线配网都会被终止,所以适配接口尽量保证返回值为 0。

无线状态

SDK 获取到 SSID 和密码后,先判断设备是否已经连接到上级路由器,未连接上级路由器则调用 tuya_adapter_wifi_station_connect 接口通知应用连接上级路由器。判断是否已经连接到上级路由器的机制是,调用 tuya_adapter_wifi_station_get_status 接口获取无线状态,状态为 WSS_GOT_IP 表示已连接到上级路由器。

在切换到 Station 模式之前,无线接口可能有默认 IP 地址,往往会导致在获取无线状态时无判断无线接口已经获取到了 IP 地址,所以返回 WSS_GOT_IP 状态,而实际这是无线接口的默认 IP 地址,而不是路由器分配了 IP 地址,会出现配网超时等现象。

另外,SDK 会定时调用 tuya_adapter_wifi_station_get_status 接口查询无线状态,要求该接口必须快速响应,尽量不超过 1 秒。

附录

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"                  // 替换成自己的产品 ID
#define UUID      "tuya461dbc63aeeb991f"              // 替换成自己的 UUID
#define AUTHKEY   "c8X4PR4wx1gMFaQlaZu5dfgVvVRwB8Ug"  // 替换成自己的 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
    // 有线 SDK,不支持无线配网
    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 无线数据帧
 */
struct rtl_wifi_header {
    uint16_t pkt_len;
    uint16_t payload_len;
    uint8_t data[252];
} __attribute__((packed));

/**
 * IEEE 802.11 无线协议帧头
 */
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;
}

/**
 * 获取无线 IP 地址接口
 */
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) {
        /**
         * 避免获取到的是默认的 IP 地址
         */
        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;
}

/**
 * 获取无线 MAC 地址接口
 */
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;
}

/**
 * 设置无线模式
 */
OPERATE_RET tuya_adapter_wifi_set_work_mode(CONST WF_WK_MD_E mode)
{
    /**
     * Realtek RTL8197F SNIFFER 模式不需要设置,这里直接保存到全局变量
     */
    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;
}

/**
 * 获取无线模式
 */
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;

    /**
     * Realtek RTL8197F SNIFFER 模式不需要设置,直接从全局变量取值
     */
    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;
}

/**
 * Station 连接路由器接口
 */
OPERATE_RET tuya_adapter_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd)
{
    CHAR_T cmd_buf[128] = {0};

    /**
     * 关闭 udhcpd 服务
     */
    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;
}

/**
 * 断开 Station 连接接口
 */
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;
}

/**
 * 获取 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;
}

/**
 * 扫描 AP 接口,该接口主要用于获取当前环境的信道,只在存在的信道中接收无线包,提高配网
 * 成功率,num 为 0 则轮询所有信道。
 */
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;
}

/* 释放扫描 AP 接口申请的堆内存 */
OPERATE_RET tuya_adapter_wifi_release_ap(AP_IF_S *ap)
{
    return OPRT_OK;
}

/* 设置信道接口,该接口用于切换信道,为了能在不同信道上接收无线包 */
OPERATE_RET tuya_adapter_wifi_set_cur_channel(CONST UCHAR_T channel)
{
    /**
     * Realtek RTL8197F 接收无线包不需要固定信道,这里直接保存到全局变量
     */
    g_cur_channel = channel;

    return OPRT_OK;
}

/* 获取信道接口,该接口用于锁定信道 */
OPERATE_RET tuya_adapter_wifi_get_cur_channel(UCHAR_T *chan)
{
    /**
     * Realtek RTL8197F 不需要固定信道,这里直接从全局变量取值
     */
    *chan = g_cur_channel;

    return OPRT_OK;
}

/**
 * 设置监听模式,需要完成以下逻辑:
 *   a) 开启监听模式时,创建线程抓无线包,把抓取到的无线包给 SNIFFER_CALLBACK 回调处理。
 *   b) 关闭监听模式时,销毁线程。
 */
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;
}

/**
 * 获取无线信号强度
 */
OPERATE_RET tuya_adapter_wifi_station_get_conn_ap_rssi(SCHAR_T *rssi)
{
    *rssi = 99;

    return OPRT_OK;
}

/* AP 配网接口 */
OPERATE_RET tuya_adapter_wifi_ap_start(CONST WF_AP_CFG_IF_S *cfg)
{
    return OPRT_OK;
}

/* AP 配网接口 */
OPERATE_RET tuya_adapter_wifi_ap_stop(VOID_T)
{
    return OPRT_OK;
}

/* 无线实现 */
OPERATE_RET tuya_adapter_wifi_assign_ap_scan(CONST SCHAR_T *ssid, AP_IF_S **ap)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_mac(CONST WF_IF_E wf, CONST NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_get_bssid(UCHAR_T *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_country_code(CONST COUNTRY_CODE_E code)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_send_mgnt(CONST UCHAR_T *buf, CONST UINT_T len)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_register_recv_mgnt_callback(CONST BOOL_T enable, CONST WIFI_REV_MGNT_CB recv_cb)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_lp_mode(CONST BOOL_T en, CONST UCHAR_T dtim)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_get_connected_ap_info_v2(FAST_WF_CONNECTED_AP_INFO_V2_S **fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_fast_station_connect_v2(CONST FAST_WF_CONNECTED_AP_INFO_V2_S *fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* 无需实现 */
BOOL_T tuya_adapter_wifi_rf_calibrated(VOID_T)
{
    return FALSE;
}
​```urn OPRT_OK;
}

/* 无线实现 */
OPERATE_RET tuya_adapter_wifi_assign_ap_scan(CONST SCHAR_T *ssid, AP_IF_S **ap)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_mac(CONST WF_IF_E wf, CONST NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_get_bssid(UCHAR_T *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_country_code(CONST COUNTRY_CODE_E code)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_send_mgnt(CONST UCHAR_T *buf, CONST UINT_T len)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_register_recv_mgnt_callback(CONST BOOL_T enable, CONST WIFI_REV_MGNT_CB recv_cb)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_set_lp_mode(CONST BOOL_T en, CONST UCHAR_T dtim)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_get_connected_ap_info_v2(FAST_WF_CONNECTED_AP_INFO_V2_S **fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wifi_fast_station_connect_v2(CONST FAST_WF_CONNECTED_AP_INFO_V2_S *fast_ap_info)
{
    return OPRT_COM_ERROR;
}

/* 无需实现 */
BOOL_T tuya_adapter_wifi_rf_calibrated(VOID_T)
{
    return FALSE;
}