Wired and Cellular Network Pairing

Last Updated on : 2026-06-03 07:28:03Copy for LLMView as MarkdownDownload PDF

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.

Functional description

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.

Application scenarios

Wired Ethernet

  • The robot is connected via an Ethernet cable, and the upstream router has access to the Internet.
  • The robot is not paired.

Cellular network

  • The robot dials up through a cellular module and obtains a valid IP address.
  • The robot is not paired.
  • The device and the mobile phone do not need to join the same local area network (LAN). The cloud completes pairing and activation.

How it works

Wired Ethernet pairing

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 network pairing

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.

Comparison

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

TKL interface description

Both wired pairing and cellular pairing require you to adapt the following TKL interfaces.

tkl_wired_set_status_cb

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:

  • Save the callback function pointer to a global variable.
  • Start a monitoring thread to monitor network interface status changes in real time.
  • Call the callback to notify the SDK when the network interface status changes.

Callback rules:

  • When the network disconnects, such as when the Ethernet cable is unplugged or the cellular network drops, call cb(TKL_WIRED_LINK_DOWN).
  • When the network connects and obtains an IP address, such as when Ethernet connects or cellular dial-up succeeds, call cb(TKL_WIRED_LINK_UP).

tkl_wired_get_status

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.

  • If the network interface is active and has a valid IP address, return TKL_WIRED_LINK_UP.
  • In other cases, return TKL_WIRED_LINK_DOWN.

tkl_wired_get_ip

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.

  • For a device with multiple Ethernet ports, the SDK uses the IP address returned by this interface for communication.
  • Return the IP address of the network interface that connects to the Internet according to your scenario.
  • In a cellular network scenario, return the IP address obtained from the dial-up interface.

tkl_wired_get_mac

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.

  • For Ethernet, read the hardware MAC address from the NIC.
  • For a cellular network, if the virtual network interface does not have a real MAC address, derive one from the module IMEI to keep the device identifier unique.

tkl_wired_set_mac

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.

Development guide

Configure the network interface

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

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.

Implement tkl_wired_get_ip

#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;
}

Implement 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;
}

Implement 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);  // 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);
}

Implement 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 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;
}

Use an empty implementation for optional interfaces

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

Things to note

  • TKL interfaces must not block. Run time-consuming tasks asynchronously.
  • The application layer manages dial-up. TKL interfaces only report status. They do not trigger dial-up or reconnect the network.
  • The MAC address must be unique. In cellular network scenarios, if the hardware MAC address is unavailable, derive one from a unique identifier such as the IMEI.
  • The cellular IP address can change. When the carrier reassigns the IP address, the SDK detects the change through the status callback and re-establishes the connection.
  • For multi-port devices, make sure 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.
  • The TKL sample code above applies to wired Ethernet. Adjust the code according to the cellular network.

FAQ

Do cellular devices need to implement the 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.

Does UDP broadcast pairing work for cellular network devices?

Usually no. Cellular devices and the mobile phone typically are not on the same LAN, so use QR code pairing instead.

Does the SDK reconnect automatically after a dial-up dropout?

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.

What polling interval works best for 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.

What if the PPP interface name is unknown?

At runtime, run ifconfig or read the /sys/class/net/ to determine the cellular interface name dynamically. Do not hardcode the interface name.