Pair Over Wireless Connection

Last Updated on : 2023-09-06 10:41:15download

Overview

Concept

This topic describes pairing over a wireless connection to enable a gateway to connect to the Tuya ecosystem. Currently, only access point (AP) mode is available. Wi-Fi Easy Connect (EZ) mode is deprecated.

The SDK comes with the implementation of the business logic of the pairing process. It defines a set of TuyaOS Kernel Layer (TKL) that shields differences in hardware and system. The underlying hardware is controlled by the TKL interfaces that need to be implemented by you. This topic guides you through developing the TKL interfaces.

Description

Pairing over a wireless connection enables a gateway device to connect to an Ethernet-connected router and then to the Tuya IoT Cloud. In this process, users are asked to enter the name and password of the router.

Principle

To pair a gateway in AP mode, users turn on the Wi-Fi AP on the gateway and connect the mobile phone to this AP. This way, the gateway and the mobile phone are on the same LAN. The gateway regularly broadcasts its encrypted device information using user datagram protocol (UDP) over LAN.

After users enter the SSID and password of the router on the mobile app, the mobile app sends the token for pairing as well as the SSID and password of the router to the gateway over a TLS connection. After the gateway parses out the SSID and password of the router, it switches to station mode and connects to the router. With a successful connection, the gateway performs the activation until the pairing is finished.

The following figure shows the process of pairing in AP mode.

Pair Over Wireless Connection

Development guide

Interfaces

During the pairing process, the SDK should get and switch between wireless operation modes, turn on/off the Wi-Fi AP, connect to the router, get the wireless connection status, and get the IP address of the wireless network interface. You need to adapt the following TKL interfaces.

  • tkl_wifi_init

    OPERATE_RET tkl_wifi_init(WIFI_EVENT_CB cb);
    

    The SDK calls this interface during initialization. The parameter of this interface is the pointer to the notification callback invoked when the wireless connection status changes. The application should monitor the wireless connection status in real time. Changes in wireless connection status can be passed to the SDK using the pointer to the notification callback.

    For example, when the gateway is connected to the router and assigned an IP address, cb(WFE_CONNECTED, NULL) is invoked. When the gateway failed to connect to the router, cb(WFE_CONNECT_FAILED, NULL) is invoked. When the gateway disconnects from the router, cb(WFE_DISCONNECTED, NULL) is invoked.

  • tkl_wifi_start_ap

    OPERATE_RET tkl_wifi_start_ap(CONST WF_AP_CFG_IF_S *cfg);
    

    If the gateway is unpaired on initialization, the SDK calls this interface to turn on the Wi-Fi AP on the gateway. This way, the mobile phone can connect to this AP and send the token for pairing to the gateway over LAN. Your application should be able to start the AP in this interface.

  • tkl_wifi_stop_ap

    OPERATE_RET tkl_wifi_stop_ap(VOID_T);
    

    The SDK calls this interface to turn off the AP when pairing is finished. Your application should be able to turn off the AP in this interface.

  • tkl_wifi_set_work_mode

    OPERATE_RET tkl_wifi_set_work_mode(CONST WF_WK_MD_E mode);
    

    Typically, the SDK calls this interface before tkl_wifi_stop_ap to switch to AP mode or before tkl_wifi_station_connect to switch to station mode. Your application should be able to set the wireless operation mode based on the parameter mode.

  • tkl_wifi_get_work_mode

    OPERATE_RET tkl_wifi_get_work_mode(WF_WK_MD_E *mode);
    

    The SDK calls this interface to get the wireless operation mode. The returned operation mode must be consistent with that set by tkl_wifi_set_work_mode. Otherwise, this might affect the pairing process.

  • tkl_wifi_station_connect

    OPERATE_RET tkl_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd);
    

    The SDK calls this interface when it receives the token for pairing from the mobile app. Your application should be able to connect to the router in this interface.

  • tkl_wifi_station_disconnect

    OPERATE_RET tkl_wifi_station_disconnect(VOID_T);
    

    The SDK calls this interface to disconnect from the router. Your application should be able to disconnect from the router in this interface.

  • tkl_wifi_station_get_status

    OPERATE_RET tkl_wifi_station_get_status(WF_STATION_STAT_E *stat);
    

    The SDK calls this interface to get the current wireless connection status. The SDK triggers the pairing process only when it receives the status WSS_GOT_IP. When the gateway is connected to the router and assigned an IP address, WSS_GOT_IP should be returned.

  • tkl_wifi_get_ip

    OPERATE_RET tkl_wifi_get_ip(CONST WF_IF_E wf, NW_IP_S *ip);
    

    The SDK calls this interface to get the IP address of the current wireless network interface. In this interface, your application should get the IP address of the AP mode or station mode based on the parameter wf.

Example

The example uses wpa_supplicant, hostapd, udhcpc, andudhcpd to implement pairing in AP mode. The example code is provided to inspire your own implementation that fits your wireless chip.

  1. Initialize the SDK using the interface for pairing over a wireless connection and sets the wireless operation mode to WF_START_AP_ONLY.

    // ...
    
    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_AP_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_AP_ONLY, PID, USER_SW_VER, NULL, 0));
    #else
        // The wired SDK does not support pairing over wireless connection.
        return OPRT_COM_ERROR;
    #endif
        // ...
    }
    
  2. Implement tkl_wifi_init to monitor the status of the wireless network interface and save the pointer to the notification callback to the global variable. Changes in wireless status (connection and disconnection) can be passed to the SDK using the pointer to the notification callback.

    STATIC WIFI_EVENT_CB  __wifi_event_cb = NULL;
    
    STATIC BOOL_T __wifi_status(VOID)
    {
        FILE *fp = NULL;
        CHAR_T buf[512] = {0};
        WF_STATION_STAT_E stat = 0;
    
        fp = popen("wpa_cli -i " WLAN_DEV " status", "r");
        if (fp == NULL) {
            return FALSE;
        }
    
        while (fgets(buf, SIZEOF(buf), fp)) {
            if (!strstr(buf, "wpa_state"))
                continue;
    
            char *k = strtok(buf, "=");
            char *v = strtok(NULL, "=");
            if (v && !strncmp(v, "COMPLETED", strlen("COMPLETED"))) {
                tkl_wifi_station_get_status(&stat);
                if (stat == WSS_GOT_IP) {
                    return TRUE;
                }
            }
        }
    
        pclose(fp);
    
        return FALSE;
    }
    
    STATIC VOID *__wifi_status_thread(VOID *arg)
    {
        BOOL_T cur_status = FALSE, lst_status = FALSE;
    
        while (1) {
            if (g_wifi_mode != WWM_STATION) {
                tkl_system_sleep(500);
                continue;
            }
    
            cur_status = __wifi_status();
    
            if (cur_status != lst_status) {
                PR_DEBUG("wifi connection status changed, %d -> %d", lst_status, cur_status);
                if (__wifi_event_cb) {
                    __wifi_event_cb(cur_status ? WFE_CONNECTED : WFE_DISCONNECTED, NULL);
                }
                lst_status = cur_status;
            }
            tkl_system_sleep(1000);
        }
    }
    
    OPERATE_RET tkl_wifi_init(WIFI_EVENT_CB cb)
    {
        pthread_t tid;
    
        __wifi_event_cb = cb;
    
        pthread_create(&tid, NULL, __wifi_status_thread, NULL);
    
        return OPRT_OK;
    }
    
  3. Implement tkl_wifi_set_work_mode to set the wireless operation mode to AP or station based on specific parameters.

    OPERATE_RET tkl_wifi_set_work_mode(CONST WF_WK_MD_E mode)
    {
        PR_DEBUG("WiFi set mode: %d", mode);
    
        g_wifi_mode = mode;
    
        switch (mode) {
            case WWM_STATION:
                exec_command("iwconfig " WLAN_DEV " mode managed", NULL, 0);
                break;
            case WWM_SOFTAP:
                // exec_command("iwconfig " WLAN_DEV " mode master", NULL, 0);
                break;
            default:
                break;
        }
    
        return OPRT_OK;
    }
    
  4. Implement tkl_wifi_get_work_mode to get the current wireless operation mode that should be consistent with that set by tkl_wifi_set_work_mode. To avoid inconsistency, directly read the value from the global variable.

    OPERATE_RET tkl_wifi_get_work_mode(WF_WK_MD_E *mode)
    {
        *mode = g_wifi_mode;
    
        PR_DEBUG("WiFi got mode: %d", *mode);
    
        return OPRT_OK;
    }
    
  5. Implement tkl_wifi_start_ap to configure the AP with the IP address specified in the parameter cfg and turn on the AP. udhcpd acts as a DHCP server to assign an IP address, IP address pool, and the value of the parameter cfg to the mobile phone.

    OPERATE_RET tkl_wifi_start_ap(CONST WF_AP_CFG_IF_S *cfg)
    {
        OPERATE_RET op_ret = OPRT_OK;
        INT_T len = 0;
        CHAR_T buf[512] = {0};
        CHAR_T cmd[128] = {0};
        CHAR_T *ap_conf_fmt =
    "interface=%s\n"
    "ssid=%s\n"
    "country_code=CN\n"
    "channel=%d\n"
    "beacon_int=100\n"
    "max_num_sta=%d\n"
    "auth_algs=3\n"
    "wpa=%d\n"
    "wpa_key_mgmt=WPA-PSK\n"
    "wpa_pairwise=TKIP CCMP\n"
    "rsn_pairwise=CCMP\n";
    
        CHAR_T *udhcpd_conf_fmt =
    "interface %s\n"
    "start %s.100\n"
    "end %s.200\n"
    "opt subnet %s\n"
    "opt lease 28800\n"
    "opt router %s\n"
    "opt dns %s\n"
    "opt domain SmartLift\n";
    
        INT_T seg1 = 0, seg2 = 0, seg3 = 0, seg4 = 0;
        CHAR_T ip_prefix[12] = {0};
    
        tkl_wifi_station_disconnect();
    
        PR_DEBUG("start ap, ssid: %s, ip: %s", cfg->ssid, cfg->ip.ip);
    
        memcpy(&g_ap_ip, &(cfg->ip), SIZEOF(NW_IP_S));
    
        sscanf(cfg->ip.ip, "%d.%d.%d.%d", &seg1, &seg2, &seg3, &seg4);
        snprintf(ip_prefix, SIZEOF(ip_prefix), "%d.%d.%d", seg1, seg2, seg3);
    
        if (cfg->p_len > 0) {
            len = snprintf(buf, SIZEOF(buf), ap_conf_fmt, WLAN_DEV, cfg->ssid, cfg->chan, cfg->max_conn, 2);
            len += snprintf(buf + len, SIZEOF(buf) - len, "wpa_passphrase=%s\n", cfg->passwd);
        } else {
            len = snprintf(buf, SIZEOF(buf), ap_conf_fmt, WLAN_DEV, cfg->ssid, cfg->chan, cfg->max_conn, 0);
        }
    
        op_ret = save_conf(HOSTAPD_CONF, buf, len);
        if (op_ret != OPRT_OK) {
            PR_ERR("fail to write %s", HOSTAPD_CONF);
        }
    
        len = snprintf(buf, SIZEOF(buf), udhcpd_conf_fmt, WLAN_DEV, ip_prefix, ip_prefix, cfg->ip.mask, cfg->ip.gw, cfg->ip.gw);
        op_ret = save_conf(UDHCPD_CONF, buf, len);
        if (op_ret != OPRT_OK) {
            PR_ERR("fail to write %s", UDHCPD_CONF);
        }
    
        snprintf(cmd, SIZEOF(cmd), "ifconfig %s %s netmask %s", WLAN_DEV, cfg->ip.ip, cfg->ip.mask);
        exec_command(cmd, NULL, 0);
        exec_command("ifconfig " WLAN_DEV " up", NULL, 0);
        tkl_system_sleep(1000);
        exec_command("hostapd -B -P /run/hostapd.pid " HOSTAPD_CONF, NULL, 0);
        exec_command("killall udhcpd", NULL, 0);
        exec_command("udhcpd " UDHCPD_CONF, NULL, 0);
    
        return OPRT_OK;
    }
    
  6. Implement tkl_wifi_stop_ap to turn off the AP. The SDK calls this interface to turn off the AP when it receives the token for pairing from the mobile app.

    OPERATE_RET tkl_wifi_stop_ap(VOID_T)
    {
        exec_command("killall udhcpd", NULL, 0);
        exec_command("killall hostapd", NULL, 0);
        exec_command("ifconfig " WLAN_DEV " down", NULL, 0);
    
        return OPRT_OK;
    }
    
  7. Implement tkl_wifi_station_connect to connect to the router. udhcpc acts as a DHCP client to get the IP address from the router.

    OPERATE_RET tkl_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd)
    {
        OPERATE_RET op_ret = OPRT_OK;
        INT_T len = 0;
        CHAR_T buf[512] = {0};
        CHAR_T *wpa_conf_fmt =
    "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n"
    "update_config=1\n"
    "country=CN\n"
    "\n"
    "network={\n"
            "\tssid=\"%s\"\n"
            "\tpairwise=CCMP TKIP\n"
            "\tkey_mgmt=WPA-PSK\n"
            "\tgroup=CCMP TKIP\n"
            "\tpsk=\"%s\"\n"
    "}\n";
    
        if (!ssid || !passwd) {
            PR_WARN("ssid or passwd is null");
            return OPRT_INVALID_PARM;
        }
    
        tkl_wifi_stop_ap();
    
        PR_DEBUG("ssid: %s, passwd: %s", ssid, passwd);
    
        len = snprintf(buf, SIZEOF(buf), wpa_conf_fmt, ssid, passwd);
        op_ret = save_conf(WPA_SUPPLICANT_CONF, buf, len);
        if (op_ret != OPRT_OK) {
            PR_ERR("fail to write %s", UDHCPD_CONF);
        }
    
        exec_command("wpa_supplicant -B -Dnl80211 -i" WLAN_DEV " -c" WPA_SUPPLICANT_CONF, NULL, 0);
        exec_command("udhcpc -i " WLAN_DEV " -s /etc/udhcpc/default.script -p /run/udhcpc_wlan0.pid -b", NULL, 0);
    
        return OPRT_OK;
    }
    
  8. Implement tkl_wifi_station_get_status to get the wireless connection status. Note that the IP address of the AP might be returned, so you need to filter IP addresses. WSS_GOT_IP is returned only after the gateway is assigned an IP address.

    OPERATE_RET tkl_wifi_station_get_status(WF_STATION_STAT_E *stat)
    {
        OPERATE_RET op_ret = OPRT_OK;
        NW_IP_S ip = {0};
    
        *stat = WSS_IDLE;
    
        op_ret = tkl_wifi_get_ip(WF_STATION, &ip);
        if (op_ret != OPRT_OK) {
            return op_ret;
        }
    
        if ((strlen(ip.ip) > 0) && (strncmp(g_ap_ip.ip, ip.ip, strlen(ip.ip)) != 0)) {
            *stat = WSS_GOT_IP;
        }
    
        return OPRT_OK;
    }
    
  9. Implement tkl_wifi_get_ip to get the IP address of the wireless network interface.

    OPERATE_RET tkl_wifi_get_ip(CONST WF_IF_E wf, NW_IP_S *ip)
    {
        struct ifreq ifr;
        int sock = 0;
    
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock < 0) {
            PR_ERR("create socket failed");
            return OPRT_COM_ERROR;
        }
    
        strncpy(ifr.ifr_name, WLAN_DEV, strlen(WLAN_DEV) + 1);
    
        if (ioctl(sock, SIOCGIFADDR, &ifr) == 0)
            strncpy(ip->ip, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), sizeof(ip->ip));
    
        if (ioctl(sock, SIOCGIFBRDADDR, &ifr) == 0)
            strncpy(ip->gw, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_broadaddr)->sin_addr), sizeof(ip->gw));
    
        if (ioctl(sock, SIOCGIFNETMASK, &ifr) == 0)
            strncpy(ip->mask, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), sizeof(ip->mask));
    
        close(sock);
    
        PR_DEBUG("WiFi ip->ip: %s", ip->ip);
    
        return OPRT_OK;
    }
    
  10. Implement tkl_wifi_get_mac to get the MAC address of the wireless network interface.

    OPERATE_RET tkl_wifi_get_mac(CONST WF_IF_E wf, NW_MAC_S *mac)
    {
        int i;
        int fd = -1;
        struct ifreq ifr;
        struct sockaddr *addr;
    
        fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
                PR_ERR("socket failed");
                return OPRT_SOCK_ERR;
        }
    
        memset(&ifr, 0, SIZEOF(ifr));
        strncpy(ifr.ifr_name, WLAN_DEV, SIZEOF(ifr.ifr_name) - 1);
        addr = (struct sockaddr *)&ifr.ifr_hwaddr;
        addr->sa_family = 1;
    
        if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
            PR_ERR("ioctl failed");
            close(fd);
            return OPRT_COM_ERROR;
        }
    
        memcpy(mac->mac, addr->sa_data, MAC_ADDR_LEN);
        PR_DEBUG("WiFi mac->mac: %02X-%02X-%02X-%02X-%02X-%02X", mac->mac[0], mac->mac[1], mac->mac[2], \
                                                                    mac->mac[3],mac->mac[4],mac->mac[5]);
    
        close(fd);
    
        return OPRT_OK;
    }
    
  11. Populate the unused interfaces with nulls.

    OPERATE_RET tkl_wifi_scan_ap(CONST SCHAR_T *ssid, AP_IF_S **ap_ary, UINT_T *num)
    { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_release_ap(AP_IF_S *ap)
    { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_cur_channel(CONST UCHAR_T chan)
    { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_get_cur_channel(UCHAR_T *chan) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_sniffer(CONST BOOL_T en, CONST SNIFFER_CALLBACK cb) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_mac(CONST WF_IF_E wf, CONST NW_MAC_S *mac) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_get_connected_ap_info(FAST_WF_CONNECTED_AP_INFO_T **fast_ap_info) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_get_bssid(UCHAR_T *mac) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_country_code(CONST COUNTRY_CODE_E ccode) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_rf_calibrated(VOID_T) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_set_lp_mode(CONST BOOL_T enable, CONST UCHAR_T dtim);
    
    OPERATE_RET tkl_wifi_station_fast_connect(CONST FAST_WF_CONNECTED_AP_INFO_T *fast_ap_info) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_station_get_conn_ap_rssi(SCHAR_T *rssi) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_send_mgnt(CONST UCHAR_T *buf, CONST UINT_T len) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_register_recv_mgnt_callback(CONST BOOL_T enable, CONST WIFI_REV_MGNT_CB recv_cb) { return OPRT_OK; }
    
    OPERATE_RET tkl_wifi_ioctl(WF_IOCTL_CMD_E cmd,  VOID *args) { return OPRT_OK; }
    

Things to note

Do not block TKL interfaces. It is recommended to process time-consuming tasks asynchronously.