配网方式-有线

更新时间:2023-09-06 10:41:15下载pdf

概述

基本概念

本文详细介绍 SDK 上有线配网功能,实现网关设备接入到涂鸦生态。

配网流程的业务逻辑已经在 SDK 内部实现,SDK 定义一套 TuyaOS Kernel Layer(简称 TKL)接口来屏蔽硬件和系统差异,TKL 接口由开发者实现,在 TKL 接口完成底层硬件的操作。本文档将提供 TKL 接口开发指导。

功能描述

有线配网是把网关类设备链接到涂鸦 IoT 云的行为。与 Wi-Fi 无线配网不同,有线配网无需输入路由器热点名称与密码。

适用场景

有线配网主要适用场景如下:

  • 网关插入网线且上级路由正常连接外网。
  • 网关处于未配网状态。

工作原理/实现方案

有线配网的工作原理是,网关设备和手机同时连接到路由器,使得设备和手机处于同一局域网下,设备把它的设备信息经过加密处理后定期在局域网上发送 UDP 广播数据包。手机 App 接收到 UDP 广播数据包后解密处理,成功获取到设备信息后会在 自动发现 页面显示待配网设备。

用户在手机 App 上单击 添加 激活待配网设备时,手机 App 与设备建立 TCP 连接,把配网授权信息发送给设备。设备收到配网授权信息后执行激活流程,直到配网完成。

开发指导

使用方法

有线配网不需要调用固定的 API 启动,但需要开发者适配以下 TKL(Tuya kernel layer)接口。

  • tkl_wired_set_status_cb

    OPERATE_RET tkl_wired_set_status_cb(TKL_WIRED_STATUS_CHANGE_CB cb);
    

    SDK 初始化会调用该接口,该接口的参数是网络接口状态变化的通知回调函数指针,需要应用实时监控网络接口的状态变化。当网络接口状态发生变化时,使用通知回调函数指针把其状态传递给 SDK。

    举例,当断开以太网时,执行 cb(TKL_WIRED_LINK_DOWN)。当连接以太网并分配到 IP 地址时,执行 cb(TKL_WIRED_LINK_UP)

  • tkl_wired_get_status

    OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status);
    

    当 SDK 需要获取当前网络接口的连接状态时,会调用该接口,SDK 关心的是网络接口是否有 IP 地址。如果网络接口是处于激活状态,并且有 IP 地址,则返回 TKL_WIRED_LINK_UP,否则返回 TKL_WIRED_LINK_DOWN

  • tkl_wired_get_ip

    OPERATE_RET tkl_wired_get_ip(NW_IP_S *ip);
    

    当 SDK 需要获取当前网络接口的 IP 地址时,会调用该接口,用于 Socket 通讯绑定地址。对于多网口设备,返回的是哪个网络接口 IP 地址,就使用哪个网络接口通讯。一般使用连接外网的网络接口,根据自己的实际情况正确返回 IP 地址。

使用示例

本文档演示了 Linux 通用平台有线配网 TKL 接口的实现,以供参考。

  1. 使用有线配网接口初始化 SDK。

    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)
        // 无线 SDK,不支持有线配网
        return OPRT_COM_ERROR;
    #else
        TUYA_CALL_ERR_RETURN(tuya_iot_sdk_init(PID, USER_SW_VER, NULL, 0));
    #endif
    
        // ...
    }
    
  2. 实现 tkl_wired_get_ip 接口,在接口中实现获取网络接口的 IP 地址。

    #define WIRED_NAME "eth0"
    
    OPERATE_RET tkl_wired_get_ip(OUT NW_IP_S *ip)
    {
        int sock = 0;
        char ipaddr[50] = {0};
    
        struct sockaddr_in *sin;
        struct ifreq ifr;
    
        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
             PR_ERR("socket create failed");
             return OPRT_COM_ERROR;
        }
    
        memset(&ifr, 0, sizeof(ifr));
        strncpy(ifr.ifr_name, WIRED_NAME, sizeof(ifr.ifr_name) - 1);
    
        if (ioctl(sock, SIOCGIFADDR, &ifr) < 0 ) {
             PR_ERR("ioctl failed");
             close(sock);
             return OPRT_COM_ERROR;
        }
    
        sin = (struct sockaddr_in *)&ifr.ifr_addr;
        strcpy(ip->ip,inet_ntoa(sin->sin_addr)); 
        close(sock);
    
        return OPRT_OK;
    }
    
  3. 实现 tkl_wired_get_status 接口,在接口中实现获取网络接口的连接状态。

    OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status)
    {
        int sock;
        struct sockaddr_in *sin;
        struct ifreq ifr;
        NW_IP_S ip;
    
        *status = TKL_WIRED_LINK_DOWN;
    
        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
             PR_ERR("socket failed");
             return OPRT_COM_ERROR;
        }
    
        memset(&ifr, 0, sizeof(ifr));
        strncpy(ifr.ifr_name, WIRED_NAME, sizeof(ifr.ifr_name) - 1);
    
        if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
            PR_ERR("ioctl failed");
            close(sock);
            return OPRT_COM_ERROR;
        }
    
        close(sock);
    
        if ((ifr.ifr_flags & IFF_UP) == 0) {
            return OPRT_OK;
        }
        
        if (tkl_wired_get_ip(&ip) != OPRT_OK) {
        	return OPRT_OK;
        }
        
        *status = TKL_WIRED_LINK_UP;
        
        return OPRT_OK;
    }
    
  4. 实现 tkl_wired_set_status_cb 接口,在接口中实现开启线程监控网络接口的状态,并把通知回调函数指针保存到全局变量。网络接口状态发生变化时,使用通知回调函数指针把状态传递给 SDK。

    STATIC TKL_WIRED_STATUS_CHANGE_CB g_link_status_change_cb;
    
    STATIC VOID *link_status_thread(VOID *arg)
    {
        INT_T old_status = -1;
        TKL_WIRED_STAT_E new_status;
    
        while (1) {
            tkl_wired_get_status(&new_status);
            if (old_status != new_status) {
                g_link_status_change_cb(new_status);
                old_status = new_status;
            }
            sleep(1);
        }
    }
    
    OPERATE_RET tkl_wired_set_status_cb(TKL_WIRED_STATUS_CHANGE_CB cb)
    {
        pthread_t tid;
    
        g_link_status_change_cb = cb;
    
        return pthread_create(&tid, NULL, link_status_thread, NULL);
    }
    
  5. 实现 tkl_wired_get_mac 接口,在接口中实现获取网络接口的 MAC 地址。

    OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac)
    {
        int i;
        int sock = -1;
        struct ifreq ifr;
        struct sockaddr *addr;
    
        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
             PR_ERR("socket failed");
             return OPRT_SOCK_ERR;
        }
    
        memset(&ifr, 0, sizeof(ifr));
        strncpy(ifr.ifr_name, WIRED_NAME, sizeof(ifr.ifr_name) - 1);
        addr = (struct sockaddr *)&ifr.ifr_hwaddr;
        addr->sa_family = 1;
    
        /* get mac addr */
        if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
            PR_ERR("ioctl failed");
            close(sock);
            return OPRT_COM_ERROR;
        }
    
        memcpy(mac->mac, addr->sa_data, MAC_ADDR_LEN);
    
        close(sock);
        return OPRT_OK;
    }
    
  6. 其他非必要接口填充空实现。

    OPERATE_RET tkl_wired_set_mac(CONST NW_MAC_S *mac)
    {
        return OPRT_OK;
    }
    

注意事项

TKL 接口不允许阻塞,耗时的任务建议做异步处理。