更新时间:2024-06-25 06:16:56下载pdf
本文介绍 SDK 中的 热点配网 功能。首先,将阐述其工作原理,随后详细说明如何实施热点配网,以便实现将网关设备接入涂鸦开发者平台的目标。
热点配网是指通过网关设备开启 Wi-Fi 热点,手机连接至该热点后,通过 App 向网关设备发送路由器的 SSID、密码及激活 Token。网关设备接收这些信息后,使用相应的 SSID 和密码连接路由器,并利用激活 Token 绑定到涂鸦开发者平台。
SDK 已内置配网流程的业务逻辑,通过定义一套 TuyaOS Kernel Layer(简称 TKL)接口来屏蔽硬件与系统的差异,您只需适配 TKL 接口即可。
热点配网的工作原理如下:
热点配网的流程图如下所示:
热点配网过程中,SDK 须完成多项任务,例如切换无线工作模式、开启/关闭 AP、连接路由器、获取无线连接状态及无线接口 IP 地址等。因此,您需适配以下 TKL 接口。
tkl_wifi_init
OPERATE_RET tkl_wifi_init(WIFI_EVENT_CB cb);
初始化 SDK 时调用此接口。参数为无线连接状态变化的通知回调函数指针,需应用实时监控无线连接状态变化,并通过该指针将状态传递给 SDK。
举例,当 成功连接到路由器并分配到 IP 地址 时,执行 cb(WFE_CONNECTED, NULL)
。当连接路由器失败时,执行 cb(WFE_CONNECT_FAILED, NULL)
。当断开路由器连接时,执行 cb(WFE_DISCONNECTED, NULL)
。
tkl_wifi_start_ap
OPERATE_RET tkl_wifi_start_ap(CONST WF_AP_CFG_IF_S *cfg);
SDK 初始化时,若设备未配网,将调用此接口开启设备的 AP,以便手机连接并传输配网信息。
tkl_wifi_stop_ap
OPERATE_RET tkl_wifi_stop_ap(VOID_T);
配网结束时,SDK 调用此接口关闭设备的 AP。
tkl_wifi_set_work_mode
OPERATE_RET tkl_wifi_set_work_mode(CONST WF_WK_MD_E mode);
SDK 需切换无线工作模式时调用此接口,例如在 tkl_wifi_start_ap
前设置 AP 模式,在 tkl_wifi_station_connect
前设置 Station 模式。
tkl_wifi_get_work_mode
OPERATE_RET tkl_wifi_get_work_mode(WF_WK_MD_E *mode);
SDK 需获取无线工作模式时调用此接口。返回模式应与 tkl_wifi_set_work_mode
接口设置的模式一致。
tkl_wifi_station_connect
OPERATE_RET tkl_wifi_station_connect(CONST SCHAR_T *ssid, CONST SCHAR_T *passwd);
SDK 接收到手机 App 的配网信息后,调用此接口来连接路由器。
tkl_wifi_station_disconnect
OPERATE_RET tkl_wifi_station_disconnect(VOID_T);
SDK 需断开路由器连接时调用此接口。
tkl_wifi_station_get_status
OPERATE_RET tkl_wifi_station_get_status(WF_STATION_STAT_E *stat);
SDK 需获取当前无线连接状态时调用此接口。SDK 需要获取到 WSS_GOT_IP
状态才会实施配网流程,当设备连接到路由器并且分配到 IP 地址应返回 WSS_GOT_IP
。
tkl_wifi_get_ip
OPERATE_RET tkl_wifi_get_ip(CONST WF_IF_E wf, NW_IP_S *ip);
SDK 需获取当前无线接口的 IP 地址时调用此接口。
以下示例展示了如何使用 wpa_supplicant
、hostapd
、udhcpc
和 udhcpd
来实现热点配网功能。
代码仅提供实现思路,需根据实际无线芯片进行适配和优化。
使用热点配网接口初始化 SDK,并设置无线工作模式为 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
// ...
}
实现 tkl_wifi_init
接口,实现开启线程监控无线连接状态,并把通知回调函数指针保存到全局变量。无线连接状态发生变化时(主要关心连接和断开状态),通过函数指针把状态传递给 SDK。
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;
}
实现 tkl_wifi_set_work_mode
接口,设置无线工作模式,主要是 AP 模式和 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;
}
实现 tkl_wifi_get_work_mode
接口,获取当前无线工作模式,返回模式应与 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;
}
实现 tkl_wifi_start_ap
接口,根据参数配置 AP 和开启 AP 的功能,必须使用 cfg
参数中的 IP 地址。udhcpd
作为 DHCP 服务器,给手机分配 IP 地址,IP 地址池以及网关 IP 地址使用 cfg
参数的值。
/**
* @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;
}
实现 tkl_wifi_stop_ap
接口,关闭设备的 AP。SDK 收到手机 App 的配网信息后,调用该接口关闭设备的 AP。
/**
* @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;
}
实现 tkl_wifi_station_connect
接口,连接路由器。udhcpc
作为 DHCP 客户端从路由器获取 IP 地址。
/**
* @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;
}
实现 tkl_wifi_station_get_status
接口,获取无线连接状态。可能获取到的是 AP 本身的 IP 地址,所以建议对 IP 地址进行过滤。只有获取到路由器分配的 IP 地址才返回 WSS_GOT_IP
状态,否则设备处于未联网状态,进入激活流程会导致超时。
/**
* @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;
}
实现 tkl_wifi_get_ip
接口,获取无线接口的 IP 地址。
/**
* @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;
}
实现 tkl_wifi_get_mac
接口,获取无线接口的 MAC 地址。
/**
* @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;
}
实现 tkl_wifi_scan_ap
接口,扫描附近的 AP。
实现 tkl_wifi_set_country_code
接口,设置国家码。
其他非必要接口填充空实现。
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
。该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈