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.
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 pairing in AP mode works:
The following figure shows the process of pairing in AP mode.
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.
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.
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
// ...
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
Implement tkl_wifi_scan_ap
to scan for nearby APs.
Implement tkl_wifi_set_country_code
to set the country code.
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; }
192.168.176.1
as the IP address.Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback