This topic describes the wired pairing feature of the SDK and how to adapt it for cellular networks (GPRS/3G/4G), so robot devices can join the Tuya ecosystem.
The SDK comes with the implementation of the business logic of the pairing process. It defines a set of TuyaOS Kernel Layer (TKL) interfaces that shield differences in hardware and system. You implement these TKL interfaces to drive the underlying hardware. This topic guides you through developing the TKL interfaces.
Wired pairing enables a robot device to connect to the Tuya IoT Cloud. Unlike Wi-Fi pairing, wired pairing does not require the router’s SSID and password.
The SDK does not differentiate between physical links (Ethernet or cellular network). It only checks whether the network interface has a valid IP address. Therefore, cellular network devices (GPRS/3G/4G) join the Tuya ecosystem by implementing the same tkl_wired_* interfaces.
Connect the robot device and the mobile phone to the same router, so that they share a LAN. The device periodically broadcasts its encrypted device information using User Datagram Protocol (UDP) on the LAN. The mobile app receives and decrypts the UDP packet, then lists the device ready for pairing on the Auto Discovery page.
When the user taps Add in the app, the app sends the pairing authorization to the device over TCP. The device receives the pairing information and runs the activation flow.
Cellular devices usually do not join the same LAN as the mobile phone, so the mobile app cannot discover them through UDP broadcast.
Use QR code activation for cellular devices. The device generates a QR code that contains device information. The user scans it with the mobile app to complete pairing and activation. For more information, see Activation and Binding with QR Code.
| Dimension | Wired Ethernet | Cellular network (GPRS/3G/4G) |
|---|---|---|
| Network interface name | eth0 |
ppp0, wwan0, usb0, rmnet0 |
LINK_UP criterion |
IFF_UP and a valid IP address |
Successful dial-up and a valid IP address |
| Media Access Control (MAC) address source | Hardware MAC address of the network interface card (NIC) | Interface MAC address or MAC address derived from the International Mobile Equipment Identity (IMEI) |
| Status polling interval | 1 s | 3 s to 5 s |
| Pairing discovery method | UDP broadcast on the LAN | QR code |
| Relationship with the mobile phone network | Same LAN | No LAN requirement |
Both wired pairing and cellular pairing require you to adapt the following TKL interfaces.
OPERATE_RET tkl_wired_set_status_cb(TKL_WIRED_STATUS_CHANGE_CB cb);
Description: The SDK calls this interface during initialization to register a callback for network interface status changes.
Usage:
Callback rules:
cb(TKL_WIRED_LINK_DOWN).cb(TKL_WIRED_LINK_UP).OPERATE_RET tkl_wired_get_status(TKL_WIRED_STAT_E *status);
Description: The SDK retrieves the current connection status of the network interface.
Criterion: The SDK checks whether the network interface has an IP address.
TKL_WIRED_LINK_UP.TKL_WIRED_LINK_DOWN.OPERATE_RET tkl_wired_get_ip(NW_IP_S *ip);
Description: The SDK retrieves the IP address of the current network interface to set the socket address.
OPERATE_RET tkl_wired_get_mac(NW_MAC_S *mac);
Description: The SDK retrieves the MAC address of the current network interface for unique device identification.
OPERATE_RET tkl_wired_set_mac(CONST NW_MAC_S *mac);
Description: The SDK sets the MAC address of the network interface.
This interface is optional. You can use an empty implementation that returns OPRT_OK.
Define the network interface name according to the hardware link type:
// Select the network interface according to the hardware configuration.
#if defined(NETWORK_TYPE_CELLULAR)
// Cellular network: determine the interface name according to the module driver.
// Common interface names: ppp0 for Point-to-Point Protocol (PPP) dial-up, wwan0, usb0, and rmnet0.
#define WIRED_NAME "ppp0"
#elif defined(NETWORK_TYPE_ETHERNET)
#define WIRED_NAME "eth0"
#else
#define WIRED_NAME "eth0"
#endif
Initialize the SDK with the wired pairing interface:
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)
// Wi-Fi SDK. Wired pairing is not supported.
return OPRT_COM_ERROR;
#else
// Scenarios with wired or cellular networks only.
TUYA_CALL_ERR_RETURN(tuya_iot_sdk_init(PID, USER_SW_VER, NULL, 0));
#endif
// ...
}
Initialize the SDK with both Wi-Fi and wired pairing:
Follow these steps to enable both Wi-Fi pairing and wired Internet access for the device. In this scenario, call tuya_iot_wf_dev_init and register status callbacks for both Wi-Fi and wired links.
/**
* @brief Activate short URL callback.
* When the device is not activated, the SDK generates a short URL. You can convert the URL into a QR code for the mobile app to scan.
* In this callback, you can display shorturl on the screen or report it to the host.
*/
VOID ty_sdk_active_shorturl_cb(CONST CHAR_T* shorturl)
{
PR_DEBUG("shorturl:%s", shorturl);
}
/**
* @brief Wired network status callback.
* tuya_iot_reg_get_nw_stat_cb() registers this callback. The SDK calls this callback when the network status changes.
* Common GW_BASE_NW_STAT_T values:
* GB_STAT_LAN_UNCONN – LAN disconnected (Ethernet cable unplugged or cellular network dropped).
* GB_STAT_LAN_CONN – LAN connected (IP address assigned, but cloud is not yet connected).
* GB_STAT_CLOUD_CONN – Cloud connected (device fully online).
*/
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)
{
// ...
/* Register SDK event callbacks. All callbacks are optional. Set unused callbacks to NULL. */
TY_IOT_CBS_S iot_cbs = {0};
iot_cbs.gw_status_cb = ty_sdk_dev_status_changed_cb; /* Device activation status change (not activated -> activated -> unbound) */
iot_cbs.gw_ug_cb = ty_user_upgrade_inform_cb; /* Over-the-air (OTA) update notification. Download and install the firmware after receiving the notification. */
iot_cbs.pre_gw_ug_cb = ty_dev_upgrade_pre_check_cb; /* OTA pre-check. Run service-specific safety checks before an OTA update. */
iot_cbs.gw_reset_cb = ty_sdk_app_reset_cb; /* Handles device removal from the app or factory reset. */
iot_cbs.dev_obj_dp_cb = ty_cmd_handle_dp_cmd_objs; /* Receives object type data point (DP) commands from the app or cloud. */
iot_cbs.dev_raw_dp_cb = ty_cmd_handle_dp_raw_objs; /* Receives raw type DP commands from the app or cloud. */
iot_cbs.dev_dp_query_cb = ty_cmd_handle_dp_query_objs; /* Handles DP status queries from the app or cloud. */
iot_cbs.active_shorturl = ty_sdk_active_shorturl_cb; /* Short URL callback for the not activated state. Convert the URL into a QR code for the app to scan. */
/* Initialize the wired service. Starts the tkl_wired status monitoring thread. */
TUYA_CALL_ERR_RETURN(tuya_svc_wired_init());
/* Initialize Wi-Fi and wired coexistence.
* GWCM_OLD_PROD – Production pairing mode. Supports both access point (AP) mode pairing and wired auto-discovery.
* WF_START_AP_ONLY – Wi-Fi pairing sub-mode. The device starts in AP mode and waits for the app to push Wi-Fi credentials.
* DEV_NM_ATH_SNGL – Single-network authentication channel.
* NULL / 0 – No additional firmware, such as microcontroller unit (MCU) sub-firmware, in this example. If needed, pass attr and 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));
/* Enable LAN broadcast so that the mobile app can automatically discover the device on the LAN. */
rt = tuya_svc_lan_init(); // Note: only wired LAN requires this call. Cellular networks such as 4G can skip it.
if (OPRT_OK != rt) {
PR_ERR("tuya_svc_lan_init error:%d", rt);
}
/* Register the Wi-Fi link status callback. This callback works with the wired link callback, and the SDK automatically selects the online link. */
TUYA_CALL_ERR_RETURN(tuya_iot_reg_get_wf_nw_stat_cb(ty_sdk_net_status_change_cb));
/* Register the wired or general network status callback. For status values, see tuya_wired_stat_change_cb. */
TUYA_CALL_ERR_RETURN(tuya_iot_reg_get_nw_stat_cb(tuya_wired_stat_change_cb));
// ...
}
Note:
GWCM_OLD_PROD: Production pairing mode. Supports both AP mode pairing and wired auto-discovery.WF_START_AP_ONLY: Wi-Fi pairing sub-mode. The device starts in AP mode and waits for pairing.DEV_NM_ATH_SNGL: Single-network authentication channel type.tkl_wired_set_status_cb registers the callback that drives the wired link status. tuya_iot_reg_get_wf_nw_stat_cb registers the callback that drives the Wi-Fi link status. The two callbacks work in parallel, and the SDK automatically selects the online link.#define WIRED_NAME "eth0" // For 4G, use #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 address. */
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); // Cellular network status changes slowly. Increase the polling interval as needed.
}
}
/**
* @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 address. */
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 returns the IP address of the network interface that connects to the Internet. Do not return the address of an internal communication interface.tkl_wired_* interfaces?Yes. The SDK uses the tkl_wired_* interfaces to manage non-Wi-Fi network connections and does not differentiate between physical link types.
Usually no. Cellular devices and the mobile phone typically are not on the same LAN, so use QR code pairing instead.
The SDK does not manage low-level dial-up. When a TKL interface reports TKL_WIRED_LINK_DOWN, the SDK pauses cloud communication. After the application layer redials and reports TKL_WIRED_LINK_UP, the SDK restores the connection automatically.
tkl_wired_get_status on cellular network devices?Use an interval of 3 s to 5 s. Cellular network status changes slowly, and frequent polling adds unnecessary system overhead.
At runtime, run ifconfig or read the /sys/class/net/ to determine the cellular interface name dynamically. Do not hardcode the interface name.
Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback