Last Updated on : 2024-06-25 09:32:26download
This topic describes how to enable Zigbee features and connect to third-party Zigbee devices on top of Zigbee functionality.
This topic only describes software implementation. Connecting your hardware to Tuya’s Zigbee module is the prerequisite. Before you code, check for hardware connection by reading the module’s version number.
Zigbee sub-device connectivity aims to lower the barrier to developing a Zigbee gateway. Tuya’s hardware and software-integrated solution provides Zigbee module and software implementation, allowing you to build a Zigbee gateway with no code and connect to Zigbee sub-devices in the Tuya ecosystem.
TuyaOS Gateway Development Framework provides Zigbee interfaces for connection to third-party Zigbee devices.
Zigbee sub-devices in the Tuya ecosystem come across various categories, including electrical products, lights, home appliances, and sensors. However, developing a Tuya-enabled smart gateway can be complex and costly. Given this, Tuya’s generic gateway solution is the optimal choice.
If the Tuya development framework has not imported the chip platform you use, for example, your proprietary chip, but you want to enable your hardware to connect to Zigbee sub-devices in the Tuya ecosystem. The solution described in this topic should be your choice. Connect your hardware to Tuya’s Zigbee module through the serial port and enable Zigbee features in the software. Then, your product becomes Zigbee-capable.
On top of the basic Zigbee functionality, you can extend your product to connect third-party Zigbee sub-devices to the Tuya Developer Platform by using the Zigbee interfaces provided by TuyaOS Gateway Development Framework. This can help to differentiate your product in the market.
This section describes how to enable Zigbee features with TuyaOS Gateway Development Framework and implement Zigbee gateway capabilities with Tuya’s Zigbee module.
Two interfaces are used to enable Zigbee features.
These two interfaces have the same parameters in JSON, but differ in configurations.
Fields in the JSON data:
Field | Description |
---|---|
storage_path | The path to persistent storage where Zigbee network information and sub-device information resides. This field is deprecated. Set the storage path using tuya_load_config/tuya_set_config . |
cache_path | The path to temporary storage where OTA firmware updates reside. This field is deprecated. Set the storage path using tuya_load_config/tuya_set_config . |
dev_name | Specifies the serial port number. |
cts | Specifies whether to enable hardware flow control.
|
thread_mode | Specifies the mode to run the Zigbee host.
|
// ...
#include "tuya_zigbee_api.h"
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
This section describes how to enable your gateway to connect to third-party Zigbee sub-devices, which can make your gateway more friendly for compatibility with Zigbee devices.
Before you get started, it can be helpful to get acquainted with how to implement a Zigbee sub-device connectivity with an SDK. For more information, see Sub-Device Connectivity.
TuyaOS Gateway Development Framework allows for connection to third-party Zigbee sub-devices by means of allowlist registration.
Register the Manufacturer Name and Model Identifier of the third-party Zigbee device to the SDK. The Basic Cluster present in a Zigbee sub-device stores these two attributes.
After a Zigbee sub-device joins the network, the SDK matches this device’s Manufacturer Name and Model Identifier against the allowlist. If a match is found, the SDK will record the MAC address of this device and notify the application of data reporting from this device through a callback. Otherwise, the SDK handles this device internally.
Implementation:
TY_Z3_DEV_S
struct array variable. Add the Manufacturer Name and Model Identifier of the third-party Zigbee devices to the array.TY_Z3_DEVLIST_S
struct variable. Assign the TY_Z3_DEV_S
struct array variable and its length to this variable.TY_Z3_DEV_CBS_S
struct variable to register the Zigbee sub-device management callbacks.tuya_zigbee_custom_dev_mgr_init
to register the allowlist and the Zigbee sub-device management callback.Example:
#include "tuya_zigbee_api.h"
// Define an allowlist
STATIC TY_Z3_DEV_S my_z3_dev[] = {
{ "TUYATEC-nPGIPl5D", "TS0001" },
{ "TUYATEC-1xAC7DHQ", "TZ3000" },
// ...
};
STATIC TY_Z3_DEVLIST_S my_z3_devlist = {
.devs = my_z3_dev,
.dev_num = CNTSOF(my_z3_dev),
};
STATIC VOID __my_z3_dev_join(TY_Z3_DESC_S *dev) {}
STATIC VOID __my_z3_dev_leave(CONST CHAR_T *dev_id) {}
int main(int argc, char **argv)
{
OPERATE_RET op_ret = OPRT_OK;
// Zigbee sub-device management callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.join = __my_z3_dev_join,
.leave = __my_z3_dev_leave,
};
// Register allowlist and device callback
op_ret = tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_custom_dev_mgr_init err: %d", op_ret);
return op_ret;
}
return 0;
}
After a permitted device joins the network, the SDK calls the join
callback to notify the application for processing. In the callback, you should parse the sub-device information and call the device binding interface to connect the sub-device to the Tuya Developer Platform. Implementation:
TY_Z3_DEV_CBS_S
join
callback for network joining. In this callback, parse the sub-device information and call tuya_iot_gw_bind_dev
to connect the sub-device to the Tuya Developer Platform.TY_Z3_DEV_CBS_S
report
callback for reporting data. In this callback, convert Zigbee Cluster Library (ZCL) data to Tuya-defined data point (DP) and call the DP reporting interface to upload the DP data to the Tuya Developer Platform.TY_GW_SUBDEV_MGR_CBS_S
dev_bind
callback for binding notification. In this callback, send the ZCL data to the sub-device to query its current status.The process of sub-device joining the network:
Example:
// ...
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
#define PROFILE_ID_HA 0x0104
#define ZCL_BASIC_CLUSTER_ID 0x0000
#define ZCL_ON_OFF_CLUSTER_ID 0x0006
#define ZCL_CMD_TYPE_GLOBAL 0x00
#define ZCL_CMD_TYPE_PRIVATE 0x01
#define ZCL_READ_ATTRIBUTES_COMMAND_ID 0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID 0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER 3 /* Attribute ID: 2 Bytes, Status: 1 Byte */
#define ZCL_REPORT_ATTRIBUTES_HEADER 2 /* Attribute ID: 2 Bytes */
TY_Z3_DEV_S my_z3_dev[] = {
{ "TUYATEC-nPGIPl5D", "TS0001" },
{ "TUYATEC-1xAC7DHQ", "TS0002" },
};
TY_Z3_DEVLIST_S my_z3_devlist = {
.devs = my_z3_dev,
.dev_num = CNTSOF(my_z3_dev),
};
/**
* @brief Data reporting notification
* @note You need to convert ZCL data into DP data, and then report the DP data to the cloud. Meanwhile, refresh the sub-device heartbeat to maintain its online status.
*/
STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
TY_OBJ_DP_S dp_data = {0};
// Check if the device is bound
dev_if = tuya_iot_get_dev_if(frame->id);
if (!dev_if || !dev_if->bind) {
PR_ERR("dev_id: %s is not bind", frame->id);
tuya_zigbee_del_dev(frame->id);
return;
}
// Refresh device heartbeat
op_ret = tuya_iot_fresh_dev_hb(frame->id);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
}
// For simplicity, this demo uses a 1-gang smart switch to show how data is processed. In an actual project, you need to implement the mapping between ZCL data and DP data.
if ((frame->profile_id != PROFILE_ID_HA) ||
(frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID)) {
return;
}
if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
} else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
} else {
return;
}
dp_data.dpid = frame->src_endpoint;
dp_data.type = PROP_BOOL;
// Report DP
op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
if (op_ret != OPRT_OK) {
PR_ERR("dev_report_dp_json_async err: %d", op_ret);
return;
}
}
/**
* @brief Joining network notification
* @note According to sub-device information, map the sub-device identify to its PID.
*/
STATIC VOID __my_z3_dev_join(TY_Z3_DESC_S *dev)
{
OPERATE_RET op_ret = OPRT_OK;
// For simplicity, the PID and the firmware version number are hard coded. In an actual project, they should match the real sub-device.
op_ret = tuya_iot_gw_bind_dev(GP_DEV_ATH_1, 0, dev->id, "ckj1pnvy", "1.0.0");
if (op_ret != OPRT_OK) {
PR_ERR("tuya_iot_gw_bind_dev err: %d", op_ret);
return;
}
}
/**
* @brief Binding result notification
* @note You need to send the attribute reading command to the sub-device to instruct it to report the current attribute value.
*/
STATIC VOID __dev_bind_cb(CONST CHAR_T *dev_id, CONST OPERATE_RET result)
{
OPERATE_RET op_ret = OPRT_OK;
TY_Z3_APS_FRAME_S frame = {0};
USHORT_T atrr_buf[5] = { 0x0000, 0x4001, 0x4002, 0x8001, 0x5000 };
// Binding failed. Remove the sub-device.
if (result != OPRT_OK) {
PR_ERR("dev_id: %s bind err", dev_id);
tuya_zigbee_del_dev(dev_id);
return;
}
// Set heartbeat
op_ret = tuya_iot_set_dev_hb_cfg(dev_id, 120, 3, FALSE);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
}
// For simplicity, this demo uses a 1-gang smart switch to show command sending. The logic is hard coded. In an actual project, you need to process it as required.
strncpy(frame.id, dev_id, SIZEOF(frame.id));
frame.profile_id = PROFILE_ID_HA;
frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
frame.src_endpoint = 0x01;
frame.dst_endpoint = 0xff;
frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;
frame.msg_length = SIZEOF(atrr_buf);
frame.message = (UCHAR_T *)atrr_buf;
op_ret = tuya_zigbee_send_data(&frame);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
return;
}
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.join = __my_z3_dev_join,
.report = __my_z3_dev_report,
};
// Sub-device management callback
TY_GW_SUBDEV_MGR_CBS_S dev_mgr_cbs = {
.dev_bind_ifm = __dev_bind_cb,
};
// Register device management
TUYA_CALL_ERR_RETURN(tuya_subdev_user_sigle_type_reg(&dev_mgr_cbs, GP_DEV_ATH_1));
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
Control (send command to sub-device): To execute a command from the mobile app or automation, the SDK calls the callback for sending DP commands to notify the application for processing. In the callback, convert the DP data to the ZCL data and send the converted data to the sub-device.
Report (report data to cloud): After the sub-device executes a command from a mobile app or physical operation, it sends the current status to the SDK. The SDK calls the data reporting callback to notify the application for processing. In the callback, convert the ZCL data to the DP data and upload the converted data to the cloud.
Data reporting is used as a response to command sending. Therefore, after the SDK calls the callback for sending DP commands, the Zigbee sub-device proactively reports the current status of the corresponding attribute after command execution. For third-party Zigbee sub-devices without the proactive data reporting logic, the SDK should query the corresponding attribute.
Methods to implement device control and data reporting:
TY_GW_SUBDEV_MGR_CBS_S
dp_cmd_obj
callback for sending DP data. In this callback, convert the DP data to the ZCL data and call tuya_zigbee_send_data
to send the converted data to the sub-device.TY_Z3_DEV_CBS_S
report
callback for reporting data. In this callback, convert the ZCL data to the DP data and call the DP reporting interface to upload the DP data to the Tuya Developer Platform.The process of controlling a sub-device:
Example:
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
#define PROFILE_ID_HA 0x0104
#define ZCL_BASIC_CLUSTER_ID 0x0000
#define ZCL_ON_OFF_CLUSTER_ID 0x0006
#define ZCL_CMD_TYPE_GLOBAL 0x00
#define ZCL_CMD_TYPE_PRIVATE 0x01
#define ZCL_READ_ATTRIBUTES_COMMAND_ID 0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID 0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER 3 /* Attribute ID: 2 Bytes, Status: 1 Byte */
#define ZCL_REPORT_ATTRIBUTES_HEADER 2 /* Attribute ID: 2 Bytes */
TY_Z3_DEV_S my_z3_dev[] = {
{ "TUYATEC-nPGIPl5D", "TS0001" },
{ "TUYATEC-1xAC7DHQ", "TS0002" },
};
TY_Z3_DEVLIST_S my_z3_devlist = {
.devs = my_z3_dev,
.dev_num = CNTSOF(my_z3_dev),
};
/**
* @brief DP command sending callback
* @note You need to convert DP data into ZCL data, and then send it to the sub-device.
*/
STATIC VOID __dev_cmd_obj_cb(CONST TY_RECV_OBJ_DP_S *cmd)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
PR_DEBUG("cmd_tp: %d, dtt_tp: %d, dps_cnt: %u", cmd->cmd_tp, cmd->dtt_tp, cmd->dps_cnt);
// Check if the device is bound
dev_if = tuya_iot_get_dev_if(cmd->cid);
if (dev_if == NULL) {
PR_WARN("%s is not bind", cmd->cid);
return;
}
// For simplicity, this demo uses a 1-gang smart switch to show how data is processed. In an actual project, you need to implement the mapping between ZCL data and DP data.
for (i = 0; i < cmd->dps_cnt; i++) {
BYTE_T dpid = cmd->dps[i].dpid;
TY_Z3_APS_FRAME_S frame = {0};
switch (dpid)
{
case 1 ... 6:
if (cmd->dps[i].type != PROP_BOOL) {
PR_WARN("dp type: %d invalid", cmd->dps[i].type);
break;
}
strncpy(frame.id, cmd->cid, SIZEOF(frame.id));
frame.profile_id = PROFILE_ID_HA;
frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
frame.cmd_type = ZCL_CMD_TYPE_PRIVATE;
frame.frame_type = 0;
frame.src_endpoint = 0x01;
frame.dst_endpoint = dpid;
frame.cmd_id = cmd->dps[i].value.dp_bool;
frame.msg_length = 0;
op_ret = tuya_zigbee_send_data(&frame);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
}
break;
default:
PR_WARN("dpid: %d is not supported", cmd->dps[i].dpid);
break;
}
}
}
/**
* @brief Data reporting notification
* @note You need to convert ZCL data into DP data, and then report the DP data to the cloud. Meanwhile, refresh the sub-device heartbeat to maintain its online status.
*/
STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
TY_OBJ_DP_S dp_data = {0};
// Check if the device is bound
dev_if = tuya_iot_get_dev_if(frame->id);
if (!dev_if || !dev_if->bind) {
PR_ERR("dev_id: %s is not bind", frame->id);
tuya_zigbee_del_dev(frame->id);
return;
}
// Refresh device heartbeat
op_ret = tuya_iot_fresh_dev_hb(frame->id);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
}
// For simplicity, this demo uses a 1-gang smart switch to show how data is processed. In an actual project, you need to implement the mapping between ZCL data and DP data.
if ((frame->profile_id != PROFILE_ID_HA) ||
(frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID)) {
return;
}
if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
} else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
} else {
return;
}
dp_data.dpid = frame->src_endpoint;
dp_data.type = PROP_BOOL;
// Report DP
op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
if (op_ret != OPRT_OK) {
PR_ERR("dev_report_dp_json_async err: %d", op_ret);
return;
}
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.report = __my_z3_dev_report,
};
// Sub-device management callback
TY_GW_SUBDEV_MGR_CBS_S dev_mgr_cbs = {
.dp_cmd_obj = __dev_cmd_obj_cb,
};
// Register device management
TUYA_CALL_ERR_RETURN(tuya_subdev_user_sigle_type_reg(&dev_mgr_cbs, GP_DEV_ATH_1));
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
The heartbeat mechanism monitors the connection between the gateway and sub-devices. There are two methods to achieve the heartbeat check.
For the active method, configure the third-party sub-device to regularly report its version number. The application refreshes the sub-device heartbeat in the data reporting callback.
For the passive method, the SDK takes care of the heartbeat check with the following methods.
TY_Z3_DEV_CBS_S
notify
callback for initialization notification and the TY_GW_SUBDEV_MGR_CBS_S
dev_bind
callback for binding notification. In the callback, call tuya_iot_set_dev_hb_cfg
to configure the heartbeat.TY_GW_SUBDEV_MGR_CBS_S
dev_hb
callback for heartbeat checks. In this callback, read the firmware version of the sub-device.TY_Z3_DEV_CBS_S
report
callback for reporting data. In this callback, call tuya_iot_fresh_dev_hb
to refresh the sub-device heartbeat.The process of heartbeat management:
Example:
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
#define PROFILE_ID_HA 0x0104
#define ZCL_BASIC_CLUSTER_ID 0x0000
#define ZCL_ON_OFF_CLUSTER_ID 0x0006
#define ZCL_CMD_TYPE_GLOBAL 0x00
#define ZCL_CMD_TYPE_PRIVATE 0x01
#define ZCL_READ_ATTRIBUTES_COMMAND_ID 0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID 0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER 3 /* Attribute ID: 2 Bytes, Status: 1 Byte */
#define ZCL_REPORT_ATTRIBUTES_HEADER 2 /* Attribute ID: 2 Bytes */
TY_Z3_DEV_S my_z3_dev[] = {
{ "TUYATEC-nPGIPl5D", "TS0001" },
{ "TUYATEC-1xAC7DHQ", "TS0002" },
};
TY_Z3_DEVLIST_S my_z3_devlist = {
.devs = my_z3_dev,
.dev_num = CNTSOF(my_z3_dev),
};
/**
* @brief Binding result notification
*/
STATIC VOID __dev_bind_cb(CONST CHAR_T *dev_id, CONST OPERATE_RET result)
{
OPERATE_RET op_ret = OPRT_OK;
// For simplicity, irrelevant logic is omitted.
// Set heartbeat
op_ret = tuya_iot_set_dev_hb_cfg(dev_id, 120, 3, FALSE);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
}
// ...
}
/**
* @brief Heartbeat check notification
*/
STATIC VOID __dev_hb_cb(CONST CHAR_T *dev_id)
{
OPERATE_RET op_ret = OPRT_OK;
TY_Z3_APS_FRAME_S frame = {0};
USHORT_T atrr_buf[1] = { 0x0001 };
strncpy(frame.id, dev_id, SIZEOF(frame.id));
frame.profile_id = PROFILE_ID_HA;
frame.cluster_id = ZCL_BASIC_CLUSTER_ID;
frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
frame.src_endpoint = 0x01;
frame.dst_endpoint = 0xFF;
frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;
frame.msg_length = SIZEOF(atrr_buf);
frame.message = (UCHAR_T *)atrr_buf;
op_ret = tuya_zigbee_send_data(&frame);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
return;
}
}
/**
* @brief Initialization notification callback
* @note Traverse all sub-devices. If the device type is the one you registered, configure the device heartbeat.
*/
STATIC VOID __my_z3_dev_notify(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
VOID *iter = NULL;
dev_if = tuya_iot_dev_traversal(&iter);
while (dev_if) {
if (dev_if->tp == GP_DEV_ATH_1) {
op_ret = tuya_iot_set_dev_hb_cfg(dev_if->id, 120, 3, FALSE);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
}
}
dev_if = tuya_iot_dev_traversal(&iter);
}
}
/**
* @brief Data reporting notification
*/
STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
OPERATE_RET op_ret = OPRT_OK;
// For simplicity, irrelevant logic is omitted.
// Refresh device heartbeat
op_ret = tuya_iot_fresh_dev_hb(frame->id);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
}
// ...
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.report = __my_z3_dev_report,
.notify = __my_z3_dev_notify,
};
// Sub-device management callback
TY_GW_SUBDEV_MGR_CBS_S dev_mgr_cbs = {
.dev_bind = __dev_bind_cb,
.dev_hb = __dev_hb_cb,
};
// Register device management
TUYA_CALL_ERR_RETURN(tuya_subdev_user_sigle_type_reg(&dev_mgr_cbs, GP_DEV_ATH_1));
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
Remove a sub-device from the Zigbee network, which can be triggered in two ways.
TY_GW_SUBDEV_MGR_CBS_S
dev_del
callback for removing a sub-device and the dev_reset
callback for removing a sub-device and clearing data. In the callback, call tuya_zigbee_del_dev
to remove the sub-device from the Zigbee network.TY_Z3_DEV_CBS_S
leave
callback for network leaving. In this callback, call tuya_iot_gw_unbind_dev
to remove the sub-device from the Tuya Developer Platform.Example:
// ...
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
/**
* @brief Device removing callback
* @note In the callback, implement removing the sub-device from the cloud.
*/
STATIC VOID __my_z3_dev_leave(CONST CHAR_T *dev_id)
{
OPERATE_RET op_ret = OPRT_OK;
op_ret = tuya_iot_gw_unbind_dev(dev_id);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_iot_gw_unbind_dev err: %d", op_ret);
return;
}
}
/**
* @brief Device removing notification callback
* @note In the callback, implement removing the sub-device from the Zigbee network.
*/
STATIC VOID __dev_del_cb(CONST CHAR_T *dev_id, CONST GW_DELDEV_TYPE type)
{
OPERATE_RET op_ret = OPRT_OK;
op_ret = tuya_zigbee_del_dev(dev_id);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
return;
}
}
/**
* @brief Notification callback for removing device and clearing data
* @note In the callback, implement removing the sub-device from the Zigbee network.
*/
STATIC VOID __dev_reset_cb(CONST CHAR_T *dev_id, DEV_RESET_TYPE_E type)
{
OPERATE_RET op_ret = OPRT_OK;
op_ret = tuya_zigbee_del_dev(dev_id);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_del_dev err: %d", op_ret);
return;
}
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.leave = __my_z3_dev_report,
};
// Sub-device management callback
TY_GW_SUBDEV_MGR_CBS_S dev_mgr_cbs = {
.dev_del = __dev_del_cb,
.dev_reset = __dev_reset_cb,
};
// Register device management
TUYA_CALL_ERR_RETURN(tuya_subdev_user_sigle_type_reg(&dev_mgr_cbs, GP_DEV_ATH_1));
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
The Zigbee OTA Cluster is used to update the Zigbee sub-device. The SDK provides the firmware update interface and the update process, allowing you to easily implement the OTA update feature.
To deploy an OTA update, you need to upload the firmware update to the Tuya Developer Platform and verify the OTA update. For more information, see Sub-Device OTA Update.
Implementation:
TY_GW_SUBDEV_MGR_CBS_S
dev_upgrade
callback for update notification. In this callback, call tuya_zigbee_upgrade_dev
to download and install the update.TY_Z3_DEV_CBS_S
upgrade_end
callback for result notification In this callback, report the firmware version of the sub-device. The mobile app determines the update result based on the received version number.Example:
// ...
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
/**
* @brief The update notification callback
*/
STATIC VOID __dev_upgrade_cb(CONST CHAR_T *dev_id, CONST FW_UG_S *fw)
{
OPERATE_RET op_ret = OPRT_OK;
op_ret = tuya_zigbee_upgrade_dev(dev_id, fw);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_upgrade_dev err: %d", op_ret);
return;
}
return;
}
/**
* @brief The update result notification callback
* @note You need to determine the update result. If the update fails, report a failure. For a successful update, report the new firmware version number.
*/
STATIC VOID __my_z3_dev_upgrade_end(CONST CHAR_T *dev_id, INT_T rc, UCHAR_T version)
{
CHAR_T sw_ver[SW_VER_LEN+1] = {0};
if (rc != OPRT_OK) {
tuya_iot_dev_upgd_result_report(dev_id, GP_DEV_ATH_1, 4);
return;
}
// The following rules for firmware versioning are defined by Tuya. You can handle them based on your needs.
CHAR_T iver1 = (CHAR_T)(version & 0x0F);
CHAR_T iver2 = (CHAR_T)((version >> 4) & 0x03);
CHAR_T iver3 = (CHAR_T)((version >> 6) & 0x03);
snprintf(sw_ver, SIZEOF(sw_ver), "%d.%d.%d", iver3, iver2, iver1);
tuya_iot_gw_subdevice_update(dev_id, sw_ver);
return;
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.upgrade_end = __my_z3_dev_upgrade_end,
};
// Sub-device management callback
TY_GW_SUBDEV_MGR_CBS_S dev_mgr_cbs = {
.dev_upgrade = __dev_upgrade_cb,
};
// Register device management
TUYA_CALL_ERR_RETURN(tuya_subdev_user_sigle_type_reg(&dev_mgr_cbs, GP_DEV_ATH_1));
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
After startup, the application reads the attributes of the sub-device and syncs it to the cloud.
Implementation:
TY_Z3_DEV_CBS_S
notify
callback for initialization callback. In this callback, traverse all the sub-devices and send the attribute reading command to them.TY_Z3_DEV_CBS_S
report
callback for reporting data. In this callback, convert the ZCL data to the DP data and call the DP reporting interface to upload the DP data to the Tuya Developer Platform.Example:
#include "tuya_zigbee_api.h"
#include "tuya_gw_subdev_api.h"
// ...
#define PROFILE_ID_HA 0x0104
#define ZCL_BASIC_CLUSTER_ID 0x0000
#define ZCL_ON_OFF_CLUSTER_ID 0x0006
#define ZCL_CMD_TYPE_GLOBAL 0x00
#define ZCL_CMD_TYPE_PRIVATE 0x01
#define ZCL_READ_ATTRIBUTES_COMMAND_ID 0x00
#define ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID 0x01
#define ZCL_REPORT_ATTRIBUTES_COMMAND_ID 0x0A
#define ZCL_READ_ATTRIBUTER_RESPONSE_HEADER 3 /* Attribute ID: 2 Bytes, Status: 1 Byte */
#define ZCL_REPORT_ATTRIBUTES_HEADER 2 /* Attribute ID: 2 Bytes */
TY_Z3_DEV_S my_z3_dev[] = {
{ "TUYATEC-nPGIPl5D", "TS0001" },
{ "TUYATEC-1xAC7DHQ", "TS0002" },
};
TY_Z3_DEVLIST_S my_z3_devlist = {
.devs = my_z3_dev,
.dev_num = CNTSOF(my_z3_dev),
};
/**
* @brief Initialization notification callback
* @note Traverse all sub-devices. If the device type is the one you registered, send the query command to the sub-device.
*/
STATIC VOID __my_z3_dev_notify(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
VOID *iter = NULL;
dev_if = tuya_iot_dev_traversal(&iter);
while (dev_if) {
TY_Z3_APS_FRAME_S frame = {0};
USHORT_T atrr_buf[5] = { 0x0000, 0x4001, 0x4002, 0x8001, 0x5000 };
if (dev_if->tp == GP_DEV_ATH_1) {
op_ret = tuya_iot_set_dev_hb_cfg(dev_if->id, 120, 3, FALSE);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_set_dev_hb_cfg err: %d", op_ret);
}
}
// For simplicity, this demo uses a 1-gang smart switch to show command sending. The logic is hard coded. In an actual project, you need to process it as required.
strncpy(frame.id, dev_if->id, SIZEOF(frame.id));
frame.profile_id = PROFILE_ID_HA;
frame.cluster_id = ZCL_ON_OFF_CLUSTER_ID;
frame.cmd_type = ZCL_CMD_TYPE_GLOBAL;
frame.src_endpoint = 0x01;
frame.dst_endpoint = 0xff;
frame.cmd_id = ZCL_READ_ATTRIBUTES_COMMAND_ID;
frame.msg_length = SIZEOF(atrr_buf);
frame.message = (UCHAR_T *)atrr_buf;
op_ret = tuya_zigbee_send_data(&frame);
if (op_ret != OPRT_OK) {
PR_ERR("tuya_zigbee_send_data err: %d", op_ret);
return;
}
dev_if = tuya_iot_dev_traversal(&iter);
}
}
/**
* @brief Data reporting notification
* @note You need to convert ZCL data into DP data, and then report the DP data to the cloud. Meanwhile, refresh the sub-device heartbeat to maintain its online status.
*/
STATIC VOID __my_z3_dev_report(TY_Z3_APS_FRAME_S *frame)
{
OPERATE_RET op_ret = OPRT_OK;
DEV_DESC_IF_S *dev_if = NULL;
TY_OBJ_DP_S dp_data = {0};
// Check if the device is bound
dev_if = tuya_iot_get_dev_if(frame->id);
if (!dev_if || !dev_if->bind) {
PR_ERR("dev_id: %s is not bind", frame->id);
tuya_zigbee_del_dev(frame->id);
return;
}
// Refresh device heartbeat
op_ret = tuya_iot_fresh_dev_hb(frame->id);
if (op_ret != OPRT_OK) {
PR_WARN("tuya_iot_fresh_dev_hb err: %d", op_ret);
}
// For simplicity, this demo uses a 1-gang smart switch to show how data is processed. In an actual project, you need to implement the mapping between ZCL data and DP data.
if ((frame->profile_id != PROFILE_ID_HA) ||
(frame->cluster_id != ZCL_ON_OFF_CLUSTER_ID)) {
return;
}
if (frame->cmd_id == ZCL_REPORT_ATTRIBUTES_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_REPORT_ATTRIBUTES_HEADER+1];
} else if (frame->cmd_id == ZCL_READ_ATTRIBUTES_RESPONSE_COMMAND_ID) {
dp_data.value.dp_bool = frame->message[ZCL_READ_ATTRIBUTER_RESPONSE_HEADER+1];
} else {
return;
}
dp_data.dpid = frame->src_endpoint;
dp_data.type = PROP_BOOL;
// Report DP
op_ret = dev_report_dp_json_async(frame->id, &dp_data, 1);
if (op_ret != OPRT_OK) {
PR_ERR("dev_report_dp_json_async err: %d", op_ret);
return;
}
}
STATIC OPERATE_RET user_zigbee_custom_init(VOID)
{
OPERATE_RET rt = OPRT_OK;
// Zigbee callback
TY_Z3_DEV_CBS_S z3_dev_cbs = {
.report = __my_z3_dev_report,
.notify = __my_z3_dev_notify,
};
// Register allowlist
TUYA_CALL_ERR_RETURN(tuya_zigbee_custom_dev_mgr_init(&my_z3_devlist, &z3_dev_cbs));
return OPRT_OK;
}
int main(int argc, char **argv)
{
ty_cJSON *app_cfg = ty_cJSON_CreateObject();
if (app_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(app_cfg, "storage_path", "./");
ty_cJSON_AddStringToObject(app_cfg, "cache_path", "/tmp/");
// Set storage path
TUYA_CALL_ERR_RETURN(tuya_set_config(app_cfg));
ty_cJSON *zb_cfg = ty_cJSON_CreateObject();
if (zb_cfg == NULL) {
return OPRT_CJSON_GET_ERR;
}
ty_cJSON_AddStringToObject(zb_cfg, "dev_name", "/dev/ttyS2");
ty_cJSON_AddNumberToObject(zb_cfg, "cts", 1);
ty_cJSON_AddNumberToObject(zb_cfg, "thread_mode", 1);
// Initialize third-party sub-device service
TUYA_CALL_ERR_RETURN(user_zigbee_custom_init());
// Initialize Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_init(zb_cfg));
// Start Zigbee service
TUYA_CALL_ERR_RETURN(tuya_zigbee_svc_start(zb_cfg));
// ...
return 0;
}
/**
* Usage guide:
* 1. Copy the content and save it to the zigbee_app.c file.
* 2. In the macro definition TEST_UART_NAME, change the serial device to the one connected to the Zigbee module.
* 3. Compile with the cross-compilation toolchain.
* $ gcc zigbee_app.c -o zigbee_app -lpthread
* 4. Copy zigbee_app to the target board and run it. If the module version is read, it means the communication works fine. Otherwise, check the serial port.
* Example of successful output:
* Send data[5:5]: 1a c0 38 bc 7e
* Recv data[128:7]: 1a c1 02 0b 0a 52 7e
* Send data[4:4]: 80 70 78 7e
* Ncp reset success.
* Send data[8:8]: 00 42 21 a8 53 dd 4f 7e
* Recv data[128:11]: 01 42 a1 a8 53 28 15 d7 c1 bf 7e
* Send data[4:4]: 81 60 59 7e
* Ncp set version success.
* Send data[9:9]: 11 43 21 57 54 39 51 2b 7e
* Recv data[128:14]: 12 43 a1 57 54 39 15 b3 59 9c 5a 7a 1c 7e
* Send data[4:4]: 82 50 3a 7e
* COO manuId:0001, version:1.0.8.
*/
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <termios.h>
#include <sys/ttydefaults.h>
#define TEST_UART_NAME "/dev/ttyS1"
#define TEST_UART_SPEED 115200
#define TEST_UART_STIO_BIT 1
#define TEST_UART_RTSCTS TRUE
#define TEST_UART_FLOWCTR TRUE
#define UART_DEBUG_PRINTF 1
typedef unsigned int bool;
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
static const struct { int bps; speed_t posixBaud; } baudTable[] =
{ { 600, B600 },
{ 1200, B1200 },
{ 2400, B2400 },
{ 4800, B4800 },
{ 9600, B9600 },
{ 19200, B19200 },
{ 38400, B38400 },
{ 57600, B57600 },
{ 115200, B115200 },
{ 230400, B230400 } };
#define BAUD_TABLE_SIZE (sizeof(baudTable) / sizeof(baudTable[0]) )
// Define CRTSCTS for both ingoing and outgoing hardware flow control
// Try to resolve the numerous aliases for the bit flags
#if defined(CCTS_OFLOW) && defined(CRTS_IFLOW) && !defined(__NetBSD__)
#undef CRTSCTS
#define CRTSCTS (CCTS_OFLOW | CRTS_IFLOW)
#endif
#if defined(CTSFLOW) && defined(RTSFLOW)
#undef CRTSCTS
#define CRTSCTS (CTSFLOW | RTSFLOW)
#endif
#ifdef CNEW_RTSCTS
#undef CRSTCTS
#define CRTSCTS CNEW_RTSCTS
#endif
#ifndef CRTSCTS
#define CRTSCTS 0
#endif
#define ASCII_XON 0x11
#define ASCII_XOFF 0x13
// Define the termios bit fields modified by ezspSerialInit
// (CREAD is omitted as it is often not supported by modern hardware)
#define CFLAG_MASK (CLOCAL | CSIZE | PARENB | HUPCL | CRTSCTS)
#define IFLAG_MASK (IXON | IXOFF | IXANY | BRKINT | INLCR | IGNCR | ICRNL \
| INPCK | ISTRIP | IMAXBEL)
#define LFLAG_MASK (ICANON | ECHO | IEXTEN | ISIG)
#define OFLAG_MASK (OPOST)
static int g_uart_fd = -1;
static char g_ezspSeq = 0;
static char g_ashFramNum = 0;
static char g_ashAckNum = 0;
static char gcur_ezspSeq = 0;
static unsigned short halCommonCrc16(unsigned char newByte, unsigned short prevResult);
int ezspSetupSerialPort(unsigned char *uart_name, unsigned int bps, unsigned char stopBits, bool rtsCts, bool flowControl)
{
int i = 0;
struct termios tios, checkTios;
speed_t baud;
// Setting this up front prevents us from closing an unset serial port FD
// when breaking on error.
g_uart_fd = -1;
while (TRUE) { // break out of while on any error
for (i = 0; i < BAUD_TABLE_SIZE; i++) {
if (baudTable[i].bps == bps) {
break;
}
}
if (i < BAUD_TABLE_SIZE) {
baud = baudTable[i].posixBaud;
} else {
printf("Invalid baud rate %d bps\r\n", bps);
break;
}
if ((stopBits != 1) && (stopBits != 2)) {
printf("Invalid number of stop bits:%d.\r\n", stopBits);
break;
}
g_uart_fd = open(uart_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (g_uart_fd == -1) {
printf("Serial port open failed:%s\r\n", strerror(errno));
break;
}
tcflush(g_uart_fd, TCIOFLUSH); // flush all input and output data
//#define ANDROID_PLATEM
#if 0//#ifdef ANDROID_PLATEM
fcntl(*serialPortFdReturn, F_SETFL, 04000);
#else
fcntl(g_uart_fd, F_SETFL, FNDELAY);
#endif
tcgetattr(g_uart_fd, &tios); // get current serial port options
cfsetispeed(&tios, baud);
cfsetospeed(&tios, baud);
tios.c_cflag |= CLOCAL; // ignore modem inputs
tios.c_cflag |= CREAD; // receive enable (a legacy flag)
tios.c_cflag &= ~CSIZE; // 8 data bits
tios.c_cflag |= CS8;
tios.c_cflag &= ~PARENB; // no parity
if (stopBits == 1) {
tios.c_cflag &= ~CSTOPB;
} else {
tios.c_cflag |= CSTOPB;
}
if (flowControl && rtsCts) {
tios.c_cflag |= CRTSCTS;
} else {
tios.c_cflag &= ~CRTSCTS;
}
tios.c_iflag &= ~(BRKINT | INLCR | IGNCR | ICRNL | INPCK | ISTRIP | IMAXBEL | IXON | IXOFF | IXANY);
if (flowControl && !rtsCts) {
tios.c_iflag |= (IXON | IXOFF); // SW flow control
} else {
tios.c_iflag &= ~(IXON | IXOFF);
}
tios.c_lflag &= ~(ICANON | ECHO | IEXTEN | ISIG); // raw input
tios.c_oflag &= ~OPOST; // raw output
memset(tios.c_cc, _POSIX_VDISABLE, NCCS); // disable all control chars
#if 0//#ifdef ANDROID_PLATEM
tios.c_cc[VSTART] = ('q'&037); // define XON and XOFF
tios.c_cc[VSTOP] = ('s'&037);
#else
tios.c_cc[VSTART] = CSTART; // define XON and XOFF
tios.c_cc[VSTOP] = CSTOP;
#endif
tios.c_cc[VMIN] = 1;
tios.c_cc[VTIME] = 0;
memset(checkTios.c_cc, _POSIX_VDISABLE, NCCS);
tcsetattr(g_uart_fd, TCSAFLUSH, &tios); // set EZSP serial port options
tcgetattr(g_uart_fd, &checkTios); // and read back the result
// Verify that the fields written have the right values
i = (tios.c_cflag ^ checkTios.c_cflag) & CFLAG_MASK;
if (i) {
// Try again since macOS mojave seems to not have hardware flow control enabled
tios.c_cflag &= ~CRTSCTS;
tcsetattr(g_uart_fd, TCSAFLUSH, &tios); // set EZSP serial port options
tcgetattr(g_uart_fd, &checkTios); // and read back the result
i = (tios.c_cflag ^ checkTios.c_cflag) & CFLAG_MASK;
if (i) {
printf("Termios cflag(s) in error: 0x%04X\r\n", i);
break;
}
}
i = (tios.c_iflag ^ checkTios.c_iflag) & IFLAG_MASK;
if (i) {
printf("Termios c_iflag(s) in error: 0x%04X\r\n", i);
break;
}
i = (tios.c_lflag ^ checkTios.c_lflag) & LFLAG_MASK;
if (i) {
printf("Termios c_lflag(s) in error: 0x%04X\r\n", i);
break;
}
i = (tios.c_oflag ^ checkTios.c_oflag) & OFLAG_MASK;
if (i) {
printf("Termios c_oflag(s) in error: 0x%04X\r\n", i);
break;
}
for (i = 0; i < NCCS; i++) {
if (tios.c_cc[i] != checkTios.c_cc[i]) {
break;
}
}
if (i != NCCS) {
printf("Termios c_cc(s) in error: 0x%04X\r\n", i);
break;
}
if ( (cfgetispeed(&checkTios) != baud)|| (cfgetospeed(&checkTios) != baud)) {
printf("Could not set baud rate to %d bps\r\n", bps);
break;
}
return 0;
}
if (g_uart_fd != -1) {
close(g_uart_fd);
g_uart_fd = -1;
}
return -1;
}
static void tuya_uart_send(unsigned char *buf, unsigned int len)
{
int count = 0;
int i = 0;
count = write(g_uart_fd, buf, len);
#ifdef UART_DEBUG_PRINTF
printf( "Send data[%d:%d]: ", len, count);
for(i = 0; i < len; i++) {
printf("%02x ", buf[i]&0xFF);
}
printf("\n");
#endif
}
static int tuya_uart_recv(unsigned char *buf, unsigned int len)
{
int read_len = 0;
int i = 0;
read_len = read(g_uart_fd, buf, len);
#ifdef UART_DEBUG_PRINTF
if(read_len > 0) {
printf("Recv data[%d:%d]: ", len, read_len);
for(i = 0; i < read_len; i++){
printf("%02x ", buf[i]&0xFF);
}
printf("\n");
}
#endif
return read_len;
}
static void ashCrc(unsigned char *buf, int len, unsigned short *crcData)
{
unsigned short crcDataTmp = 0xFFFF;
int i = 0;
for(i=0; i<len; i++) {
crcDataTmp = halCommonCrc16(buf[i], crcDataTmp);
}
*crcData = crcDataTmp;
}
// Define constants for the LFSR in ashRandomizeBuffer()
#define LFSR_POLY 0xB8 // polynomial
#define LFSR_SEED 0x42 // initial value (seed)
static unsigned char ashRandomizeArray(unsigned char seed, unsigned char *buf, unsigned char len)
{
if (seed == 0) {
seed = LFSR_SEED;
}
while (len--) {
*buf++ ^= seed;
seed = (seed & 1) ? ((seed >> 1) ^ LFSR_POLY) : (seed >> 1);
}
return seed;
}
static unsigned short halCommonCrc16(unsigned char newByte, unsigned short prevResult)
{
prevResult = ((unsigned short) (prevResult >> 8)) | ((unsigned short) (prevResult << 8));
prevResult ^= newByte;
prevResult ^= (prevResult & 0xff) >> 4;
prevResult ^= (unsigned short) (((unsigned short) (prevResult << 8)) << 4);
prevResult ^= ((unsigned char) (((unsigned char) (prevResult & 0xff)) << 5))
| ((unsigned short) ((unsigned short) ((unsigned char) (((unsigned char) (prevResult & 0xff)) >> 3)) << 8));
return prevResult;
}
static void paraRandom(unsigned char *inoutStr, int inLen)
{
ashRandomizeArray(0, inoutStr, inLen);
}
static void get_ncp_info_cmd(unsigned char *buf, unsigned int *len)
{
unsigned char cmdBuf[9] = {0x00, 0x00, 0x21, 0x57, 0x54, 0x39, 0x00, 0x00, 0x7E};
unsigned short crcData = 0;
unsigned char ezspSeqTmp = 0;
cmdBuf[0] = ((g_ashFramNum & 0x07) << 4) | (g_ashFramNum & 0x07);
ezspSeqTmp = g_ezspSeq;
paraRandom(&ezspSeqTmp, 1);
cmdBuf[1] = ezspSeqTmp;
ashCrc(cmdBuf, 6, &crcData);
cmdBuf[6] = ((crcData >> 8)& 0xff);
cmdBuf[7] = (crcData & 0xff);
memcpy(buf, cmdBuf, 9);
*len = 9;
gcur_ezspSeq = g_ezspSeq;
g_ashFramNum++;
g_ezspSeq++;
}
static void get_ncp_reset_cmd(unsigned char *buf, unsigned int *len)
{
unsigned char cmdBuf[9] = {0x1A, 0xC0, 0x00, 0x00, 0x7E};
unsigned short crcData = 0;
ashCrc(&cmdBuf[1], 1, &crcData);
cmdBuf[2] = ((crcData >> 8)& 0xff);
cmdBuf[3] = (crcData & 0xff);
memcpy(buf, cmdBuf, 5);
*len = 5;
}
static void get_ncp_setver_cmd(unsigned char *buf, unsigned int *len)
{
unsigned char cmdBuf[9] = {0x00, 0x00, 0x21, 0xa8, 0x53, 0x00, 0x00, 0x7E};
unsigned short crcData = 0;
unsigned char ezspSeqTmp = 0;
cmdBuf[0] = ((g_ashFramNum & 0x07) << 4) | (g_ashFramNum & 0x07);
ezspSeqTmp = g_ezspSeq;
paraRandom(&ezspSeqTmp, 1);
cmdBuf[1] = ezspSeqTmp;
ashCrc(cmdBuf, 5, &crcData);
cmdBuf[5] = ((crcData >> 8)& 0xff);
cmdBuf[6] = (crcData & 0xff);
memcpy(buf, cmdBuf, 8);
*len = 8;
gcur_ezspSeq = g_ezspSeq;
g_ashFramNum++;
g_ezspSeq++;
}
static void get_ack_cmd(unsigned char *buf, unsigned int *len)
{
unsigned char cmdBuf[4] = {0x00, 0x00, 0x00, 0x7E};
unsigned short crcData = 0;
cmdBuf[0] = ((0x1<<7)| (g_ashAckNum & 0x07));
ashCrc(cmdBuf, 1, &crcData);
cmdBuf[1] = ((crcData >> 8)& 0xff);
cmdBuf[2] = (crcData & 0xff);
memcpy(buf, cmdBuf, 4);
*len = 4;
g_ashAckNum++;
}
static void tuya_send_ack(void)
{
unsigned char send_buf[128]={0};
unsigned int send_len = 0;
get_ack_cmd(send_buf,&send_len);
tuya_uart_send(send_buf,send_len);
}
static void paraNcpData(unsigned char *inAsh, int inAshLen, unsigned char *outEzsp, int *outEzspLen)
{
*outEzspLen = inAshLen-1-2-1; // 1byte ASH header + 2byte CRC + 1byte tail
memcpy(outEzsp, inAsh+1, *outEzspLen);
paraRandom(outEzsp, *outEzspLen);
}
static void para_ncp_info(unsigned short *manuId, unsigned short *verNUm,unsigned char *inAsh, int inAshLen)
{
unsigned char outEzsp[32] = {0};
int outEzspLen = 0;
paraNcpData(inAsh, inAshLen, outEzsp, &outEzspLen);
*manuId = ((outEzsp[7]&0xFF)<<8) | (outEzsp[6] & 0xFF);
*verNUm = ((outEzsp[9]&0xFF)<<8) | (outEzsp[8] & 0xFF);
}
void uart_recv_hand(bool is_skip, char *data_buf, int *data_len)
{
unsigned char recv_cmd[128]={0};
unsigned char total_recv_cmd[128]={0};
int read_result = 0;
int total_len = 0;
int i = 0;
int data_index = 0;
sleep(1);
do {
read_result = tuya_uart_recv(recv_cmd, 128);
if(read_result > 0){
memcpy(total_recv_cmd+total_len, recv_cmd, read_result);
total_len += read_result;
memset(recv_cmd, 0, 128);
}
} while(read_result > 0);
if (FALSE == is_skip && total_len > 0) {
if (total_len <= 4) // is ack
return;
for (i = 0; i < total_len; i++) {
if (0x7E == total_recv_cmd[i]) {
break;
}
}
if (i == 3) { // skip ack
data_index += 4;
}
memcpy(data_buf, total_recv_cmd+data_index, total_len-data_index);
*data_len = total_len-data_index;
}
if (total_len > 4) {
tuya_send_ack();
}
}
static int ncp_reset(void)
{
unsigned char send_cmd[20]={0};
unsigned char recv_cmd[128]={0};
unsigned int send_len = 0;
unsigned int recv_len = 0;
int send_num = 0;
int recv_num = 0;
do {
get_ncp_reset_cmd(send_cmd,&send_len);
tuya_uart_send(send_cmd, send_len);
recv_num = 0;
do {
usleep(10*1000);
uart_recv_hand(FALSE, recv_cmd, &recv_len);
recv_num++;
} while ((recv_len <= 0) && (recv_num <= 200));
send_num++;
} while ((recv_len <= 0) && (send_num <= 3));
if(recv_len > 0)
return 0;
else
return -1;
}
static int ncp_ver_set(void)
{
unsigned char send_cmd[20] = {0};
unsigned char recv_cmd[128] = {0};
unsigned int send_len = 0;
unsigned int recv_len = 0;
int send_num = 0;
int recv_num = 0;
do {
get_ncp_setver_cmd(send_cmd, &send_len);
tuya_uart_send(send_cmd, send_len);
recv_num = 0;
do {
usleep(10*1000);
uart_recv_hand(FALSE, recv_cmd, &recv_len);
recv_num++;
} while((recv_len <= 0) && (recv_num <= 200));
send_num++;
} while ((recv_len <= 0) && (send_num <= 3));
if (recv_len > 0)
return 0;
else
return -1;
}
static int check_zigbee_is_ok(void)
{
unsigned char send_cmd[20]={0};
unsigned char recv_cmd[128]={0};
unsigned int send_len = 0;
unsigned int recv_len = 0;
int read_result = 0;
unsigned short manuId = 0;
unsigned short verNUm = 0;
unsigned char ver_str[11]={0};
int ret = 0;
ret = ncp_reset();
if (ret < 0) {
printf("Ncp reset error.\n");
return -1;
}else{
printf("Ncp reset success.\n");
}
ret = ncp_ver_set();
if (ret < 0) {
printf("Ncp set version error.\n");
return -1;
} else {
printf("Ncp set version success.\n");
}
get_ncp_info_cmd(send_cmd,&send_len);
tuya_uart_send(send_cmd, send_len);
uart_recv_hand(FALSE, recv_cmd, &recv_len);
if (recv_len > 0) {
para_ncp_info(&manuId, &verNUm, recv_cmd, recv_len);
sprintf(ver_str, "%d.%d.%d", ((verNUm>>12) & 0xF),((verNUm>>8) & 0xF),(verNUm&0xFF));
printf("COO manuId:%04x, version:%s.\n", manuId, ver_str);
} else {
printf("COO error.\n");
}
return 0;
}
int main(char argc, char *argv[])
{
int ret = 0;
// open UART
ret = ezspSetupSerialPort(TEST_UART_NAME, TEST_UART_SPEED, TEST_UART_STIO_BIT, TEST_UART_RTSCTS, TEST_UART_FLOWCTR);
if (0 == ret) {
check_zigbee_is_ok();
} else {
printf("ezspSetupSerialPort err: %d\n", ret);
}
return ret;
}
Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback