有线/蜂窝配网

更新时间:2026-06-01 09:22:53LLM 副本以 Markdown 格式查看下载 PDF

本文详细介绍 SDK 的有线配网功能,以及蜂窝网络(GPRS/3G/4G)配网适配方案,以实现机器人设备接入涂鸦生态。

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

功能描述

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

SDK 不区分底层物理链路类型(以太网或蜂窝网络),仅关注网络接口是否具备有效 IP 地址。因此蜂窝网络(GPRS/3G/4G)设备同样通过实现 tkl_wired_* 系列接口接入涂鸦生态。

适用场景

有线以太网场景

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

蜂窝网络场景

  • 机器人通过蜂窝模组(GPRS/3G/4G)拨号上网,已获取有效 IP 地址。
  • 机器人处于未配网状态。
  • 蜂窝网络场景下,设备无需与手机处于同一局域网,配网激活通过云端完成。

工作原理

有线以太网配网原理

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

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

蜂窝网络配网原理

蜂窝网络设备通常不与手机处于同一局域网,无法使用 UDP 广播发现设备。

配网激活方式为 二维码扫码激活:设备生成包含设备信息的二维码,用户使用手机 App 扫码完成配网激活,可参考 扫码激活绑定

对比总结

维度 有线以太网 蜂窝网络(GPRS/3G/4G)
网络接口名 eth0 ppp0wwan0usb0rmnet0
LINK_UP 判据 IFF_UP + 有 IP 拨号成功 + 有效 IP
MAC 来源 网卡硬件 MAC 网口 MAC 或 IMEI 派生
状态轮询间隔 1s 3~5s
配网发现方式 UDP 局域网广播 二维码
与手机网络关系 同一局域网 无需同一局域网

TKL 接口说明

有线配网、蜂窝配网都需要开发者适配以下 TKL 接口。

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 地址。
  • 蜂窝网络场景下,返回拨号接口获取的 IP 地址。

tkl_wired_get_mac

OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac);

功能:SDK 获取当前网络接口的 MAC 地址,用于设备唯一标识。

  • 以太网直接读取网卡硬件 MAC。
  • 蜂窝网络若虚拟网口无真实 MAC,可从模组 IMEI 派生,保证设备唯一性。

tkl_wired_set_mac

OPERATE_RET tkl_wired_set_mac(CONST NW_MAC_S *mac);

功能:SDK 设置网络接口的 MAC 地址。

非必要接口,可填充空实现返回 OPRT_OK

开发指导

网络接口配置

根据实际硬件链路类型定义网络接口名称:

// 根据实际硬件配置选择网络接口
#if defined(NETWORK_TYPE_CELLULAR)
    // 蜂窝网络:根据模组驱动确定接口名
    // 常见接口名:ppp0(PPP 拨号)、wwan0、usb0、rmnet0
    #define WIRED_NAME "ppp0"
#elif defined(NETWORK_TYPE_ETHERNET)
    #define WIRED_NAME "eth0"
#else
    #define WIRED_NAME "eth0"
#endif

SDK 初始化

使用有线配网接口初始化 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

    // ...
}

使用 Wi-Fi 与有线并存的配网接口初始化 SDK:

按照以下配置流程,设备即可同时具备 Wi-Fi 配网与有线上网能力。此场景下需调用 tuya_iot_wf_dev_init 并注册 Wi-Fi 与有线双链路的状态回调。


/**
 * @brief 激活短链回调
 *        设备未激活时,SDK 生成一个短链 URL,可将其转换为二维码供手机 App 扫码激活。
 *        开发者可在此回调中将 shorturl 显示到屏幕或上报给上位机。
 */
VOID ty_sdk_active_shorturl_cb(CONST CHAR_T* shorturl)
{
    PR_DEBUG("shorturl:%s", shorturl);
}

/**
 * @brief 有线网络综合状态回调
 *        由 tuya_iot_reg_get_nw_stat_cb() 注册,SDK 在网络状态切换时调用。
 *        GW_BASE_NW_STAT_T 常用值:
 *          GB_STAT_LAN_UNCONN  — 局域网断开(有线拔线/蜂窝掉线)
 *          GB_STAT_LAN_CONN    — 局域网已连接(有 IP 地址,但尚未连上云端)
 *          GB_STAT_CLOUD_CONN  — 云端已连接(设备完全在线)
 */
VOID tuya_wired_stat_change_cb(IN CONST GW_BASE_NW_STAT_T status)
{
    PR_NOTICE("tuya_wired_stat_change_cb status:%d", status);
    if (status == GB_STAT_LAN_CONN) {
        PR_NOTICE("tuya_wired_stat_change_cb connected");
    } else if (status == GB_STAT_LAN_UNCONN) {
        PR_NOTICE("tuya_wired_stat_change_cb disconnected");
    } else if (status == GB_STAT_CLOUD_CONN) {
        PR_NOTICE("tuya_wired_stat_change_cb connected to cloud");
    } else {
        PR_NOTICE("tuya_wired_stat_change_cb unknown status:%d", status);
    }
}

int main(int argc, char **argv)
{
    // ...

    /* 填写 SDK 事件回调;所有回调均为可选,不需要的置 NULL 即可 */
    TY_IOT_CBS_S iot_cbs = {0};
    iot_cbs.gw_status_cb    = ty_sdk_dev_status_changed_cb;  /* 设备激活状态变化(未激活→激活→解绑) */
    iot_cbs.gw_ug_cb        = ty_user_upgrade_inform_cb;     /* OTA 升级通知,收到后执行固件下载与更新 */
    iot_cbs.pre_gw_ug_cb    = ty_dev_upgrade_pre_check_cb;   /* OTA 升级前预检查,可在此做业务安全判断 */
    iot_cbs.gw_reset_cb     = ty_sdk_app_reset_cb;           /* APP 移除设备或恢复出厂时触发 */
    iot_cbs.dev_obj_dp_cb   = ty_cmd_handle_dp_cmd_objs;     /* 接收 App/云端下发的 OBJ 类型 DP 指令 */
    iot_cbs.dev_raw_dp_cb   = ty_cmd_handle_dp_raw_objs;     /* 接收 App/云端下发的 RAW 类型 DP 指令 */
    iot_cbs.dev_dp_query_cb = ty_cmd_handle_dp_query_objs;   /* App/云端主动查询 DP 状态时触发 */
    iot_cbs.active_shorturl = ty_sdk_active_shorturl_cb;     /* 未激活时的短链回调,可转二维码供 App 扫码 */

    /* 初始化有线服务;内部启动 tkl_wired 状态监听线程 */
    TUYA_CALL_ERR_RETURN(tuya_svc_wired_init());

    /* Wi-Fi + 有线并存初始化
     *   GWCM_OLD_PROD   — 生产配网模式,支持热点模式配网与有线自动发现并存
     *   WF_START_AP_ONLY — 设备启动时仅开启热点,等待 App 推送 Wi-Fi 凭据
     *   DEV_NM_ATH_SNGL  — 单网络认证通道
     *   NULL / 0         — 本示例无附加固件(MCU 子固件),如有需填 attr 与 attr_num
     */
    TUYA_CALL_ERR_RETURN(tuya_iot_wf_dev_init(GWCM_OLD_PROD, WF_START_AP_ONLY,
                                               &iot_cbs, NULL,
                                               PID, USER_SW_VER,
                                               DEV_NM_ATH_SNGL, NULL, 0));

    /* 开启局域网广播,使手机 App 能在局域网内自动发现设备 */
    rt = tuya_svc_lan_init();    //注:仅有线局域网需要调用此接口,4G等无线网络无需调用。
    if (OPRT_OK != rt) {
        PR_ERR("tuya_svc_lan_init error:%d", rt);
    }

    /* 注册 Wi-Fi 链路状态回调;与有线链路回调并行工作,SDK 内部自动选择上线链路 */
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_get_wf_nw_stat_cb(ty_sdk_net_status_change_cb));

    /* 注册有线 / 综合网络状态回调;状态枚举见 tuya_wired_stat_change_cb 说明 */
    TUYA_CALL_ERR_RETURN(tuya_iot_reg_get_nw_stat_cb(tuya_wired_stat_change_cb));

    // ...
}

说明

  • GWCM_OLD_PROD:配网模式,支持热点模式配网与有线自动发现并存。
  • WF_START_AP_ONLY:Wi-Fi 配网子模式,设备启动时开启热点等待配网。
  • DEV_NM_ATH_SNGL:单网络认证通道类型。
  • 有线链路状态由 tkl_wired_set_status_cb 注册的回调驱动,Wi-Fi 链路状态由 tuya_iot_reg_get_wf_nw_stat_cb 注册的回调驱动,两者并行工作,SDK 内部自动选择上线链路。

实现 tkl_wired_get_ip

#define WIRED_NAME "eth0"  //如 4G,即 #define WIRED_NAME "usb0"
/**
 * @brief  get the ip address of the wired link
 * 
 * @param[in]   ip: the ip address
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_ip(OUT NW_IP_S *ip)
{
    int sock = -1;
    struct ifreq ifr;
    struct sockaddr_in *sin;

    if (ip == NULL) {
        printf( "%s %d: invalid param\n", __func__, __LINE__ );
        return OPRT_INVALID_PARM;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf( "%s %d: socket create fail\n", __func__, __LINE__ );
        return OPRT_SOCK_ERR;
    }

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, WIRED_NAME, sizeof(ifr.ifr_name) - 1);
    sin = (struct sockaddr_in *)&(ifr.ifr_addr);

    /* get ip addr */
    if (ioctl(sock, SIOCGIFADDR, &ifr) < 0) {
        //printf( "%s %d: %s ip ioctl error=%d,%s\n", __func__, __LINE__, ifr.ifr_name, errno, strerror( errno ) );
        goto com_error;
    }
    strncpy(ip->addr.ip4.ip, inet_ntoa(sin->sin_addr), sizeof(ip->addr.ip4.ip));

    /* get net mask */
    if (ioctl(sock, SIOCGIFNETMASK, &ifr) < 0) {
        //printf( "%s %d: %s mask ioctl error=%d,%s\n", __func__, __LINE__, ifr.ifr_name, errno, strerror( errno ) );
        goto com_error;
    }
    strncpy(ip->addr.ip4.mask, inet_ntoa(sin->sin_addr), sizeof(ip->addr.ip4.mask));

    /* get gateway */
    if (tkl_wired_get_gateway(ip) < 0)
        goto com_error;

    close(sock);
    return OPRT_OK;

com_error:
    close(sock);
    return OPRT_COM_ERROR;
}

实现 tkl_wired_get_status

/**
 * @brief  get the link status of wired link
 *
 * @param[out]  status: the wired link status is up or not
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status)
{
    int sock = -1;
    struct ifreq ifr;
    int ret = OPRT_COM_ERROR;

    if ( !status ){
        printf( "%s %d: arg error\n", __func__, __LINE__ );
        return OPRT_COM_ERROR;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf( "%s %d: socket error=%d,%s\n", __func__, __LINE__, errno, strerror( errno ) );
        return OPRT_SOCK_ERR;
    }

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, WIRED_NAME, sizeof(ifr.ifr_name) - 1);

    if ( ioctl( sock, SIOCGIFFLAGS, &ifr )  < 0 ) {
        printf( "%s %d: failed to get iterface info=%d,%s\n", __func__, __LINE__, errno, strerror( errno ) );
        ret = OPRT_COM_ERROR;
        goto exit_end;

    }

    if ( ifr.ifr_flags & IFF_RUNNING ) {
        *status = TKL_WIRED_LINK_UP;

    }
    else {
        *status = TKL_WIRED_LINK_DOWN;
    }
    ret = OPRT_OK;

exit_end:
    if ( sock ) {
        close( sock );
    }
    return ret;
}

实现 tkl_wired_set_status_cb

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);  // 蜂窝网络状态变化相对缓慢,适当增大轮询间隔
    }
}

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

实现 tkl_wired_get_mac

/**
 * @brief  get the mac address of the wired link
 * 
 * @param[in]   mac: the mac address
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */
OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac)
{
    int sock = -1;
    struct ifreq ifr;
    struct sockaddr *addr;

    if (mac == NULL) {
        printf( "%s %d: invalid param\n", __func__, __LINE__ );
        return OPRT_INVALID_PARM;
    }

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf( "%s %d: socket create fail\n", __func__, __LINE__ );
        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) {
        printf( "%s %d: get mac, error=%d,%s\n", __func__, __LINE__,  errno, strerror( errno ) );
        close(sock);
        return OPRT_COM_ERROR;
    }

    memcpy(mac->mac, addr->sa_data, MAC_ADDR_LEN);

    close(sock);
    return OPRT_OK;
}

非必要接口空实现

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

注意事项

  • TKL 接口不允许阻塞,耗时的任务建议做异步处理。
  • 拨号管理由应用层负责,TKL 接口仅负责状态上报,不负责触发拨号或断线重连。
  • MAC 地址必须保证唯一性,蜂窝网络场景下若无法获取硬件 MAC,必须使用 IMEI 等唯一标识派生。
  • 蜂窝网络的 IP 地址可能变化(运营商重新分配),SDK 内部会通过状态回调感知并重新建立连接。
  • 多网口设备 需确保 tkl_wired_get_ip 返回的是连接外网的接口 IP,避免返回内部通讯接口地址。
  • 以上 TKL 接口例程代码是针对有线以太网做的适配,其它蜂窝网络需要根据实际情况做调整。

FAQ

蜂窝网络设备是否必须实现 tkl_wired_* 接口?

是的。SDK 统一使用 tkl_wired_* 接口管理非 Wi-Fi 网络连接,不区分底层物理链路。

蜂窝网络场景下 UDP 广播配网是否可用?

通常不可用。蜂窝网络设备与手机不在同一局域网,需使用二维码配网。

拨号断线后 SDK 会自动重连吗?

SDK 不负责底层拨号管理。当 TKL 接口上报 TKL_WIRED_LINK_DOWN 后,SDK 会暂停云端通讯;当应用层完成重拨并上报 TKL_WIRED_LINK_UP 后,SDK 会自动恢复连接。

蜂窝网络设备的 tkl_wired_get_status 轮询间隔设置多少合适?

建议 3~5 秒。蜂窝网络状态变化相对缓慢,过于频繁的轮询会增加不必要的系统开销。

PPP 接口名称不确定怎么办?

可在系统启动后通过 ifconfig 或读取 /sys/class/net/ 目录动态确定蜂窝接口名称,避免硬编码。