电工插座

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

本文以 SDK 内的智能单插 Demo 为例,介绍如何使用涂鸦标准模组 Zigbee SDK 开发,开发一款智能单插产品。

获取 Demo

涂鸦 IoT 平台,进入 硬件开发 阶段后,选择 涂鸦标准模组 SDK 开发 下载的 SDK 开发资料包内,有丰富的应用 Demo 可以用于参考开发应用代码。智能单插 Demo 就众多 Demo 中的其中之一。

电工插座

Demo 目录结构

解压下载到的 SDK 开发资料包后,您可以在 apps 文件夹下找到众多 Demo 例程。其中,插座 Demo 的目录结构如下:

电工插座

app 目录包含的文件和文件夹结构如下所示:

    app
     ├── build-all.py
     ├── light
     ├── sensor
     ├── smart_plug
     └── switch
           ├── common
           │   ├── include
           │   └── src
           └── project
                 └── sample_switch1
                          ├── documents
                          ├── EFR32MG13P732F512
                          ├── EFR32MG21A020F768
                          ├── include
                          ├── src
                          ├── pre-build.py
                          └── pre-build-iar.py

下表介绍了 sample_switch1 文件夹中包含的文件说明:

目录 说明
EFR32MG13P732F512 芯片是 EFR32MG13P732F512 的编译的入口,包含 IAR 和 GCC 编译的工程文件
EFR32MG21A020F768 芯片是 EFR32MG21A020F768 的编译的入口,包含 IAR 和 GCC 编译的工程文件
include 存放应用工程的 .h 格式的头文件
src 存放应用工程的 .c 格式的源文件
pre-build.py GCC 编译用到的脚本,应用无需修改
pre-build-iar.py IAR 编译用到的脚本,应用无需修改
switch/common 如果多个功能有公共编译文件可以放到这里面

代码说明

本章节介绍 Demo 代码是如何实现插座的功能,以及您可以怎么参考 Demo,并进行应用代码的开发。

通信逻辑

设备与云端交互的逻辑主要概括如下:

  1. 数据交互方式。

    Zigbee 子设备的数据先是上传到涂鸦网关,涂鸦网关将 Zigbee 协议解析成涂鸦标准协议,与云端使用 JSON 数据格式进行交互,数据的标识和定义是在平台第一步选择功能点处配置的。

    电工插座

  2. 设备标识。

    • 在涂鸦 IoT 平台创建产品会产生 PID 信息,PID 是设备的唯一标识,产品的配置都是和 PID 绑定的,比如 App UI 面板、云端配置、语音功能等,因此需要将 PID 信息写入到代码中,demo 代码在工程中的 package.json 文件中写入 PID 信息。
    • 固件指纹由固件标识名和固件版本号组成,设备生产授权时上位机读取模组的固件标识名和版本号用于订单信息校验,主要用于检验生产固件是否正确。
    • 固件标识名(一般使用项目名称)和固件版本号(x.x.x)可以在 package.json 文件中设置。其中 name 字段一般指的是固件标识名,version 指的是版本号。

    注意:固件标识名和固件版本号一定要和平台上创建的版本号一样,否则用云模组烧录平台会授权失败。

    电工插座

    • module_name 指的是模组名称,这个一定要和自己的使用的模组名称对应,否者用上位机时会授权失败。
    • model_id 是设备组网快速识别设备类型使用,是涂鸦子设备与涂鸦网关约定的标识,具体每个产品类型都不一样,具体可以参考 Zigbee 固件信息说明,里面有各个产品对应的model_id。
      电工插座
  3. 连接云端前授权。

    设备要连接到云端,需要进行授权,Zigbee 的设备如果不授权只能免费使用 7 天。授权码的获取和授权的流程请参考 通用 Wi-Fi SDK 烧录、授权和产测

Demo 功能

Demo 实现的功能概括如下:

功能名称 实现方式
本地控制 检测按键 I/O 口状态,控制继电器 I/O 口输出信号对继电器进行控制,将状态上报到网关,网关上报到云端。
进入组网状态 检测按键 I/O 口状态,如果检测到长按按键超过一定时间后(10S),调用重新组网函数,使设备进入到组网状态。
状态指示 判断设备当前的状态,并通过 LED 灯处于亮、灭、闪烁对不同状态进行指示。
App 下控制网关组网 此部分代码涂鸦 SDK 和网关已经封装完成,设备进入到配网状态后,可以在 App 上控制网关进行组网。
App 控制 解析 App 下发数据对硬件进行控制,并将状态上报到云端。
App 移除 调用移除接口,控制设备进入到配网状态。
语音控制 此部分代码与 App 对设备进行控制原理相同。
OTA 升级 接收云端推送升级固件,并更新运行。
产测 通过串口或者扫描信标进行产测。

功能实现

在了解 Demo 代码是如何实现设备管控功能前,您需要了解 SDK 的初始化流程:

电工插座

本章节主要介绍功能相关的代码介绍,部分函数对应多个功能实现,所以在对应功能内展示相关部分代码,其他部分代码使用 ***** 替代。

基础函数

整个流程中,功能的实现,您可以先关以下函数:

dev_power_on_init();     // 上电初始化
dev_system_on_init();   // 协议栈和系统基本组件启动后初始化
dev_mf_test_callback();        //生产测试用户回调
nwk_state_changed_callback(); //网络状态改变回调
  • 配置固件信息

    在涂鸦 IoT 平台创建完产品之后,首先配置固件信息。固件信息配置在对应工程下的 package.json 文件里进行配置,重要片段如下所示;

    package.json
    {
    	"fimwareInfo": {
    		"name": "oem_si32_zg_plug_10A_USB_dlx", //固件名称
    		"description": "ZigBee switch common", //固件描述
    		"version": "1.0.9", //软件版本号
    		"bv_version": "1.0", //目前固定,暂不需要更改
    		"ic": "efr32mg13p732gm48", // ZigBee 芯片型号
    		"ota_image_type":"0x1602", //OTA升级识别使用,目前固定
    		"manufacture_id":"0x1002", //OTA升级识别使用,目前固定
    		"model_id":"TS0105", //设备组网快速识别设备类型使用
    		"pid": "ZHahfZRP", //设备默认产品 id,IOT平台创建
    	"manufacture_name": "_TZ3000_",//产品id前缀,网关识别用什么协议来交互
    	"module_name":"ZS3L" //使用的模组型号
    	}
    }
    
  • Zigee设备创建和组网参数设定

    Zigbee 设备创建和对应的网络行为是密不可分的,这部分需要您通过 dev_power_on_init() 实现。以下代码是创建一个单插设备:

    const attr_t g_group_attr_list[] = {
    	GROUP_ATTR_LIST
    };
    
    const attr_t g_scene_attr_list[] = {
    	SCENE_ATTR_LIST
    };
    
    #define ON_OFF_PRIVATE_ATTR_LIST \
    	{ 0x0000, ATTR_BOOLEAN_ATTRIBUTE_TYPE, 1, (ATTR_MASK_TOKEN_FAST),  (uint8_t*)0x00 }, \
    
    const attr_t g_light_attr_list[] = {
    	ON_OFF_PRIVATE_ATTR_LIST
    };
    
    const cluster_t g_server_cluster_id[] = {
    	DEF_CLUSTER_GROUPS_CLUSTER_ID(g_group_attr_list)
    	DEF_CLUSTER_SCENES_CLUSTER_ID(g_scene_attr_list)
    	DEF_CLUSTER_ON_OFF_CLUSTER_ID(g_light_attr_list)
    };
    
    #define SERVER_CLUSTER_LEN  get_array_len(g_server_cluster_id)
    
    /**
    * @note 代表一个ON_OFF_LIGHT 设备,1个endpoint,
    * 支持 Server端 group、scene、onoff cluster。
    * 分别支持GROUP_ATTR_LIST、SCENE_ATTR_LIST、ON_OFF_PRIVATE_ATTR_LIST属性
    */
    const dev_description_t g_dev_des[] = {
    	{ 0x01, ZHA_PROFILE_ID, ZG_DEVICE_ID_ON_OFF_LIGHT, SERVER_CLUSTER_LEN, (cluster_t *)&g_server_cluster_id[0], 0, NULL},
    };
    
    void dev_power_on_init(void)
    {
    	zg_dev_config_t g_zg_dev_config;
    	join_config_t cfg;
    
    	/**
    	* @note 描述是一个什么设备,多少 endpoint,每个 endpoint 有什么 cluster 和 attributes。
    	*/
    	dev_register_zg_ep_infor((dev_description_t *)g_dev_des, EP_SUMS);
    
    	/**
    	* @note 把 Zigbee 设备设置配成一个路由设备。
    	*/
    	memset(&g_zg_dev_config, 0, sizeof(zg_dev_config_t));
    	g_zg_dev_config.dev_type = ZG_ROUTER;
    	dev_register_zg_dev_config(&g_zg_dev_config);
    
    	/**
    	* @note 配置上电或者远程删除后立即进入组网状态,组网超时 1 分钟。
    	*/
    	memset(&cfg, 0, sizeof(cfg));
    	cfg.auto_join_power_on_flag = TRUE;
    	cfg.auto_join_remote_leave_flag = TRUE;
    	cfg.join_timeout = 60000;
    	dev_zg_join_config(&cfg);
    
    	return;
    
  • Zigbee 设备入网和退网

    • 通过调用 dev_zigbee_join_start() 可以实现退出之前的网络,然后尝试组网。

    • 通过调用 dev_zigbee_leave_for_user() 可以实现只退出之前的网络。

    • 首先可以先写一个按键处理函数,通过按键来控制入网和退网。

    /*
    * @note __dev_key_handle 假定是注册的按键处理函数
    */
    static void __dev_key_handle(uint32_t key_id, key_st_t key_st, uint32_t push_time)
    {
    	switch(key_id) {
    		case KEY_1_INDEX: {
    			if(key_st == KEY_ST_PUSH) { ///< 按钮处于按下状态
    				if(push_time == 3000) { ///< 按钮按下持续了3000ms,后让LED2 开始闪烁
    					dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
    				}
    			}
    			else {
    				if(push_time < 3000) { ///< 按钮按下持续了不到3000ms,做短按处理
    					//TODO:
    				}
    				else {
    					dev_zigbee_join_start(60000); // /< 按键长按3s以上后,松开组网,组网超时1分钟
    				}
    			}
    			break;
    		}
    
    		case KEY_2_INDEX: {
    			if(key_st == KEY_ST_PUSH) { ///< 按钮处于按下状态
    				if(push_time == 3000) { ///< 按钮按下持续了3000ms,后让LED2 开始闪烁
    					dev_led_start_blink(LED_2_INDEX, 500, 500, DEV_LED_BLINK_FOREVER, DEV_IO_OFF); // /< LED1输出500ms高,300ms低,交替输出,让灯闪烁
    				}
    			}
    			else {
    				if(push_time < 3000) { ///< 按钮按下持续了不到3000ms,做短按处理
    					//TODO:
    				}
    				else {
    					dev_zigbee_leave_for_user(); // /< 按键长按3s以上后,松开退网
    					dev_led_stop_blink(LED_2_INDEX, DEV_IO_ON); // /< 点亮灯代表处于退网状态
    				}
    			}
    			break;
    		}
    
    		default: {
    			break;
    		}
    	}
    }
    
  • 管理 Zigbee 网关心跳

    为了维护和网关之间的链路有效性,刷新路由信息。设备和网关之间会维护一个心跳信息。心跳的启动会依赖系统的软定时服务,所以需要在 dev_system_on_init() 或更晚的地方调用。如果设备和网关没有定期维护心跳,设备会在 App 上显示离线(比如断电)。

    SDK 内部默认会根据设备类型产生一个周期心跳消息,您也可以显性调用更改默认行为。下面是一个在 dev_system_on_init() 实现的例子:

    void dev_system_on_init(void)
    {
    	dev_heartbeat_set(APP_VERSION, 180000); // /< 180s+30s随机值发送一次心跳
    }
    
  • Zigbee 网络状态

    SDK 提供一个回调函数来报告当前设备的 Zigbee 网关网络状态,应用开发无需维护网络状态,一般是通过指示灯来展示网络状态。应用要实现的回调函数例子如下:

    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 控制

设备在成功配网之后,可以在 App 操作控制面板,来下发对应的 DP 数据。设备通过 dev_msg_recv_callback() 回调函数来接收 Zigbee 网关的相关数据。以下为一个开关的数据接收示例:

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: { // 私有数据处理
            uint8_t len = dev_msg->data.bare_data.len;
            uint8_t *data = dev_msg->data.bare_data.data;
            //TODO: 私有协议处理
            break;
        }

        // 标准数据处理
        case CLUSTER_ON_OFF_CLUSTER_ID: {
            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: {
                        //TODO:关命令
                        break;
                    }
                    case CMD_ON_COMMAND_ID: {
                        //TODO:开命令
                        break;
                    }
                    case CMD_TOGGLE_COMMAND_ID: {
                        //TODO:取反命令
                        break;
                    }
                    default: {
                        break;
                    }
                }
                break;
            }

        }
        default:
            // Unrecognized cluster ID, error status will apply.
            break;
    }

    return result;
}

数据上报

数据上报指的是将数据上报给网关,网关将数据同步到云端及 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秒
}

语音控制

语音控制与 App 控制实现原理一样,实现 App 控制的相关功能即可

您需要在平台增值服务开通语音功能后,才能使用音响对设备进行控制,具体参考 第三方音响接入服务

产测

产品在批量生产时,为提高生产效率,会用代码实现批量的自动化测试,通常称为产测。
产测一般用到串口,首先确定程序是否初始化对应的串口,SDK 里面会写好,代码如下:

/**
 * @description: mf test uart register, aotomatic generated, not edit
 * @param {void} none
 * @return: user_uart_config_t mf uart config information
 */
user_uart_config_t config;
user_uart_config_t* mf_test_uart_config(void)
{
    memset(&config, 0, sizeof(user_uart_config_t));
    if (MODULE_NAME == TYZS2R)
    {
        user_uart_config_t default_config = TYZS2R_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    else if(MODULE_NAME == TYZS5)
    {
        user_uart_config_t default_config = TYZS5_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    else
    {
        user_uart_config_t default_config = TYZS3_USART_CONFIG_DEFAULT;
        memcpy(&config, &default_config, sizeof(user_uart_config_t));
    }
    return &config;
}

程序里面的 MODULE_NAME 是根据 package.json 文件里面的 module_name 字段来的,如果用的模组名称再程序中没有,按照自己的模组型号来配置程序。如上段程序中没有 ZS3L 型号,您可以按照以下代码填写:

电工插座

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 升级固件。

/**
 * @note (OPTION) This function is called when an OTA event occurs.Includes the beginning and end of the OTA
 * @param[in] {evt} The type of OTA event. Refer to enum ZG_OTA_EVT_T definition for details.
 * @return none
 */

VIRTUAL_FUNC void zg_ota_evt_callback(ZG_OTA_EVT_T evt);

固件编译

sample_switch1 为例,固件使用 IAR 编译的步骤如下:

  1. 进入目录silicon_labs_zigbee\app\switch\project\sample_switch1\EFR32MG13P732F512

  2. 双击 tuya_sdk.eww 即可打开工程。

  3. 打开工程后,进行开发编译。

    打开 IAR 后,建议不要通过 Open workspace 方式来打开工程,否则编译可能出错。另外,Rebuild All 之前建议 Clean 一下。

    电工插座

  4. 编译成功后会在 \app\smart_plug\project\sample_plug1\EFR32MG21A020F768\build\exe 目录下生成烧录用的.s37文件和.bin,如下图片所示:

    电工插座

  5. 编译出的文件,名称中尾缀为 .s37的是生产固件,生产时使用,一般您上传和烧录都是这个 QIO 文件。

烧录授权

将固件上传至涂鸦 IoT 平台,申请激活码,通过涂鸦 云模组烧录授权平台 写入授权信息,详情参考 ZigbeeSDK烧录授权