应用开发

更新时间:2022-11-24 09:20:10下载pdf

IoT 设备的基础功能通常包括网络连接、设备控制、设备状态上报、OTA 等。此外,还会涉及到生产相关的烧录授权、产测等功能。本文讲解涂鸦 Zigbee SDK 运行逻辑以及如何使用 SDK 调用相关接口函数实现相应功能。

SDK 运行流程

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

应用开发

主要函数作用

函数名称 函数作用
dev_power_on_init 这是硬件启动后的第一个函数,使用者至少需要实现 Zigbee 设备的创建,组网参数的设定。
dev_system_on_init 这是系统启动后的第一个功能函数,使用者可以实现Zigbee 属性操作,硬件初始化,软件定时处理等初始化。
nwk_state_changed_callback 实现 Zigbee 网络状态处理,当网络状态改变时调用此函数。

上电快速初始化

根据系统流程图,您首先要实现函数 dev_power_on_init,这是硬件启动后的第一个函数。

您需要实现 Zigbee 设备的创建和设定 Zigbee 设备的参数。包括设备角色(路由器、终端设备)、加入设备网络、重新加入参数等。

CPU 和基本时钟在调用该函数之前被初始化。

/**
 * @note (MUST) This is the first function after the hardware starts.
 * The CPU and base clock are initialized before calling this function.
 * You need to implement the creation of Zigbee devices and
 * determine the parameters of Zigbee device behavior.
 * Include device roles(router, end device), device networking(join),
 * rejoin parameters, and more. Refer to the TUYA Zigbee SDK demo for details.
 * @param none
 * @return none
 */
void dev_power_on_init(void);

示例代码片段

void dev_power_on_init(void)
{
    join_config_t cfg;
    zg_dev_config_t zg_dev_config;

    /**
   根据标准的要求创建Zigbee设备(profile endpoint cluster attributes) 。
   注册一个完整的 Zigbee 设备,包含描述 Zigbee 设备的 endpoint,cluster,attributes等。
    */
    dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS); //该函数必须要在 power_on_init 里面调用

    memset(&zg_dev_config, 0, sizeof(zg_dev_config_t));
    zg_dev_config.dev_type = ZG_ROUTER;
    dev_register_zg_dev_config(&zg_dev_config); //配置 Zigbee 的设备角色,是路由还是 end device 和 join,rejoin 的参数

    memset(&cfg, 0, sizeof(cfg));
    cfg.auto_join_power_on_flag = TRUE;
    cfg.auto_join_remote_leave_flag = TRUE;
    cfg.join_timeout = ZIGBEE_JOIN_MAX_TIMEOUT; //配置上电和远程删除的组网策略
    dev_zg_join_config(&cfg);

    //TODO: others task
    return;
}

Zigbee 设备创建和基础配置相关 API

API 功能 限制
dev_register_zg_ep_infor 注册一个完整的 Zigbee 设备,包含描述 Zigbee 设备的 endpoint,cluster,attributes等 只能在 dev_power_on_init 里面调用制
dev_register_zg_dev_config 配置 Zigbee 的设备角色,是路由还是 end device 和 join,rejoin 的参数 只能在 dev_power_on_init 里面调用制
dev_zg_join_config 配置上电和远程删除的组网策略 只能在 dev_power_on_init 里面调用制

协议栈和系统基本组件启动后初始化

dev_system_on_init 是系统启动后的第一个函数。在调用这个函数之前,Zigbee 栈和一些基本的组件已启动。您可以使用除个别 API 之外的所有 API。可以实现 Zigbee 属性操作,硬件初始化,软件定时处理等初始化。

/**
 * @note (MUST) This is the first function after the hardware starts.
 * The CPU and base clock are initialized before calling this function.
 * You need to implement the creation of Zigbee devices and
 * determine the parameters of Zigbee device behavior.
 * Include device roles(router, end device), device networking(join),
 * rejoin parameters, and more. Refer to the TUYA Zigbee SDK demo for details.
 * @param none
 * @return none
 */
void dev_power_on_init(void);

示例代码片段

static void __uart_rx_callback(uint8_t *data, uint16_t len)
{
    //TODO: 串口接收处理
}

static void __dev_evt_callback(uint8_t evt)
{
    switch(evt) {
        case EVT_LOCK_CANCEL: {
            //TODO: 自定义事件处理
            break;
        }

        default: {
            break;
        }
    }
}

void dev_system_on_init(void)
{
    user_uart_config_t *p_default_cfg = mf_test_uart_config();
    user_uart_config_t uart_cfg;
    memcpy(&uart_cfg, p_default_cfg, sizeof(user_uart_config_t));
    uart_cfg.func = __uart_rx_callback;
    user_uart_init(&uart_cfg); //单个串口配置,包含串口硬件参数和串口数据接收回调函数,
    dev_timer_start_with_callback(EVT_LOCK_CANCEL, 1000, __dev_evt_callback);//启动某个延时执行事件并设置事件处理函数
    dev_change_power(11, 19);//配置发送功率
    return;
}

组网操作

让设备进入搜索 Zigbee 网络的状态,如果设备已经加入网络,该函数会退网并立即搜网。

/**
 * @description: join start delay timer hander, join now
 * @param {none}
 * @return: none
 */
void network_join_start_delay_timer_callback(void)
{
    dev_zigbee_join_start(ZIGBEE_JOIN_MAX_TIMEOUT); //
}

当网络状态改变时调会用这个函数。建议在此函数处进行网络状态处理。

/**
 * @note (MUST) This function is invoked when the network state changes.
 * Handling network-related matters at this function is recommended.
 * @param[in] {state} Refer to NET_EVT_T for more detal.
 * @return none
 */
void nwk_state_changed_callback(NET_EVT_T state);

示例代码片段

void nwk_state_changed_callback(NET_EVT_T state)
{
    switch(state) {
        case NET_POWER_ON_LEAVE: {
            //TODO: 上电无网络状态
            break;
        }
        case NET_JOIN_START: {
            //TODO: 组网开始
            break;
        }
        case NET_JOIN_TIMEOUT: {
            //TODO: 组网失败
            break;
        }
        case NET_POWER_ON_ONLINE: {
            //TODO: 上电有网络状态
            break;
        }
        case NET_JOIN_OK: {
            //TODO: 组网成功
            break;
        }
        case NET_REJOIN_OK: {
            //TODO: 重连成功
            break;
        }
        case NET_LOST: {
            //TODO: 和父节点丢失
            break;
        }
        case NET_REMOTE_LEAVE: {
            //TODO: 远程离网通知
            break;
        }
        case NET_LOCAL_LEAVE: {
            //TODO: 本地离网通知
            break;
        }
        case NET_MF_TEST_LEAVE: {
            //TODO: 产测离网通知
            break;
        }
        default: {
            break;
        }
    }
}

下发数据处理

当网关或者 App 端有数据下发时,SDK 会自动找到下发数据处理的回调函数并执行,您需要完成回调函数的具体逻辑,实现解析接收到的数据并执行相关的命令。

您可以通过 dev_msg_recv_callback来接收 Zigbee 网关的相关数据。以下为一个开关的数据接收示例:

/**
 * @description: device receive message callback
 * @param {*dev_msg} received message information
 * @return: ZCL_CMD_RET_T
 */
ZCL_CMD_RET_T dev_msg_recv_callback(dev_msg_t *dev_msg)
{
    ZCL_CMD_RET_T result = ZCL_CMD_RET_SUCCESS;
    switch (dev_msg->cluster)
    {
        case CLUSTER_PRIVATE_TUYA_CLUSTER_ID: //private data processing
        {
            break;
        }
        case CLUSTER_ON_OFF_CLUSTER_ID: //0x0006 Zigbee ON OFF Cluster //standard data processin
        {
            attr_value_t *attr_list = dev_msg->data.attr_data.attr_value;
            uint8_t attr_sums = dev_msg->data.attr_data.attr_value_sums;
            uint8_t i;
            for(i = 0; i < attr_sums; i++)
            {
                switch(attr_list[i].cmd)
                {
                    case CMD_OFF_COMMAND_ID: //关命令
                    {
                        __dev_switch_op(dev_msg->endpoint, DEV_IO_OFF); //控制硬件接口
                        __dev_report_onoff_msg(dev_msg->endpoint, QOS_0);//上报网关设备开关状态
                        break;
                    }
                    case CMD_ON_COMMAND_ID: //开命令
                    {
                        __dev_switch_op(dev_msg->endpoint, DEV_IO_ON);
                        __dev_report_onoff_msg(dev_msg->endpoint, QOS_0);
                        break;
                    }
                    case CMD_TOGGLE_COMMAND_ID: //取反命令
                    {
                        __dev_switch_op(dev_msg->endpoint, (DEV_IO_ST_T)!g_relay_onoff_status[dev_msg->endpoint - 1]);
                        __dev_report_onoff_msg(dev_msg->endpoint,QOS_0);
                        break;
                    }
                    default:
                    {
                        break;
                    }
                }
                break;
            }
        }
        default:
            // Unrecognized cluster ID, error status will apply.
            break;
    }
    return result

Zigbee Global 类的 Write 命令通过 dev_msg_write_attr_callback_ext 或者 dev_msg_write_attr_callback 来通知应用层。

void dev_msg_write_attr_callback(uint8_t endpoint, CLUSTER_ID_T cluster, uint16_t attr_id)
{
    //TODO: 远程写属性成功后调用该函数
    return;
}

void dev_msg_write_attr_callback_ext(
    uint8_t endpoint,
    CLUSTER_ID_T cluster,
    uint16_t attr_id,
    uint8_t mask,
    uint16_t manufacturer_code,
    uint8_t type,
    uint8_t size,
    uint8_t* value)
{
    //TODO: 远程写属性成功后调用该函数,附带了原始数据参数
    return;
}

数据上报

设备状态改变时,根据设备状态对应的 DP,调用相应的上报接口函数,将设备状态同步到云端和 App。

static void __send_result_cb(SEND_ST_T st, dev_send_data_t *msg)
{
    switch(st) {
        case SEND_ST_OK: {
            //TODO: 发送成功
            break;
        }
        default: {
            //TODO: 发送失败
            break;
        }
    }
}

static void send_data_demo(void)
{
    dev_send_data_t send_data;

    memset(&send_data, 0, sizeof(dev_send_data_t));

    send_data.zcl_id = 0;  ///< 用户自定义 ID,发送成功失败回调会传回该参数
    send_data.qos = QOS_1; ///< 如果没有收到ACK,会重传
    send_data.direction = ZCL_DATA_DIRECTION_SERVER_TO_CLIENT;
    send_data.command_id = CMD_REPORT_ATTRIBUTES_COMMAND_ID;       ///< 通用上报属性的命令
    send_data.addr.mode = SEND_MODE_GW;                            ///< 发送给网关
    send_data.addr.type.gw.cluster_id = CLUSTER_ON_OFF_CLUSTER_ID; ///< 开关属性
    send_data.addr.type.gw.src_ep = 1; ///< 设备 endpoint
    send_data.delay_time = 0;   ///< 延时发送时间
    send_data.random_time = 0;  ///< 随机发送时间范围
    send_data.data.zg.attr_sum = 1;  ///< 上报属性的个数
    send_data.data.zg.attr[0].attr_id = ATTR_ON_OFF_ATTRIBUTE_ID; ///< 开光状态属性
    send_data.data.zg.attr[0].type = ATTR_BOOLEAN_ATTRIBUTE_TYPE; ///< 属性数据类型
    send_data.data.zg.attr[0].value_size = 1;                     ///< 属性数据长度
    send_data.data.zg.attr[0].value[0] = 1; ///< 1代表开,0代表关状态
    dev_zigbee_send_data(&send_data, __send_result_cb, 1000); ///< 发送,1000代表报文最大重传持续时间是1秒
}

固件编译

  • Windows 下用 IAR 编译

  • Linux 下用GCC 编译

    #./run.sh clean   //清除编译输出的问题
    #./run.sh build 0 //编译 release 版本
    #./run.sh build 1 //编译 debug 版本(带串口打印信息)
    

烧录授权

将编译生成的生产文件烧录到芯片内,并使用涂鸦授权工具进行授权,即可验证代码功能。

不同芯片的烧录方式不同,需要参考具体芯片的烧录说明文档,授权通过串口通信,将授权信息写入到芯片特定 Flash 区域,此部分功能由涂鸦 SDK 自动完成,您无需关心具体的逻辑。

授权是产测中的其中一个测试项。涂鸦云模组烧录工具软件,以及 PCBA 产测软件都可以对模组进行授权。涂鸦云模组烧录工具进行烧录时,会自动对模组进行授权。只有对设备授权后,设备才能长期正常运行。

通过 J-Link 软件烧录的固件的设备,不能长期的运行,只能正常运行一周。固件开发好后,可以上传到涂鸦后台,进行云模组烧录工具进行授权。

产测

应用开发

产品在批量生产时,为提高生产效率,会用代码实现批量的自动化测试,通常称为产测。
SDK 底层已经对部分产测测试项进行过处理。例如,进入测试模式,读取 Mac,检测固件版本,写入 PID,读取 PID,进行 RF 测试,获取 RSSI 信号值,授权测试等。SDK中代码如下:

/**
 * @description: device manufactury test callback, when device is in manufactury test model,
 * sdk will use this callback to notify application the test item and test command;
 * @param {cmd} manufactury test type
 * @param {*args} manufactury test data
 * @param {arg_len} manufactury test data length
 * @return: none
 */
MF_TEST_RET_T dev_mf_test_callback(MF_TEST_CMD_T cmd, uint8_t *args, uint16_t arg_len)
{
    switch(cmd)
    {
        case MF_TEST_LED_ON_ALL:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_ON);
            dev_led_stop_blink(LED_1_IO_INDEX, DEV_IO_ON);
            break;
        }
        case MF_TEST_LED_OFF_ALL:
        {
            dev_led_stop_blink(NET_LED_1_IO_INDEX, DEV_IO_OFF);
            dev_led_stop_blink(LED_1_IO_INDEX, DEV_IO_OFF);
            break;
        }
        case MF_TEST_LED_BLINK_ALL:
        {
            dev_led_start_blink(NET_LED_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            dev_led_start_blink(LED_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            break;
        }
        case MF_TEST_RELAY_ON_ALL:
        {
            dev_led_stop_blink(RELAY_1_IO_INDEX, DEV_IO_ON);
            break;
        }
        case MF_TEST_RELAY_OFF_ALL:
        {
            dev_led_stop_blink(RELAY_1_IO_INDEX, DEV_IO_OFF);
            break;
        }
        case MF_TEST_RELAY_BLINK_ALL:
        {
            dev_led_start_blink(RELAY_1_IO_INDEX, 500, 500, 4, DEV_IO_OFF);
            break;
        }
        case MF_TEST_BUTTON:
        {
            g_work_st = DEV_WORK_ST_TEST;
            return MF_TEST_DOING;
        }
        case MF_TRANSFER:
        {
        }
        default :
        {
            break;
        }
    }
    return MF_TEST_SUCCESS;
}

固件 OTA 升级

Zigbee 模组 SDK 自带 OTA 升级功能。工程开发编译完成后,会生成后缀为 .s37 的烧录固件以及后缀为 .bin 的 OTA 升级固件。

设备可以通过网关升级固件版本。如果 OTA 版本固件比当前版本低或者一致,就不能进行升级。高版本固件上传到涂鸦 IoT 平台,可以给要升级的设备配置白名单进行升级

  • 对于 Router 节点(常供电设备),您不需要对 OTA 升级进行额外的开发。

  • 对于 End device 节点(低功耗设备),因为低功耗设备长期进行休眠,关闭了接收 RF 数据能力,所以在移动应用上操作设备升级后,需要唤醒设备发送数据请求获取升级命令,并且设备需要在以下回调函数中处理相关逻辑:

    // OTA 升级回调
    void zg_ota_evt_callback(ZG_OTA_EVT_T evt)
    {
    	switch(evt) {
    		case ZG_OTA_EVT_START: { //开始升级
    			zg_poll_interval_change( 250 ); // 配置数据请求间隔时间为 250ms,以加快升级。
    			zg_poll_start();
    			break;
    		}
    		default: {
    			zg_poll_interval_change( 1000 );
    			zg_poll_end(); // 升级完成后,停止数据请求,避免电量浪费。
    			break;
    		}
    	}
    }