更新时间:2024-06-25 10:03:13下载pdf
在涂鸦开发者平台下载的模组 SDK 开发资料包内,有相关的应用 Demo 可以用于参考开发应用代码,此文档以智能单插 Demo 为例,介绍如何通过涂鸦平台,使用模组 SDK 开发方式,开发一款智能单插产品。
使用通用 Wi-Fi SDK Demo 前,您至少已经在 涂鸦开发者平台 上创建了一款采用 Wi-Fi 通讯方式的智能产品,例如一款电工插座产品。详细步骤请参考 产品智能化。
在产品开发的 硬件开发 阶段,选择 涂鸦模组 SDK 开发 方式,并根据产品需求选择合适的模组,下载开发使用的 SDK 和 功能调试文件。
平台下载到的 SDK 开发资料包解压后,可以在 apps 文件夹下找到 demo 例程,这里以插座 Demo 为例演示,目录结构如下。
Demo 中包含的文件及相关说明如下所示:
文件名 | 说明 |
---|---|
tuya_device.c | 包含设备相关初始化,功能代码,产测等 |
hw_table.c | 硬件相关的初始化及控制操作等 |
tuya_device.h | 头文件,其中包含关键固件 key 参数 |
hw_table.h | 头文件,硬件控制接口等 |
本章节介绍 Demo 代码是如何实现插座的功能,以及怎么参考 Demo 进行应用代码的开发。
设备与涂鸦云端交互的逻辑主要概括如下:
涂鸦设备与云端使用 Json 数据格式进行交互,数据的标识和定义是在平台第一步选择功能点处配置的,demo 代码在 tuya_device.h 文件中通过 typedef 定义 DP 信息
在涂鸦开发者平台创建产品会产生 PID 信息,PID 是设备的唯一标识,产品的配置都是和PID绑定的,比如App UI面板、云端配置、语音功能等,因此需要将 PID 信息写入到代码中,demo 代码在 tuya_device.h 中通过宏定义写入 PID 信息
设备要连接到涂鸦云端,需要进行授权,授权码的获取和授权的流程请参考 通用 Wi-Fi SDK 烧录、授权和产测。
说明:现阶段获取授权码需要通过工单,提供代码内的PID信息获取。
Demo 实现的功能概括如下:
功能名称 | 实现方式 |
---|---|
本地控制 | 检测按键IO口状态,控制继电器IO口输出信号对继电器进行控制,如果设备联网,将状态上报到云端 |
进入配网状态 | 检测按键IO口状态,如果检测到长按按键超过一定时间后(5S),调用重置函数,使设备进入到配网状态 |
状态指示 | 判断设备当前的状态,并通过LED灯处于亮、灭、快闪、慢闪对不同状态进行指示 |
App配网 | 此部分代码涂鸦SDK已经封装完成,设备进入到配网状态后,使用App选择对应的配网方式进行配网即可,无需编写代码 |
App控制 | 解析App下发数据对硬件进行控制,并将状态上报到云端 |
App移除 | 调用移除接口,控制设备进入到配网状态 |
语音控制 | 此部分代码与App对设备进行控制原理相同 |
定时开关 | 启动软件定时器,间隔固定时间,更新倒计时,判断时间结束时,控制设备响应相应的动作 |
断电记忆 | 控制设备到一个状态时,启用一个定时器,设置时间,时间到后,将设备当前的状态写到Flash内,当重新上电时,去Flash中读取设备状态参数,对设备进行控制,保证设备处于断电前的状态 |
OTA升级 | 接收云端推送升级固件,并更新运行 |
产测 | 扫描产测SSID并判断信号强度是否满足要求,进行对应的指示 |
在了解 Demo 代码是如何实现设备管控功能前,您需要了解 SDK 的初始化流程:
整个流程中,应用相关功能的实现,您需要重点关注 4 个函数:
user_main() // 入口函数,通过调用下边三个函数完成 SDK 初始化
pre_device_init() // 查看 SDK 版本,设置打印日志输出
app_init() // 设置 Wi-Fi 模组工作模式,注册产测回调函数
device_init() // 注册功能回调函数,完成硬件相关的初始化
查看 SDK 版本:
//打印SDK相关信息、固件名称和版本、设置日志打印等级
VOID pre_device_init(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
PR_DEBUG("%s",tuya_iot_get_sdk_info());
PR_DEBUG("%s:%s",APP_BIN_NAME,DEV_SW_VERSION);
PR_NOTICE("firmware compiled at %s %s", __DATE__, __TIME__);
SetLogManageAttr(TY_LOG_LEVEL_INFO);
}
设置Wi-Fi模组工作的模式:
//设置Wi-Fi模组工作的模式
VOID app_init(VOID)
{
app_cfg_set(GWCM_LOW_POWER,prod_test);
}
完成硬件相关的初始化,注册功能回调函数:
//完成硬件相关的初始化,注册功能回调函数
OPERATE_RET device_init(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
//功能代码相关的回调函数
TY_IOT_CBS_S wf_cbs = {
status_changed_cb,\
gw_ug_inform_cb,\
gw_reset_cb,\
dev_obj_dp_cb,\
dev_raw_dp_cb,\
dev_dp_query_cb,\
NULL,
};
//IoT框架的初始化
op_ret = tuya_iot_wf_soc_dev_init_param(func_select.gwcm_mode_user, WF_START_SMART_FIRST, &wf_cbs, FIRMWAIRE_KEY, PRODUCT_KEY, DEV_SW_VERSION);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_wf_soc_dev_init err:%d",op_ret);
return op_ret;
}
//硬件相关的初始化
op_ret = init_hw(&g_hw_table);
if(OPRT_OK != op_ret) {
PR_ERR("init_hw err:%d",op_ret);
return op_ret;
}
//查询Wi-Fi模组的状态回调函数
op_ret = tuya_iot_reg_get_wf_nw_stat_cb(__get_wf_status);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb err:%d",op_ret);
return op_ret;
}
//状态保存的回调函数,倒计时的回调函数,Wi-Fi指示状态的回调函数
op_ret = differ_init();
if(OPRT_OK != op_ret) {
PR_ERR("differ_init err:%d",op_ret);
return op_ret;
}
//内存状态查询回调函数,用于判断程序内存是否正常
op_ret = sys_add_timer(memory_left_timer_cb, NULL, &func_select.memory_left_timer);
if(OPRT_OK != op_ret) {
return op_ret;
}else {
sys_start_timer(func_select.memory_left_timer,2*1000,TIMER_CYCLE);
}
INT_T size = tuya_hal_system_getheapsize();
PR_DEBUG("device_init ok free_mem_size:%d",size);
return OPRT_OK;
}
//倒计时、断电记忆等功能的实现
STATIC OPERATE_RET differ_init(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
if(func_select.is_save_stat) {
PR_DEBUG("is_save_stat = %d",func_select.is_save_stat);
read_saved_stat();
op_ret = sys_add_timer(save_stat_timer_cb, NULL, &func_select.save_stat_timer);
if(OPRT_OK != op_ret) {
return op_ret;
}
}
if(func_select.is_count_down) {
op_ret = sys_add_timer(cd_timer_cb, NULL, &func_select.cd_timer);
if(OPRT_OK != op_ret) {
return op_ret;
}else {
PR_NOTICE("cd_timer ID:%d",func_select.cd_timer);
sys_start_timer(func_select.cd_timer, 1000, TIMER_CYCLE);
}
}
op_ret = sys_add_timer(wf_timer_cb,NULL, &func_select.wf_timer);
if(OPRT_OK != op_ret) {
PR_ERR("sys_add_timer failed! op_ret:%d", op_ret);
return op_ret;
}
return OPRT_OK;
}
后续章节是对功能相关的代码介绍,部分函数对应多个功能实现,所以在对应功能内展示相关部分代码,其他部分代码使用 *****
替代。
//在硬件初始化时调用处理函数
HW_TABLE g_hw_table =
{
.wifi_stat_led.io_cfg = {.type = IO_DRIVE_LEVEL_LOW, .pin = TY_GPIOA_0},
.rst_button = {TY_GPIOA_5,TRUE,LP_ONCE_TRIG,KEY_RST_TIME,50,key_process},
.channels = channels
};
VOID key_process(IN TY_GPIO_PORT_E port, IN PUSH_KEY_TYPE_E type, IN INT_T cnt)
{
*******************
else if(NORMAL_KEY == type) {
if(g_hw_table.rst_button.port== port) {
BOOL_T is_every_active = hw_get_all_channel_stat();
hw_trig_all_channel(is_every_active);
if(func_select.is_save_stat) {
sys_start_timer(func_select.save_stat_timer, 5000, TIMER_ONCE);
}
op_ret = upload_all_dp_stat();
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
return op_ret;
}
}
}
}
// 继电器控制引脚配置
STATIC CTRL_CHANNEL channels[] =
{
{
.relay = {.type = IO_DRIVE_LEVEL_HIGH, .pin = TY_GPIOA_14},
.button = {TY_GPIOA_5,TRUE,LP_INVALID,0,50,NULL},
.led.io_cfg = {.type = IO_DRIVE_LEVEL_NOT_EXIST },
.dpid = DP_SWITCH,
.cd_dpid = DP_COUNT_DOWN,
.prtest_swti1_count = 0
}
}
//指示灯控制引脚配置
STATIC OPERATE_RET led_pin_reg(INOUT OUT_PUT_S *output)
{
OPERATE_RET op_ret;
if(NULL == output) {
PR_ERR("NULL pointer");
return OPRT_INVALID_PARM;
}
if(!IS_IO_TYPE_ALL_PERIPH(output->io_cfg.type)) {
PR_ERR("IO type not define");
return OPRT_INVALID_PARM;
}
if(output->io_cfg.type != IO_DRIVE_LEVEL_NOT_EXIST) {
op_ret = tuya_create_led_handle(output->io_cfg.pin,\
IO_ACTIVE_TYPE(output->io_cfg.type),\
&(output->io_handle));
if(OPRT_OK != op_ret) {
PR_ERR("op_ret =%d",op_ret);
return op_ret;
}
}
return OPRT_OK;
}
涂鸦模组 SDK 支持实现 Wi-Fi 快连配网和热点配网两种配网方式,对应涂鸦智能 App 中的 EZ模式 和 AP模式。
此 Demo 通过控制按键长按控制设备进入到不同的配网状态。g_hw_table
中注册按键处理函数 key_process
,判断按键长按,调用 tuya_iot_wf_gw_unactive
控制设备进入配网状态。配网状态的切换形式是循环机制:
tuya_iot_wf_gw_unactive
会进入到 Wi-Fi 快连配网状态。tuya_iot_wf_gw_unactive
会进入到热点配网状态。HW_TABLE g_hw_table =
{
.wifi_stat_led.io_cfg = {.type = IO_DRIVE_LEVEL_LOW, .pin = TY_GPIOA_0},
.rst_button = {TY_GPIOA_5,TRUE,LP_ONCE_TRIG,KEY_RST_TIME,50,key_process},
.channels = channels
};
VOID key_process(IN TY_GPIO_PORT_E port, IN PUSH_KEY_TYPE_E type, IN INT_T cnt)
{
*******************
if(LONG_KEY == type) {
if(g_hw_table.rst_button.port== port) {
PR_NOTICE("LONG PRESS KEY!");
op_ret = tuya_iot_wf_gw_unactive();
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_wf_gw_unactive op_ret:%d",op_ret);
return;
}
}
}
*******************
}
当网络状态变化时,device_init
函数中的 tuya_iot_reg_get_wf_nw_stat_cb
回调函数调用 __get_wf_status
,通过hw_set_wifi_led_stat
对查询设备的当前状态。
OPERATE_RET device_init(VOID)
{
*****************************
op_ret = tuya_iot_reg_get_wf_nw_stat_cb(__get_wf_status);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb err:%d",op_ret);
return op_ret;
}
*****************************
}
STATIC VOID __get_wf_status(IN CONST GW_WIFI_NW_STAT_E stat)
{
OPERATE_RET op_ret = OPRT_OK;
func_select.sys_wifi_stat = stat;
hw_set_wifi_led_stat(stat);
STATIC BOOL_T proc_flag = FALSE;
if((STAT_STA_CONN <= stat)&&(proc_flag == FALSE)) {
op_ret = upload_all_dp_stat();
if(OPRT_OK != op_ret) {
PR_ERR("upload_all_dp_stat op_ret:%d",op_ret);
return;
}
proc_flag = TRUE;
}
return;
}
根据设备当前的状态指示,App 选择对应的配网方式进行配网,此部分代码 SDK 内已实现,无需您再开发。
注意:配网成功后,设备会调用
Start_boot_up
重启一次,将所有的 DP 当前的状态上报到云端,用于 App 控制面板显示。
// 设备连接到云端时,会调用 Start_boot_up 重启一次将设备当前所有 DP 的状态进行上报,用于 App 面板显示
VOID Start_boot_up(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
op_ret = upload_all_dp_stat();
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
return;
}
}
OPERATE_RET upload_all_dp_stat(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T count_sec = 0;
INT_T ch_idx = 0, dp_idx = 0;
INT_T dp_cnt = 0;
if(func_select.is_count_down) {
dp_cnt = g_hw_table.channel_num*2;
}else {
dp_cnt = g_hw_table.channel_num;
}
TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
if(NULL == dp_arr) {
PR_ERR("malloc failed");
return OPRT_MALLOC_FAILED;
}
memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S));
for(ch_idx = 0,dp_idx = 0; (ch_idx < g_hw_table.channel_num)&&(dp_idx < dp_cnt); ch_idx++,dp_idx++) {
dp_arr[dp_idx].dpid = get_channel_dpid(ch_idx);
dp_arr[dp_idx].type = PROP_BOOL;
dp_arr[dp_idx].time_stamp = 0;
dp_arr[dp_idx].value.dp_bool = get_channel_stat(ch_idx);
if(func_select.is_count_down) {
dp_idx++;
dp_arr[dp_idx].dpid = get_channel_cddpid(ch_idx);
dp_arr[dp_idx].type = PROP_VALUE;
dp_arr[dp_idx].time_stamp = 0;
if(g_hw_table.channels[ch_idx].cd_sec >= 0) {
dp_arr[dp_idx].value.dp_value = g_hw_table.channels[ch_idx].cd_sec;//get_cd_remain_sec(ch_idx);
}else {
dp_arr[dp_idx].value.dp_value = 0;
}
}
}
op_ret = dev_report_dp_json_async(get_gw_cntl()->gw_if.id,dp_arr,dp_cnt);
Free(dp_arr);
dp_arr = NULL;
if(OPRT_OK != op_ret) {
PR_ERR("dev_report_dp_json_async op_ret:%d",op_ret);
return op_ret;
}
return OPRT_OK;
}
在 device_init
函数中,注册了 DP 数据的处理回调函数 dev_obj_dp_cb
。当 App 接收到数据时,dev_obj_dp_cb
调用 deal_dps_proc
对接收到的 DP 数据处理,并执行相关硬件,然后调用 dev_report_dp_json_async
将状态返回给 App。
OPERATE_RET device_init(VOID)
{
***************************
TY_IOT_CBS_S wf_cbs = {
status_changed_cb,\
gw_ug_inform_cb,\
gw_reset_cb,\
dev_obj_dp_cb,\
dev_raw_dp_cb,\
dev_dp_query_cb,\
NULL,
};
****************************
}
VOID dev_obj_dp_cb(IN CONST TY_RECV_OBJ_DP_S *dp)
{
PR_DEBUG("dp->cid:%s dp->dps_cnt:%d",dp->cid,dp->dps_cnt);
deal_dps_proc(dp);
}
STATIC VOID deal_dps_proc(IN CONST TY_RECV_OBJ_DP_S *dp)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T i = 0,ch_index;
for(i = 0;i < dp->dps_cnt;i++) {
PR_DEBUG("dpid:%d type:%d time_stamp:%d",dp->dps[i].dpid,dp->dps[i].type,dp->dps[i].time_stamp);
switch(dp->dps[i].dpid) {
case DP_SWITCH: {
ch_index = hw_set_channel_by_dpid(dp->dps[i].dpid, dp->dps[i].value.dp_bool);
if( ch_index >= 0) {
if((func_select.is_count_down)&&(g_hw_table.channels[ch_index].cd_sec >= 0)) {
g_hw_table.channels[ch_index].cd_sec = -1;
}
op_ret = upload_channel_stat(ch_index);
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
break;
}
}
if(func_select.is_save_stat) {
sys_start_timer(func_select.save_stat_timer, 5000, TIMER_ONCE);
}
}
break;
case DP_COUNT_DOWN: {
ch_index = hw_find_channel_by_cd_dpid(dp->dps[i].dpid);
if(ch_index >= 0) {
if(dp->dps[i].value.dp_value == 0) {
g_hw_table.channels[ch_index].cd_sec = -1;
}else {
g_hw_table.channels[ch_index].cd_sec = dp->dps[i].value.dp_value;
}
op_ret = upload_channel_stat(ch_index);
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
break;
}
}
}
break;
default:
break;
}
}
}
OPERATE_RET upload_channel_stat(IN UINT_T ch_idx)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T count_sec = 0;
INT_T dp_idx = 0;
INT_T dp_cnt = 0;
dp_cnt = (func_select.is_count_down)?2:1;
TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
if(NULL == dp_arr) {
PR_ERR("malloc failed");
return OPRT_MALLOC_FAILED;
}
memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S));
dp_arr[dp_idx].dpid = get_channel_dpid(ch_idx);
dp_arr[dp_idx].type = PROP_BOOL;
dp_arr[dp_idx].time_stamp = 0;
dp_arr[dp_idx].value.dp_bool = get_channel_stat(ch_idx);
dp_idx ++;
if(func_select.is_count_down) {
dp_arr[dp_idx].dpid = get_channel_cddpid(ch_idx);
dp_arr[dp_idx].type = PROP_VALUE;
dp_arr[dp_idx].time_stamp = 0;
if(g_hw_table.channels[ch_idx].cd_sec >= 0) {
dp_arr[dp_idx].value.dp_value = g_hw_table.channels[ch_idx].cd_sec;//get_cd_remain_sec(ch_idx);
}else {
dp_arr[dp_idx].value.dp_value = 0;
}
dp_idx ++;
}
if(dp_idx != dp_cnt) {
PR_ERR("dp_idx:%d,dp_cnt:%d",dp_idx,dp_cnt);
Free(dp_arr);
dp_arr = NULL;
return OPRT_COM_ERROR;
}
op_ret = dev_report_dp_json_async(get_gw_cntl()->gw_if.id,dp_arr,dp_cnt);
Free(dp_arr);
dp_arr = NULL;
if(OPRT_OK != op_ret) {
PR_ERR("dev_report_dp_json_async op_ret:%d",op_ret);
return op_ret;
}
return OPRT_OK;
}
//App 查询设备状态
VOID dev_dp_query_cb(IN CONST TY_DP_QUERY_S *dp_qry)
{
PR_DEBUG("Recv DP Query Cmd");
OPERATE_RET op_ret = OPRT_OK;
op_ret = upload_all_dp_stat();
if(OPRT_OK != op_ret) {
PR_ERR("upload_all_dp_stat op_ret:%d",op_ret);
return;
}
}
在 App 移除设备时,设备接收到移除命令后,调用 gw_reset_cb
重置设备并进入配网状态。
VOID gw_reset_cb(IN CONST GW_RESET_TYPE_E type)
{
OPERATE_RET op_ret = OPRT_OK;
if(GW_REMOTE_RESET_FACTORY != type) {
return;
}
if(func_select.is_save_stat) {
// 恢复出厂设置的上电能力默认关闭
op_ret = wd_common_fuzzy_delete(STORE_OFF_MOD);
if(OPRT_OK != op_ret) {
PR_ERR("clear power_stat op_ret:%d",op_ret);
}
}
return;
}
语音控制与 App 控制实现原理一样,详细请参考 App 配网、App 控制、App 移除。
注意:您需要在平台增值服务开通语音功能后,才能使用音响对设备进行控制,具体参考 第三方音响接入服务。
在 func_select
中使能倒计时功能,在 differ_init
中判断倒计时功能为真,启动软件定时器注册 cd_timer_cb
回调函数,每隔 30 秒更新一次倒计时,倒计时结束后执行相应动作。
STATIC OPERATE_RET differ_init(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
*******************************
if(func_select.is_count_down) {
op_ret = sys_add_timer(cd_timer_cb, NULL, &func_select.cd_timer);
if(OPRT_OK != op_ret) {
return op_ret;
}else {
PR_NOTICE("cd_timer ID:%d",func_select.cd_timer);
sys_start_timer(func_select.cd_timer, 1000, TIMER_CYCLE);
}
}
op_ret = sys_add_timer(wf_timer_cb,NULL, &func_select.wf_timer);
if(OPRT_OK != op_ret) {
PR_ERR("sys_add_timer failed! op_ret:%d", op_ret);
return op_ret;
}
return OPRT_OK;
}
FUNC_SELECT func_select = {
.is_save_stat = FALSE,
.is_count_down = TRUE,
.gwcm_mode_user = GWCM_SPCL_MODE,
.cd_upload_period = 30,
.prod_flag = FALSE
};
STATIC VOID cd_timer_cb(IN UINT_T timerID, IN PVOID pTimerArg)
{
UCHAR_T i = 0;
OPERATE_RET op_ret = OPRT_OK;
for(i = 0; i<g_hw_table.channel_num; i++) {
if(g_hw_table.channels[i].cd_sec < 0) {
continue;
}else {
--g_hw_table.channels[i].cd_sec;
if(g_hw_table.channels[i].cd_sec <= 0) {
g_hw_table.channels[i].cd_sec = -1;
hw_trig_channel(i);
op_ret = upload_channel_stat(i);
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
return ;
}
}else {
if(g_hw_table.channels[i].cd_sec % 30 == 0) {
op_ret = upload_channel_stat(i);
if(OPRT_OK != op_ret) {
PR_ERR("upload_channel_stat op_ret:%d",op_ret);
return;
}
}
}
}
}
}
在 func_select
中使能断电记忆功能,在 differ_init
中判断断电记忆功能为真,启动软件定时器注册 save_stat_timer_cb
回调函数,每隔 5 秒调用 wd_common_write
将当前状态写入到驱动(Flash)中。
当再次上电时,调用 read_saved_stat
从 Flash 中读取当前的状态,控制进入到此状态,实现断电记忆,并发送给 App 显示。
#define STORE_OFF_MOD "save_off_stat"
#define STORE_CHANGE "init_stat_save"
FUNC_SELECT func_select = {
.is_save_stat = FALSE,
.is_count_down = TRUE,
.gwcm_mode_user = GWCM_SPCL_MODE,
.cd_upload_period = 30,
.prod_flag = FALSE
};
STATIC VOID save_stat_timer_cb(IN UINT_T timerID,IN PVOID pTimerArg)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T i;
PR_DEBUG("save stat");
IN CONST BYTE_T buff[48] = "{\"power\":[";
for(i=0; i<g_hw_table.channel_num; ++i) {
if(g_hw_table.channels[i].stat) {
strcat(buff, "true");
}
else if(!g_hw_table.channels[i].stat) {
strcat(buff, "false");
}
if(i < g_hw_table.channel_num -1) {
strcat(buff, ",");
}else {
strcat(buff, "]}");
}
}
PR_DEBUG("%s", buff);
op_ret = wd_common_write(STORE_OFF_MOD,buff,strlen(buff));
if(OPRT_OK != op_ret) {
PR_DEBUG("kvs_write err:%02x",op_ret);
}
}
VOID read_saved_stat(VOID)
{
PR_DEBUG("_______________SAVE________________");
OPERATE_RET op_ret = OPRT_OK;
cJSON *root = NULL, *js_power = NULL, *js_ch_stat = NULL;
UINT_T buff_len = 0;
UCHAR_T *buff = NULL;
INT_T i;
op_ret = wd_common_read(STORE_OFF_MOD,&buff,&buff_len);
if(OPRT_OK != op_ret) {
PR_DEBUG("msf_get_single err:%02x",op_ret);
return;
}
PR_DEBUG("read stat: %s", buff);
root = cJSON_Parse(buff);
Free(buff);
if(NULL == root) {
PR_ERR("cjson parse err");
return;
}
js_power = cJSON_GetObjectItem(root, "power");
if(NULL == js_power) {
PR_ERR("cjson get power error");
goto JSON_PARSE_ERR;
}
UCHAR_T count = cJSON_GetArraySize(js_power);
if(count != g_hw_table.channel_num) {
for(i = 0;i< g_hw_table.channel_num; ++i) {
hw_set_channel(i, FALSE);
}
return;
}
for(i=0; i< g_hw_table.channel_num; ++i) {
js_ch_stat = cJSON_GetArrayItem(js_power, i);
if(js_ch_stat == NULL) {
PR_ERR("cjson %d ch stat not found", i);
goto JSON_PARSE_ERR;
}else {
if(js_ch_stat->type == cJSON_True) {
hw_set_channel(i, TRUE);
}else {
hw_set_channel(i, FALSE);
}
}
}
JSON_PARSE_ERR:
cJSON_Delete(root); root = NULL;
}
在涂鸦开发者平台配置升级,设备收到升级推送后调用 gw_ug_inform_cb 回调函数进行固件更新并运行
VOID gw_ug_inform_cb(IN CONST FW_UG_S *fw)
{
PR_DEBUG("Rev GW Upgrade Info");
PR_DEBUG("fw->fw_url:%s", fw->fw_url);
//PR_DEBUG("fw->fw_md5:%s", fw->fw_md5);
PR_DEBUG("fw->sw_ver:%s", fw->sw_ver);
PR_DEBUG("fw->file_size:%d", fw->file_size);
tuya_iot_upgrade_gw(fw, get_file_data_cb, upgrade_notify_cb, NULL);
}
OPERATE_RET get_file_data_cb(IN CONST FW_UG_S *fw, IN CONST UINT_T total_len, IN CONST UINT_T offset,IN CONST BYTE_T *data, IN CONST UINT_T len, OUT UINT_T *remain_len, IN PVOID pri_data)
{
PR_DEBUG("Rev File Data");
PR_DEBUG("Total_len:%d ", total_len);
PR_DEBUG("Offset:%d Len:%d", offset, len);
return OPRT_OK;
}
VOID upgrade_notify_cb(IN CONST FW_UG_S *fw, IN CONST INT_T download_result, IN PVOID pri_data)
{
PR_DEBUG("download Finish");
PR_DEBUG("download_result:%d", download_result);
}
在 app_init
中通过 app_cfg_set
注册产测函数,扫描在 tuya_main.c
中定义的 TEST_SSID,判断信号强度。
如果信号强度大于 -60dB,认为设备信号正常,启动软件定时器,调用 protest_switch_timer_cb
回调函数,进行功能测试。
如果信号强度小于 -60dB,认为设备信号异常,做出相应指示,引导用户进行问题排查。
#define TEST_SSID "tuya_mdev_test1"
VOID app_init(VOID)
{
app_cfg_set(GWCM_LOW_POWER,prod_test);
}
STATIC VOID prod_test(IN BOOL_T flag, IN CHAR_T rssi)
{
OPERATE_RET op_ret = OPRT_OK;
PR_DEBUG("dev_test_start_cb");
PR_DEBUG("rssi:%d", rssi);
if((rssi<-60) ||(flag == FALSE)) {
PR_ERR("rssi:%d,flag:%d",rssi,flag);
return;
}
op_ret = sys_add_timer(protest_switch_timer_cb,NULL,&g_hw_table.switch_timer);
if(OPRT_OK != op_ret) {
PR_ERR("sys_add_timer switch_timer err");
return ;
}
op_ret = prod_test_init_hw(&g_hw_table);
if(OPRT_OK != op_ret) {
PR_ERR("prod_test_init_hw err");
return ;
}
func_select.prod_flag = TRUE;
}
STATIC VOID protest_switch_timer_cb(IN UINT timerID, IN PVOID pTimerArg)
{
STATIC UCHAR_T act_cnt = 0;
STATIC UCHAR_T target_ch = 0;
PR_DEBUG("all relay timer callback");
if( target_ch < get_all_ch_num()) {
hw_trig_channel(target_ch);
act_cnt++;
}
if(target_ch >= get_all_ch_num()) {
act_cnt = 0;
target_ch = 0;
}else if(act_cnt < 6) {
sys_start_timer(g_hw_table.switch_timer, RELAY_ACT_INTER, TIMER_ONCE);
}else{
act_cnt = 0;
target_ch++;
sys_start_timer(g_hw_table.switch_timer, RELAY_ACT_INTER, TIMER_ONCE);
}
}
在开发过程中,您应该加入设备状态的监控函数和打印信息,以便及时发现异常,解决相关问题。Demo 代码中通过启动软件定时器,调用 memory_left_timer_cb
回调函数,监控内存信息,确认设备运行正常。
OPERATE_RET device_init(VOID)
{
**************************************
op_ret = sys_add_timer(memory_left_timer_cb, NULL, &func_select.memory_left_timer);
if(OPRT_OK != op_ret) {
return op_ret;
}else {
sys_start_timer(func_select.memory_left_timer,2*1000,TIMER_CYCLE);
}
INT_T size = tuya_hal_system_getheapsize();
PR_DEBUG("device_init ok free_mem_size:%d",size);
return OPRT_OK;
}
SDK 内提供编译脚本,您可以通过如下命令调用脚本可实现代码编译。
编译指令:进入到编译脚本所在目录,执行 ./build_app.sh apps/<项目名称> <项目名称> <版本号>
。
命令示例:
./build_app.sh apps/one_plug_demo one_plug_demo 1.0.0
编译成功后会在 apps/项目名称/output/版本号/xxx.bin
目录下生成烧录用的二进制(bin)文件,如下图片所示:
编译出的 bin 文件,名称中含 QIO 和 DOUT 尾缀的是生产固件,需要根据自己用的 Flash 的工作模式进行选择,生产时使用,名称中含 _(1)
和 _(2)
的是用户区固件,调试阶段使用,名称中含 _ug
的是升级固件,OTA时使用。
文件名 | 说明 |
---|---|
one_plug_demo_(1)_1.0.0.bin | 用户区固件,用于芯片烧录工具使用 |
one_plug_demo_(2)_1.0.0.bin | 用户区固件,用于芯片烧录工具使用 |
one_plug_demo_DOUT_1.0.0.bin | 生产固件,用于模组Flash工作方式为DOUT的模组 |
one_plug_demo_QIO_1.0.0.bin | 生产固件,用于模组Flash工作方式为QIO的模组 |
one_plug_demo_ug_1.0.0.bin | 升级固件,用于上传涂鸦开发者平台用户区固件和 OTA 固件 |
将固件上传至涂鸦开发者平台,申请激活码,通过涂鸦 云模组烧录授权平台 写入授权信息,详情参考 通用 Wi-Fi 烧录、授权和产测 。
在调试过程中可以通过添加打印信息,监控设备的运行状态,调试代码。
涂鸦通用 SDK 的日志等级有以下几种分类:
您可以通过设置日志的等级来决定哪些日志信息输出,例如设置日志输出等级为TY_LOG_LEVEL_INFO
,则ERR
、WARN
、NOTICE
、INFO
相关信息会显示,DEBUG
、TRACE
则不会显示。
/* tuya sdk definition of log level */
typedef INT_T LOG_LEVEL;
#define TY_LOG_LEVEL_ERR 0 // 错误信息,程序正常运行不应发生的信息
#define TY_LOG_LEVEL_WARN 1 // 警告信息
#define TY_LOG_LEVEL_NOTICE 2 // 需要注意的信息
#define TY_LOG_LEVEL_INFO 3 // 通知信息
#define TY_LOG_LEVEL_DEBUG 4 // 程序运行调试信息,RELEASE版本中删除
#define TY_LOG_LEVEL_TRACE 5 // 程序运行路径信息,RELEASE版本中删除
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈