有线配网指南

更新时间:2022-11-24 09:20:23下载pdf

本文详细介绍使用综合 SDK 如何实现有线配网功能,我们将会实现通过有线配网方式来激活网关设备。

背景信息

有线配网的工作原理是,设备通过有线方式连接到路由器,手机也连接到路由器,让手机和设备处于同一局域网下,设备把它的 PID、IP 地址等信息加密后发送 UDP 广播,手机 App 接收并解密广播包数据后,显示待配网设备。

用户点击添加设备后,手机 App 使用 UDP 广播的 IP 地址与设备建立 TCP 长连接,把配网令牌发送给设备,设备接收到配网令牌,完成配网流程。

实现

SDK 已经实现有线配网的底层逻辑,定义了一套有线适配接口,应用只需要完成接口适配即可。

有线适配接口比较简单,SDK 只关心有线是否连接和网口的 IP 地址,所以我们主要适配这两个接口。

  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. 适配获取有线 IP 地址接口,实现获取有线接口的 IP 地址。主要用于局域网通讯,从该网口发送 UDP 广播以及把 IP 地址广播出去,开发者可根据实际情况适配。

    OPERATE_RET tuya_adapter_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, ETH_DEV, 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. 适配获取有线连接状态,实现检测网线插拔状态,并把状态返回给 SDK。SDK 会定时调用该接口来检测有线连接状态是否正常,所以该接口不能阻塞,任务处理尽量不超过 1 秒。

    BOOL_T tuya_adapter_wired_station_conn(VOID)
    {
        int sock;
        struct sockaddr_in *sin;
        struct ifreq ifr;
    
        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, ETH_DEV, sizeof(ifr.ifr_name) - 1);
    
        if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
            PR_ERR("ioctl failed");
            close(sock);
            return FALSE;
        }
    
        close(sock);
    
        if ((ifr.ifr_flags & IFF_UP) == 0)
            return FALSE;
    
        return TRUE;
    }
    
  4. 如果是有线 + 无线 SDK,还需要额外适配获取有线外网状态接口。有线 + 无线 SDK 内部实现了有线和无线的切换,有线和无线同时连接则有线优先。当前状态从无线切换到有线,SDK 会调用该接口获取有线的外网是否连通,有线外网正常才会切换到有线。

    /**
     * 简单处理,任何情况都认为有线外网是连通的,只要网线连接并分配了 IP 地址,都是有线优先。
     */
    OPERATE_RET tuya_adapter_wired_if_connect_internet(BOOL_T *status)
    {
        *status = TRUE;
    
        return OPRT_OK;
    }
    

附录

main.c

#include <unistd.h>

#include "uni_log.h"
#include "base_os_adapter.h"
#include "tuya_iot_base_api.h"
#include "tuya_iot_com_api.h"

#include "tuya_iot_sdk_api.h"
#include "tuya_iot_sdk_defs.h"

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
#include "tuya_os_adapt_bt.h"
#endif

#define PID       "fljmamufiym5fktz"                  // 替换成自己的产品 ID
#define UUID      "tuya461dbc63aeeb991f"              // 替换成自己的 UUID
#define AUTHKEY   "c8X4PR4wx1gMFaQlaZu5dfgVvVRwB8Ug"  // 替换成自己的 AUTHKEY

STATIC VOID __gw_reset_cb(GW_RESET_TYPE_E type)
{
    PR_DEBUG("gw reset callback, type: %d", type);

    if (GW_RESET_DATA_FACTORY != type) {
        exit(0);
    }

    return;
}

STATIC VOID __gw_upgrade_cb(CONST FW_UG_S *fw)
{
    PR_DEBUG("gw upgrade callback");

    if (fw == NULL) {
        PR_ERR("invalid param");
        return;
    }

    PR_DEBUG("        tp: %d", fw->tp);
    PR_DEBUG("    fw_url: %s", fw->fw_url);
    PR_DEBUG("    sw_ver: %s", fw->sw_ver);
    PR_DEBUG("   fw_hmac: %s", fw->fw_hmac);
    PR_DEBUG(" file_size: %u", fw->file_size);

    return;
}

STATIC VOID __gw_active_stat_cb(GW_STATUS_E status)
{
    PR_DEBUG("gw active stat callback, status: %d", status);

    return;
}

STATIC VOID __gw_reboot_cb(VOID)
{
    PR_DEBUG("gw reboot callback");

    exit(0);

    return;
}

STATIC VOID __nw_stat_cb(IN CONST SDK_NW_STAT_T stat)
{
    PR_DEBUG("network stat: %d", stat);

    return;
}

STATIC VOID __wired_stat_cb(IN CONST SDK_WIRED_NW_STAT_T stat)
{
    PR_DEBUG("wired stat: %d", stat);

    return;
}

int main(int argc, char **argv)
{
    OPERATE_RET rt = OPRT_OK;
    GW_PROD_INFO_S prod_info = {0};

    /* gw base callback */
    TY_GW_INFRA_CBS_S gw_cbs = {
        .gw_reset_cb       = __gw_reset_cb,
        .gw_upgrade_cb     = __gw_upgrade_cb,
        .gw_active_stat_cb = __gw_active_stat_cb,
        .gw_reboot_cb      = __gw_reboot_cb,
    };

#if defined(TY_BT_MOD) && (TY_BT_MOD == 1)
    tuya_os_adapt_reg_bt_intf();
#endif

    /* initiate os-layer service*/
    tuya_os_intf_init();

    /* initiate iot-layer service */
    TUYA_CALL_ERR_RETURN(tuya_iot_init("./"));

    /* set the logging level to debug */
    SET_PR_DEBUG_LEVEL(TY_LOG_LEVEL_DEBUG);

    PR_DEBUG("SDK INFO: %s", tuya_iot_get_sdk_info());

    /* set uuid and authkey */
    prod_info.uuid     = UUID;
    prod_info.auth_key = AUTHKEY;
    TUYA_CALL_ERR_RETURN(tuya_iot_set_gw_prod_info(&prod_info));

    /* pre-initiate sdk service */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_pre_init(TRUE));  

    /* initiate application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_user_svc_init(&gw_cbs));

    /* initiate sdk service */
#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

    /* register net stat notification callback */
    TUYA_CALL_ERR_RETURN(tuya_iot_sdk_reg_netstat_cb(__nw_stat_cb, __wired_stat_cb, NULL));

    /* start application service, more service in here */
    TUYA_CALL_ERR_RETURN(tuya_user_svc_start(NULL));

    while (1) {
        sleep(10);
    }

    return 0;
}

hal_wired.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>

#include "uni_log.h"
#include "tuya_os_adapt_wired.h"

#define ETH_DEV "eth0"

/**
 * 获取网口 IP 地址接口
 */
OPERATE_RET tuya_adapter_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, ETH_DEV, 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;
}

/**
 * 获取网线连接状态接口
 */
BOOL_T tuya_adapter_wired_station_conn(VOID)
{
    int sock;
    struct sockaddr_in *sin;
    struct ifreq ifr;

    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, ETH_DEV, sizeof(ifr.ifr_name) - 1);

    if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
        PR_ERR("ioctl failed");
        close(sock);
        return FALSE;
    }

    close(sock);

    if ((ifr.ifr_flags & IFF_UP) == 0)
        return FALSE;

    return TRUE;
}

/**
 * 获取有线外网状态接口,仅在有线+无线 SDK 有效。
 */
OPERATE_RET tuya_adapter_wired_if_connect_internet(BOOL_T *status)
{
    *status = TRUE;

    return OPRT_OK;
}

/**
 * 获取无线信号强度接口,仅在有线+无线 SDK 有效。获取信号强度上报到涂鸦云。
 *   a) 当前是有线连接时,通过该接口获取无线的信号强度。
 *   b) 当前是无线连接时,通过 tuya_adapter_wifi_station_get_conn_ap_rssi 接口获取无线的信号强度。
 */
OPERATE_RET tuya_adapter_wired_wifi_station_get_conn_ap_rssi(OUT SCHAR_T *rssi)
{
    *rssi = 99;

    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wired_get_mac(OUT NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wired_set_mac(IN CONST NW_MAC_S *mac)
{
    return OPRT_OK;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wired_wifi_set_station_connect(IN CONST CHAR_T *ssid, IN CONST CHAR_T *passwd)
{
    return OPRT_OK;
}

/* 无需实现 */
BOOL_T tuya_adapter_wired_wifi_need_cfg(VOID)
{
    return FALSE;
}

/* 无需实现 */
OPERATE_RET tuya_adapter_wired_get_nw_stat(GW_BASE_NW_STAT_T *stat)
{
    return OPRT_OK;
}