Pair in AP Mode

Last Updated on : 2024-06-25 09:36:12download

This topic describes how pairing in AP mode works and how to implement this feature to connect a gateway to the Tuya Developer Platform.

Overview

Features

After the mobile phone connects to the access point (AP) emitted by the gateway, the mobile app sends the Wi-Fi credentials (SSID and password) and activation token to the gateway. The gateway uses the Wi-Fi credentials to connect to the Wi-Fi network, while using the activation token to bind with the Tuya Developer Platform.

The SDK comes with the implementation of the pairing logic. It defines a set of TuyaOS Kernel Layer (TKL) APIs that abstract hardware and system differences, so you only need to adapt the TKL APIs.

How it works

How pairing in AP mode works:

  1. After the gateway turns on AP mode, the mobile phone connects to this AP, enabling both the phone and gateway to be on the same LAN.
  2. The gateway regularly sends encrypted UDP broadcast packets that contain device information on the LAN.
  3. After users enter the Wi-Fi credentials on the mobile app, the mobile app sends the token for pairing as well as the Wi-Fi credentials to the gateway over a TLS connection.
  4. The gateway parses the received information, switches to station mode, connects to the Wi-Fi network, and completes activation.

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

SDKDeveloperMobile AppCloudSet the Wi-Fi eventcallback.Switch Wi-Fi to AP mode.Start AP mode.Request an activationtoken.Return an activation token.Connect to the device's AP.UDP advertising <port: 6668>Establish a TCP connection <port: 6667>Send pairing information.Turn off AP.Switch Wi-Fi to stationmode.Connect to the router'sWi-Fi network.Push Wi-Fi connectionevent.Activation.Return the device's schema.SDKDeveloperMobile AppCloud

Development guide

How to use

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

  • tkl_wifi_init

    OPERATE_RET tkl_wifi_init(WIFI_EVENT_CB cb);
    

    Call this API when initializing the SDK. The parameter is the callback pointer for notifying changes in wireless connection status. Monitor the wireless connection status, and pass it to the SDK using this pointer when any changes occur.

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

  • tkl_wifi_start_ap

    OPERATE_RET tkl_wifi_start_ap(CONST WF_AP_CFG_IF_S *cfg);
    

    During SDK initialization, if the device is not paired, this API will be invoked to turn on the device’s AP mode. This enables the mobile phone to connect to the AP and transmit pairing information.

  • tkl_wifi_stop_ap

    OPERATE_RET tkl_wifi_stop_ap(VOID_T);
    

    When pairing is completed, this API will be invoked to turn off the device’s AP mode.

  • tkl_wifi_set_work_mode

    OPERATE_RET tkl_wifi_set_work_mode(CONST WF_WK_MD_E mode);
    

    The SDK uses this API to switch between wireless operation modes. For example, switch to AP mode before tkl_wifi_start_ap and to station mode before tkl_wifi_station_connect.

  • tkl_wifi_get_work_mode

    OPERATE_RET tkl_wifi_get_work_mode(WF_WK_MD_E *mode);
    

    The SDK uses this API to get the current wireless operation mode. The return value should match the mode set by tkl_wifi_set_work_mode.

  • tkl_wifi_station_connect

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

    After receiving the pairing information from the mobile app, the SDK uses this API to connect to the router’s Wi-Fi network.

  • tkl_wifi_station_disconnect

    OPERATE_RET tkl_wifi_station_disconnect(VOID_T);
    

    The SDK uses this API to disconnect from the router’s Wi-Fi network.

  • tkl_wifi_station_get_status

    OPERATE_RET tkl_wifi_station_get_status(WF_STATION_STAT_E *stat);
    

    The SDK uses this API 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’s Wi-Fi network 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 uses this API to get the IP address of the current wireless network interface.

Example

This example shows how to use wpa_supplicant, hostapd, udhcpc, and udhcpd 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 API for pairing in AP mode and set 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)
        /* < support wireless && wired > */
        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)
        /* < only support wireless > */
        TUYA_CALL_ERR_RETURN(tuya_iot_wf_sdk_init(GWCM_OLD, WF_START_AP_ONLY, PID, USER_SW_VER, NULL, 0));
    #else
        /* < other > */
        return OPRT_COM_ERROR;
    #endif
        // ...
    }
    
  2. Implement tkl_wifi_init, create a thread for monitoring wireless connection status, 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 this callback pointer.

    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);
        }
    }
    
    /**
     * @brief Set Wi-Fi station work status change callback
     *
     * @param[in]      cb: the Wi-Fi station work status change callback
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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.

    /**
     * @brief Set Wi-Fi work mode
     *
     * @param[in]       mode: Wi-Fi work mode
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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. The return value should match the mode set by tkl_wifi_set_work_mode.

    /**
     * @brief Get Wi-Fi work mode
     *
     * @param[out]      mode: Wi-Fi work mode
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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 and start the AP mode based on parameters. Be sure to use the IP address specified in the cfg parameter. 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.

    /**
     * @brief Start a soft AP
     *
     * @param[in]       cfg: the soft AP config
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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 device’s AP mode. The SDK calls this API to turn off the AP mode when it receives the pairing information from the mobile app.

    /**
     * @brief Stop a soft AP
     *
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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’s Wi-Fi network. udhcpc acts as a DHCP client to get the IP address from the router.

    /**
     * @brief Connect Wi-Fi with SSID and password
     *
     * @param[in]       SSID
     * @param[in]       password
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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. The return value might be the IP address of the AP itself. It is recommended to filter the IP address. WSS_GOT_IP is returned only when the IP address assigned by the router is obtained. Otherwise, the device will be offline and the pairing process will time out.

    /**
     * @brief Get Wi-Fi station work status
     *
     * @param[out]      stat: the Wi-Fi station work status
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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.

    /**
     * @brief Get Wi-Fi IP info. When Wi-Fi works in
     *        AP + station mode, Wi-Fi has two IP addresses.
     *
     * @param[in]       wf: Wi-Fi function type
     * @param[out]      ip: the IP address info
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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.

    /**
     * @brief Get Wi-Fi MAC info. When Wi-Fi works in
     *        AP + station mode, Wi-Fi has two MAC addresses.
     *
     * @param[in]       wf: Wi-Fi function type
     * @param[out]      mac: the MAC info
     * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
     */
    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. Implement tkl_wifi_scan_ap to scan for nearby APs.

  12. Implement tkl_wifi_set_country_code to set the country code.

  13. Populate the unused interfaces with empty implementation.

    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 APIs. It is recommended to process time-consuming tasks asynchronously.
  • The app selects the encryption method based on the IP address segment. When turning on the device’s AP mode, set the wireless interface to the IP address specified in the SDK configuration. The current version uses 192.168.176.1 as the IP address.