应用开发

更新时间:2023-01-29 07:32:07下载pdf

本文档通过介绍如何创建新工程以及对开发框架和文件函数等的说明,使开发者能够了解涂鸦 Zigbee SDK 的启动流程,带领开发者基于涂鸦提供的 SDK 对模组进行二次开发。

创建工程

打开获取到的开发资料包,在 software\TuyaOS\apps 目录下新建一个 EFR32MG21_switch1_io_XX 的工程文件夹。该文件夹就是工程名,也是上传固件时使用的固件标识名。

在编译生成固件后上传到云平台时会出现固件上传失败的情况,可能是因为云平台已经有了该固件标识名,所以在创建工程时,可将文件夹设为不同的工程名称,避免出现上传固件失败的问题。

应用开发

固件命名可以按照:芯片平台+产品类型+产品特性+厂商标识名/个人姓名缩写,也可根据个人喜好来命名。如:固件名称可以命名为 EFR32MG21_switch1_io_XXEF32MG21 表示使用的是 EFR32MG 芯片进行开发的,switch1 表示一路开关,io 表示通过 IO 口拉高拉低来控制开关,xx 为名字缩写或其他。

在该文件夹下,再新建 includesrc 两个文件夹。include 文件夹用来存放工程中所用到的头文件,src文件夹用来存放工程中所用的源文件。可以在 includesrc 文件夹下创建新的文件夹,对不同功能的 .c.h 文件进行分类管理。

以 SDK 中的 tuyaos_demo_zg_light2 为例,该工程的目录树如下:

apps                                 //存放应用工程
  └── EFR32MG21_switch1_io_ab        //新建的工程
      ├── include                    //用到的头文件
      │   ├── app_cluster_color_control.h
      │   ├── app_cluster_level.h
      │   ├── app_cluster_on_off.h
      │   ├── app_common.h
      │   ├── app_config.h
      │   ├── app_light_control.h
      │   ├── app_light_tools.h     
      │   └── tuya_sdk_callback.h   
      ├── src                        //创建的源文件
      │   ├── app_cluster_color_control.c
      │   ├── app_cluster_level.c
      │   ├── app_cluster_on_off.c
      │   ├── app_common.c
      │   ├── app_config.c
      │   ├── app_light_control.c
      │   ├── app_light_tools.c
      │   └── tuya_sdk_callback.c     //应用回调接口集文件

TuyaOS 开发框架介绍

目录结构

TuyaOS                                
  └── apps        
      ├── tuyaos_demo_zg_light2                   
      │   ├── app_cluster_color_control.h
      ├── commponents
      ├── docs
      ├── include
      │   ├── adapater
      │   │   ├── adc
      │   │   ├── gpio
      │   │   ├── i2c
      │   │   ├── pwm
      │   │   ├── spi
      │   │   ├── timer
      │   │   ├── uart
      │   │   ├── zigbee
      │   ├── base  
      │   └── components 
      │       ├─tal_driver
      │       ├─tal_system
      │       ├─tal_system_service
      │       └─tal_zigbee
      ├── libs    
      ├── tools                          
      │   ├── bootloader
      │   ├── commander
      │   ├── ld
      │   ├── scripts
      │   ├── templates
      ├── build_app.py       
目录名称 作用
apps 包含各种品类的示例代码 demo,新建工程只需在该目录下新建文件夹并添加相关代码即可
components 包含涂鸦开发的各类开源组件,用户添加组件只需新建目录即可,然后将组件代码放在此文件夹下
docs 包含相关说明文档
include 包含 TuyaOS 开发框架各类 API 接口文件
lib 包含 TuyaOS 开发框架依赖的库文件
tools 包含应用在构建和编译过程中所用到的相关工具和脚本

上电流程说明

以下是上电后 SDK 运行完整流程图和用到的主要函数。您可以参考理解 SDK 的完整运行流程,以便更好地开发应用代码。

应用开发

上电流程中,需要关注 tuya_init_firsttuya_init_secondtuya_init_thirdtuya_init_lasttuya_main_loop 这几个函数。下面针对这几个函数进行相关介绍。

tuya_init_first() 函数

在上电后,对时钟等必要外设进行初始化之后就会调用此 API ,用户可以在此 API 中初始化自己的相关外设。

/**
 * @brief Generally used for peripheral initialization
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_init_first(VOID_T)
{
    TAL_UART_CFG_T uart_cfg = {
        .rx_buffer_size = 256,
        .open_mode = 0,
        {
            .baudrate = 115200,
            .parity = TUYA_UART_PARITY_TYPE_NONE,
            .databits = TUYA_UART_DATA_LEN_8BIT,
            .stopbits = TUYA_UART_STOP_LEN_1BIT,
            .flowctrl = TUYA_UART_FLOWCTRL_NONE,
        }
    };
    tal_uart_init(USER_UART0, &uart_cfg);

    app_button_init();

#if (ENABLE_TAL_LOG == 1)
    tal_log_create_manage_and_init(TAL_LOG_LEVEL_DEBUG, 128, dev_uart_output);
#endif

    TAL_PR_DEBUG("/*********first init*********/");
    return OPRT_OK;
} 

tuya_init_second() 函数

涂鸦开发框架进行内部必要组件初始化完成后调用此 API ,需要用户在此函数中注册和初始化 Zigbee 相关功能,包括固件信息、ep 注册、 Zigbee 节点配置、入网配置、心跳设置等。

/**
 * @brief Generally used for register zigbee device
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_init_second(VOID_T)
{
    //initialize firmware infomation
    TAL_PR_DEBUG("Application version:%d", FIRMWARE_VER);

    //creat software timer
    tal_sw_timer_create(app_button_scan_cb, NULL, &etimer_key_scan);
    tal_sw_timer_create(app_onoff_count_down_timer_cb, NULL, &etimer_countdown);
    tal_sw_timer_create(__app_network_join_start_delay_cb, NULL, &etimer_join_start_delay);
    tal_sw_timer_create(__app_clr_rst_cnt_delay_cb, NULL, &etimer_clr_rst_cnt_delay);

    //register zigbee endpoint
    tal_zg_endpoint_register(dev_endpoint_desc, GET_ARRAY_LEN(dev_endpoint_desc));
    TAL_PR_DEBUG("identify init ret:%d", tal_zg_identify_init());
    tal_zll_target_touchlink_init();
    tal_zll_target_rssi_threshold_set(-90);

    //zigbee node configuration
    __app_router_node_init();

    //zigbee joining network configuration
    TAL_ZG_JOIN_CFG_T join_config = {
        .auto_join_power_on_flag = TRUE,
        .auto_join_remote_leave_flag = TRUE,
        .join_timeout = 20000,
    };
    tal_zg_join_config(&join_config);

    //time synchronization period setting
    tal_time_sync_period_set(60 * 1000);
    __app_power_on_check_rst_cnt_proc();
    TAL_PR_DEBUG("/*********1.0.0 second init*********/");

    return OPRT_OK;
}     

tuya_init_third() 函数

在原厂 SDK 进行协议栈和相关资源初始化完成后会调用此 API ,例如设置 Zigbee 的发射功率以及原厂使用的 token,必须要在此函数或者之后的流程中才可以使用。此 API 在进入产测模式之前,用户可以设置一些特殊功能。

/**
 * @brief Generally used for initialization before manufacturing test
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_init_third(VOID_T)
{
    TAL_PR_DEBUG("/*********third init*********/");

    OPERATE_RET ret;

    ret = app_light_init();
    if (ret != OPRT_OK)
    {
        TAL_PR_DEBUG("light init ERROR");
    }
    return OPRT_OK;
}     

tuya_init_last() 函数

只有不进入产测模式才会进入到此 API 中,同时此 API 也是进入 while(1) 前的回调函数。

/**
 * @brief Generally used for initialization after manufacturing test
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_init_last(VOID_T)
{
    UINT8_T version;
    
    tal_uart_deinit(USER_UART0);
    TAL_UART_CFG_T uart_cfg = {
        .rx_buffer_size = 256,
        .open_mode = 0,
        {
            .baudrate = 115200,
            .parity = TUYA_UART_PARITY_TYPE_NONE,
            .databits = TUYA_UART_DATA_LEN_8BIT,
            .stopbits = TUYA_UART_STOP_LEN_1BIT,
            .flowctrl = TUYA_UART_FLOWCTRL_NONE,
        }
    };
    tal_uart_init(USER_UART0, &uart_cfg);

    tal_zg_read_attribute(TUYA_PRIMARY_ENDPOINT,
                          CLUSTER_BASIC_CLUSTER_ID,
                          ATTR_APPLICATION_VERSION_ATTRIBUTE_ID,
                          (VOID_T *)&version,
                          sizeof(version));

	// tal_heartbeat_period_set(30*000);
    // tal_heartbeat_type_set(HEARTBEAT_APP_VERSION);
    TAL_PR_DEBUG("/*********last init %d*********/", version);
    return OPRT_OK;
}

tuya_main_loop() 函数

此函数是放在 while(1) 主循环中的,用户可以在此 API 中添加更多需要 loop 的函数,但是强烈不建议在此 API 中进行阻塞或者是添加耗时较长的处理函数。

tuya_main_loop() 主要用于用户侧添加调试、验证性的操作,需谨慎使用。此回调接口占用过多时间片会影响整个系统框架的稳定性!

/**
 * @brief user-defined callback interface in main loop.do not block!!!
 * 
 * @return OPERATE_RET 
 */
OPERATE_RET tuya_main_loop(VOID_T)
{
    return OPRT_OK;
}

文件介绍

常用头文件介绍

文件名称 功能
tuya_tools.h Tuya IoT OS 通用工具函数接口
tkl_system.h 提供统一适配系统基础接口,比如获取系统重启原因和获取系统运行的 ticket 等
tkl_memory.h 封装了内存管理接口
tuya_error_code.h 涂鸦对一些错误类型的定义
tuya_cloud_types.h 对参数类型的封装,对变量、函数进行类型定义或修饰时应调用这里的函数
tkl_gpio.h GPIO kernel layer abstract layer
tal_gpio.h GPIO abstract layer

tuya_sdk_callback.c 文件

tuya_sdk_callback.c 文件是用户基于 TuyaOS Zigbee SDK 实现业务开发的回调接口集。需要用户根据实际业务场景的需要,实现相关回调、虚函数的重写、应用逻辑功能代码的填充等。一些重要的回调函数/虚函数列表如下:

函数名称 函数功能
tuya_init_first() 用户进行硬件初始化
tuya_init_second() 用于 Zigbee 相关初始化、如 Zigbee 设备注册等
tuya_init_third() 进入产测前回调接口
tuya_init_last() 退出产测后回调接口
tuya_main_loop() 主循环回调接口,谨慎使用,不可阻塞!
tal_zcl_general_msg_recv_callback() ZCL general command 接收回调接口
tal_zcl_specific_msg_recv_callback() ZCL specific command 接收回调接口
tal_zcl_nwk_status_changed_callback() Zigbee 网络状态改变回调接口
tal_zg_scene_save_callback() Add scene/store scene 命令回调接口
tal_zg_scene_recall_callback() Recall scene 命令回调接口
tal_zg_sdd_group_callback() 添加群组命令回调接口
tal_zg_remove_group_callback() 移除群组命令回调接口
tal_zg_reset_factory_default_callback() 恢复出厂设置命令回调接口

ZCL specific command 接收回调函数

/**
 * @brief specific message receive callback
 * 
 * @param msg 
 * @return TAL_MSG_RET_E 
 */
TAL_MSG_RET_E tal_zcl_specific_msg_recv_callback(TAL_ZCL_MSG_T *msg)
{
    TAL_PR_DEBUG("app spec msg cb: cluster 0x%02x, cmd 0x%02x", msg->cluster, msg->command);

    ZIGBEE_CMD_T app_cmd_type = ZIGBEE_CMD_SINGLE;
    if (msg->mode == ZG_UNICAST_MODE)
    {
        app_cmd_type = ZIGBEE_CMD_SINGLE;
        TAL_PR_DEBUG("receive single message");
    }
    else
    {
        app_cmd_type = ZIGBEE_CMD_GROUP;
        TAL_PR_DEBUG("receive group message");
    }

    switch (msg->cluster)
    {
    case CLUSTER_ON_OFF_CLUSTER_ID:
    {
        //handle on/off cluster command
        app_onoff_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;
    case CLUSTER_LEVEL_CONTROL_CLUSTER_ID:
    {
        //handle level control cluster command
        app_level_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;

    case CLUSTER_COLOR_CONTROL_CLUSTER_ID:
    {
        //handle color control cluster command
        app_color_cluster_handler(msg->command, msg->payload, msg->length, app_cmd_type);
    }
    break;
    case CLUSTER_PRIVATE_TUYA_CLUSTER_ID:
    {
        break;
    }
    default:
        break;
    }

    return ZCL_MSG_RET_SUCCESS;
}

Add scene/store scene 命令回调函数

/**
 * @brief save scene callback(add scene and store scene)
 * 
 * @param ep_id 
 * @param scene_id 
 * @param group_id 
 * @param data 
 * @return VOID_T 
 */
VOID_T tal_zg_scene_save_callback(UINT8_T ep_id, UINT8_T scene_id, UINT16_T group_id, TAL_SCENE_DATA_T *data)
{
    TAL_PR_DEBUG("scene save: ep %d, sce %d, gp 0x%02x, type %d", ep_id, scene_id, group_id, data->type);

    UINT8_T onoff;
    LIGHT_MODE_E mode;
    UINT16_T bright;
    UINT16_T temper;

    app_light_ctrl_data_switch_get((UINT8_T *)&onoff);
    app_light_ctrl_data_mode_get((LIGHT_MODE_E *)&mode);
    app_light_ctrl_data_bright_get(&bright);
    app_light_ctrl_data_temper_get(&temper);

    data->pdata[0] = onoff;
    data->pdata[1] = mode;

    data->pdata[2] = 0;
    data->pdata[3] = 0;

    data->pdata[4] = 0;
    data->pdata[5] = 0;

    data->pdata[6] = 0;
    data->pdata[7] = 0;

    data->pdata[8] = bright >> 8;
    data->pdata[9] = bright;

    data->pdata[10] = temper >> 8;
    data->pdata[11] = temper;

    data->len = 12;

    TAL_PR_DEBUG("ADD SCENE DATA LEN = 0x%x", data->len);
    TAL_PR_DEBUG("ADD SCENE DATA : ");
    // for (UINT8_T i = 0; i < data->len; i++) {
    //     TAL_PR_DEBUG("0x%x ", data->pdata[i]);
    // }
}

Recall scene 命令回调函数

  /**
   * @brief
   *
   * @param str
   * @return VOID_T
   */
/**
 * @brief recall scene callback
 *
 * @param[in]   ep_id:     endpoint id
 * @param[in]   scene_id:  scene id
 * @param[in]   group_id:  group id
 * @param[in]   time100ms: scene transition time(bat:100ms)
 * @param[in]   data:      point to scene data
 * @return  VOID_T
 */
VOID_T tal_zg_scene_recall_callback(UINT8_T ep_id, UINT8_T scene_id, UINT16_T group_id, UINT_T time100ms, TAL_SCENE_DATA_T *data)
{
    TAL_PR_DEBUG("scene recall: ep %d, sce %d, gp 0x%02x, type %d", ep_id, scene_id, group_id, data->type);
    UINT8_T onoff;
    LIGHT_MODE_E mode;
    UINT8_T proc_flag = TRUE;

    UINT16_T app_bright;
    UINT16_T app_temper;

    OPERATE_RET ret = 0;

    UINT8_T pre_onoff;

    TAL_PR_DEBUG("RECALL SCENE DATA LEN = %d", data->len);
    // for (UINT8_T i = 0; i < data->len; i++) {
    //     TAL_PR_DEBUG("%d ", data->pdata[i]);
    // }
    onoff = data->pdata[0];
    mode = (LIGHT_MODE_E)(data->pdata[1]);
    app_bright = (data->pdata[8] << 8) | data->pdata[9];
    app_temper = (data->pdata[10] << 8) | data->pdata[11];

    app_light_ctrl_data_switch_get((UINT8_T *)&pre_onoff);
    ret = app_light_ctrl_data_switch_set(onoff);

    if (pre_onoff != onoff)
    {
        //onoff state has been changed, stop the count down timer, and report onoff
        app_onoff_set_count_down_report_flag_time(TRUE, 1000);
        app_onoff_data_count_down_set(0);
    }
    if (ret == OPRT_OK)
    {
        proc_flag = TRUE;
    }
    ret = app_light_ctrl_data_mode_set((LIGHT_MODE_E)mode);
    if (ret == OPRT_OK)
    {
        proc_flag = TRUE;
    }
    if (mode == WHITE_MODE)
    {
        ret = app_light_ctrl_data_bright_set(app_bright, 0);
        if (ret == OPRT_OK)
        {
            proc_flag = TRUE;
        }
        ret = app_light_ctrl_data_temp_set(app_temper, 0);
        if (ret == OPRT_OK)
        {
            proc_flag = TRUE;
        }
    }
    else
    {
        // do nothing
    }
    if (proc_flag)
    {
        app_light_ctrl_proc();
        app_onoff_report_value(QOS_0, 0, onoff);
        app_color_report_ty_temperature_value(QOS_0, 500, app_temper);
        app_level_report_ty_bright_value(QOS_0, 800, app_bright);
    }
}

app_common.c 文件

  • 串口初始化函数:

    /**
     * @brief
     *
     * @param str
     * @return VOID_T
     */
    VOID_T dev_uart_output(IN CONST CHAR_T *str)
    {
        tal_uart_write(USER_UART0, str, strlen(str));
    }
    
  • GPIO 初始化函数:

    /**
     * @brief 
     * 
     * @return OPERATE_RET 
     */
    OPERATE_RET app_gpio_init(VOID_T)
    {
        OPERATE_RET v_ret = OPRT_COM_ERROR;
    
        TUYA_GPIO_BASE_CFG_T v_cfg = {
            .mode = TUYA_GPIO_PUSH_PULL,
            .direct = TUYA_GPIO_OUTPUT,
            .level = TUYA_GPIO_LEVEL_HIGH,
        };
    
        v_ret = tal_gpio_init(LED_IO, &v_cfg);
    
        if  (v_ret!=OPRT_OK) {
            TAL_PR_DEBUG("gpio init error!");
        }
    
        g_user_gpio_init_flag = TRUE;
    
        TAL_PR_DEBUG("gpio init ok!");
        return v_ret;
    }
    
  • 按键初始化函数:

    /**
     * @brief
     *
     * @return VOID_T
     */
    VOID_T app_button_init(VOID_T)
    {
        TUYA_GPIO_IRQ_T irq_cfg = {
            .mode = TUYA_GPIO_IRQ_FALL,
            .cb = __dev_pin_irq_handle,
            .arg = NULL,
        };
        tal_gpio_irq_init(KEY_GPIO, &irq_cfg);
    
    }
    
  • 设备初始化函数:

    /**
     * @note switch init
     *          1. gpio init;
     *          2. reset cnt processing
     *          3. power on config default light dada
     *          4. turn on led
     * @param [in] none
     * @return: OPERATE_RET
     */
    OPERATE_RET app_switch_init(VOID_T)
    {
    
        app_gpio_init();
        __dev_power_on_reset_data();
        app_switch_ctrl_proc();
    
        return OPRT_OK;
    }
    
    

app_cluster_on_off.c 文件

对设备的控制的功能都在该文件内进行实现。

  • DP 上报函数

    /**
     * @brief
     *
     * @param Qos
     * @param delay_ms
     * @param onoff
     * @return VOID_T
     */
    VOID_T app_onoff_report_value(TAL_SEND_QOS_E Qos, USHORT_T delay_ms, UINT8_T onoff)
    {
        TAL_ZG_SEND_DATA_T send_data;
    
        tal_system_memset(&send_data, 0, sizeof(TAL_ZG_SEND_DATA_T));
    
        tal_zg_send_data(&send_data, NULL, 2000);
        TAL_PR_DEBUG("Report onoff qos:%d VALUE %d", Qos, onoff);
    }
    
  • DP 下发处理函数

    /**
     * @note app on/off command handler
     * @param [in]{UCHAR_T} onoff
     * @param [in]{ZIGBEE_CMD_T} single or group transmit cmd
     * @return: none
     */
    STATIC VOID_T __app_onoff_cmd_set_value_handler(ZIGBEE_CMD_T cmd_type, UCHAR_T onoff)
    {
        TAL_SEND_QOS_E QOS = QOS_1;
        ULONG_T delay_ms = 0;
        OPERATE_RET ret;
        UINT8_T i = 0;
        TAL_PR_DEBUG("Receive on/off: %d", onoff);
    
        if (cmd_type == ZIGBEE_CMD_GROUP)
        {
            delay_ms = 10000 + tal_system_get_random(1000);
            QOS = QOS_0;
        }
        app_onoff_report_value(QOS, delay_ms, onoff);
        //dont report onoff again in the count down handler
        count_down_report_onoff_flag = 0;
        count_down_report_delay = delay_ms;
        app_onoff_data_count_down_set(0);
    
        ret = app_light_ctrl_data_switch_set(onoff);
        if (ret != OPRT_OK)
        {
            return;
        }
        app_light_ctrl_proc();
        //update on/off attribute
        tal_zg_write_attribute(1,
                               CLUSTER_ON_OFF_CLUSTER_ID,
                               ATTR_ON_OFF_ATTRIBUTE_ID,
                               &onoff,
                               ATTR_BOOLEAN_ATTRIBUTE_TYPE);
    }
    
  • 设备控制函数

    /**
     * @note: Light control proc
     * @param {none}
     * @return: OPERATE_RET
     */
    OPERATE_RET app_light_ctrl_proc(VOID_T)
    {
        STATIC UINT8_T last_switch_status = FALSE;
        TAL_PR_DEBUG("sg_lighht_ctrl_data.onoff = %d, last_switch_status = %d",sg_light_ctrl_data.onoff_status, last_switch_status);
    
        if (sg_light_ctrl_data.onoff_status == TRUE)
        {
            ///< OFF
            tal_gpio_write(LED_GPIO,TUYA_GPIO_LEVEL_HIGH);
        }
        else
        {
            ///< ON
            if (last_switch_status == FALSE)
            {
                ///< light is off
            }
            tal_gpio_write(LED_GPIO,TUYA_GPIO_LEVEL_LOW);
        }
        last_switch_status = sg_light_ctrl_data.onoff_status;
        return OPRT_OK;
    }
    
  • 获取设备状态函数

    通过此函数可以获取设备当前的开关状态。

    /**
     * @note: get switch data
     * @param {OUT UINT8_T *onff -> switch data return}
     * @return: OPERATE_RET
     */
    OPERATE_RET app_ctrl_data_switch_get(UINT8_T *on_off)
    {
        *on_off = sg_ctrl_data.onoff_status;
    
        return OPRT_OK;
    }