本文详细介绍 SDK 的有线配网功能,以及蜂窝网络(GPRS/3G/4G)配网适配方案,以实现机器人设备接入涂鸦生态。
配网流程的业务逻辑已经在 SDK 内部实现,SDK 定义一套 TuyaOS Kernel Layer(简称 TKL)接口来屏蔽硬件和系统差异,TKL 接口由开发者实现,在 TKL 接口完成底层硬件的操作。本文档将提供 TKL 接口开发指导。
有线配网是把机器人类设备连接到涂鸦 IoT 云的行为。与 Wi-Fi 无线配网不同,有线配网无需输入路由器热点名称与密码。
SDK 不区分底层物理链路类型(以太网或蜂窝网络),仅关注网络接口是否具备有效 IP 地址。因此蜂窝网络(GPRS/3G/4G)设备同样通过实现 tkl_wired_* 系列接口接入涂鸦生态。
机器人设备和手机同时连接到路由器,使得设备和手机处于同一局域网下,设备将其设备信息经过加密处理后定期在局域网上发送 UDP 广播数据包。手机 App 接收到 UDP 广播数据包后解密处理,成功获取到设备信息后会在 自动发现 页面显示待配网设备。
用户在手机 App 上单击 添加 激活待配网设备时,手机 App 与设备建立 TCP 连接,将配网授权信息发送给设备。设备收到配网授权信息后执行激活流程,直到配网完成。
蜂窝网络设备通常不与手机处于同一局域网,无法使用 UDP 广播发现设备。
配网激活方式为 二维码扫码激活:设备生成包含设备信息的二维码,用户使用手机 App 扫码完成配网激活,可参考 扫码激活绑定。
| 维度 | 有线以太网 | 蜂窝网络(GPRS/3G/4G) |
|---|---|---|
| 网络接口名 | eth0 |
ppp0、wwan0、usb0、rmnet0 |
| LINK_UP 判据 | IFF_UP + 有 IP | 拨号成功 + 有效 IP |
| MAC 来源 | 网卡硬件 MAC | 网口 MAC 或 IMEI 派生 |
| 状态轮询间隔 | 1s | 3~5s |
| 配网发现方式 | UDP 局域网广播 | 二维码 |
| 与手机网络关系 | 同一局域网 | 无需同一局域网 |
有线配网、蜂窝配网都需要开发者适配以下 TKL 接口。
OPERATE_RET tkl_wired_set_status_cb(TKL_WIRED_STATUS_CHANGE_CB cb);
功能:SDK 初始化时调用该接口,注册网络接口状态变化的通知回调函数。
用途:
回调调用规则:
cb(TKL_WIRED_LINK_DOWN)。cb(TKL_WIRED_LINK_UP)。OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status);
功能:SDK 获取当前网络接口的连接状态。
判据:SDK 关注网络接口是否有 IP 地址。
TKL_WIRED_LINK_UPTKL_WIRED_LINK_DOWNOPERATE_RET tkl_wired_get_ip(NW_IP_S *ip);
功能:SDK 获取当前网络接口的 IP 地址,用于 Socket 通讯绑定地址。
OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac);
功能:SDK 获取当前网络接口的 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:
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 内部自动选择上线链路。#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;
}
/**
* @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;
}
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);
}
/**
* @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_wired_get_ip 返回的是连接外网的接口 IP,避免返回内部通讯接口地址。tkl_wired_* 接口?是的。SDK 统一使用 tkl_wired_* 接口管理非 Wi-Fi 网络连接,不区分底层物理链路。
通常不可用。蜂窝网络设备与手机不在同一局域网,需使用二维码配网。
SDK 不负责底层拨号管理。当 TKL 接口上报 TKL_WIRED_LINK_DOWN 后,SDK 会暂停云端通讯;当应用层完成重拨并上报 TKL_WIRED_LINK_UP 后,SDK 会自动恢复连接。
tkl_wired_get_status 轮询间隔设置多少合适?建议 3~5 秒。蜂窝网络状态变化相对缓慢,过于频繁的轮询会增加不必要的系统开销。
可在系统启动后通过 ifconfig 或读取 /sys/class/net/ 目录动态确定蜂窝接口名称,避免硬编码。
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈