前言
之前我们介绍了如何在涂鸦智能平台创建智能设备, 以及开发环境搭建, 今天我们就来分析源码,一步步了解对接涂鸦云过程以及API接口,文章所涉及的工程源码,我会上传到我的github:
https://github.com/astonb/tuya_platform,需要的小伙伴自行下载。喜欢diy的小伙伴可以参考此工程,结合以下的源码分析,可以改造成自己的智能设备,实现远程控制。好了,进入我们的正题~
分析
首先需要知道应用入口函数是: OPERATE_RET device_init(VOID) ,在device.c文件中。
Kotlin
OPERATE_RET device_init(VOID)
{
OPERATE_RET op_ret;
PR_NOTICE("fireware info name:%s version:%s", APP_BIN_NAME, USER_SW_VER);
//Product_Init();//1. 涂鸦设备初始化, 参数: pid, app控制回调
op_ret = tuya_device_init(PRODECT_KEY, device_cb, USER_SW_VER);
if (op_ret != OPRT_OK)
{
return op_ret;
}
tuya_active_reg(syn_active_data);
op_ret = sys_add_timer(syn_timer_cb, NULL, &Syntimer);
if(OPRT_OK != op_ret)
{
PR_ERR("add syn_timer err");
return op_ret;
}
else
{
sys_start_timer(Syntimer, SYN_TIME*1000, TIMER_CYCLE);
}
Product_Init();
op_ret = device_differ_init();
if (op_ret != OPRT_OK)
{
return op_ret;
}
return op_ret;
} |
分析:
tuya_device_init(PRODECT_KEY, device_cb, USER_SW_VER);
功能:该函数主要完成涂鸦设备初始化。
参数1:设备pid,pid在涂鸦平台创建智能设备时获取,大家根据自己创建的设备为准
参数2:在app操作,下发指令给设备的回调函数:
C++
VOID device_cb(SMART_CMD_E cmd,cJSON *root){
CHAR *buf = cJSON_PrintUnformatted(root);
if (NULL == buf)
{
PR_ERR("malloc error");
return;
}
PR_DEBUG("root cmd:%s",buf);
//实际控制设备
device_ControlByApp(root);
Free(buf);
} |
如:上述例子中的, 将收到的指令传递给device_ControlByApp(root)函数,完成实际对设备的控制:
Markdown
/*********
* Function: Motor_ControlByApp
* Description: 接受app控制处理函数
* Input: root命令
* Output:
* Return:
*********/
void device_ControlByApp(cJSON *root)
{
cJSON *cmd = cJSON_GetObjectItem(root, DP_control); //DP_control是在涂鸦平台定义的数据点 if(cmd != NULL){ switch_dev.statu = (bool)cmd->valueint; tuya_write_gpio_level(WF_SWITCH_GPIO_PIN, switch_dev.statu); } cmd = cJSON_GetObjectItem(root, DP_delayoff);////DP_delayoff是在涂鸦平台定义的数据点 if(cmd != NULL){ switch_dev.delayoff = cmd->valueint; } PostSemaphore(Upload_SemHandle);
} |
说明:DP_control 和 DP_delayoff 是在涂鸦平台定义的设备数据点:
PostSemaphore(Upload_SemHandle); 是 二值信号量(作用:任务同步,因为我们在另外开了一个任务,专门用于上报设备状态,如下代码),用于上报设备状态的, 这里我们控制完设备后, 就应该主动上报下设备状态,以便app能实时。
C++
//================上报设备状态的任务====================void MSG_Upload_Poll_Task(PVOID pArg){
OPERATE_RET ret;
//创建信号量
Upload_SemHandle = CreateSemaphore();
if(Upload_SemHandle == NULL)
{
PR_ERR("Create Upload_Sem failed");
ThrdJoin(Upload_TaskHandle, NULL);
return;
}
//初始化为二值信号量,且初始化值为0
InitSemaphore(Upload_SemHandle, 0, 1);
//创建互斥量,用于上报设备状态时加锁(这里可以省去,不影响)
ret = CreateMutexAndInit(&Upload_mutexHandle);
if(ret != OPRT_OK)
{
PR_ERR("Create Upload_Mutex failed");
ThrdJoin(Upload_TaskHandle, NULL);
return;
}
while(1)
{
//一直等待信号量,等到了就上报设备状态if(WaitSemaphore(Upload_SemHandle) == OPRT_OK)
{
ret = msg_upload_proc();
if(ret != OPRT_OK)
PR_ERR("msg_upload failed!");
}
}
} |
其中msg_upload_proc();就是实现设备状态上报:
Markdown
/*********
* Function: msg_upload_proc
* Description: dp上报函数
* Input:
* Output:
* Return:
*********/
INT msg_upload_proc(void)
{
OPERATE_RET err;
//判断连接涂鸦云的状态,如果是断开的,也就没必要上报了
if(tuya_get_cloud_stat() == FALSE) return OPRT_DP_REPORT_CLOUD_ERR; MutexLock(Upload_mutexHandle);
//判断wifi连接状态,如果不是正常连接,那也没必要上报了
GW_WIFI_STAT_E wf_stat = tuya_get_wf_status(); if(STAT_UNPROVISION == wf_stat || \ STAT_STA_UNCONN == wf_stat || \ (tuya_get_gw_status() != STAT_WORK)) { err = OPRT_WF_CONN_FAIL; goto exit; }
//================以下是设备数据点格式转化==================
cJSON *root = cJSON_CreateObject(); if(NULL == root) { err = OPRT_BUF_NOT_ENOUGH; goto exit; } //根据实际情况上报设备数据点 cJSON_AddBoolToObject(root, DP_control, switch_dev.statu); cJSON_AddNumberToObject(root, DP_delayoff, switch_dev.delayoff); CHAR *out = cJSON_PrintUnformatted(root); cJSON_Delete(root); if(NULL == out) { PR_ERR("cJSON_PrintUnformatted err:"); err = OPRT_CJSON_PARSE_ERR; goto exit; } PR_DEBUG("out[%s]", out);
//=======================end===========================
//最终调用该函数实现数据点上报
OPERATE_RET op_ret = tuya_obj_dp_trans_report(out); Free(out); if( OPRT_OK == op_ret ) { err = OPRT_OK; goto exit; }else { PR_DEBUG("msg upload error"); err = OPRT_DP_REPORT_CLOUD_ERR; goto exit; }
exit:
MutexUnLock(Upload_mutexHandle); return err;
} |
以上我们完成了设备控制和状态上报功能。
回到device_init用户
主函数
中,接着继续分析下面的代码:
tuya_active_reg(syn_active_data);
该函数作用是:当设备连接涂鸦云成功激活的回调函数,主要是上报下设备状态,相当于设备重新上电启动连接云端时,同步下app显示的设备状态。
参数: syn_active_data
Plain Text
STATIC VOID syn_active_data(VOID)
{
PR_DEBUG("sys active data");
if(tuya_get_cloud_stat())
PostSemaphore(Upload_SemHandle);
elsePR_DEBUG("cloud unconnect !!!");
} |
其函数主要是判断下设备是否成功连接云端,如果不是,则直接退出,如果是,则上报设备状态。
继续device_init代码块:
C++
op_ret = sys_add_timer(syn_timer_cb, NULL, &Syntimer);
if(OPRT_OK != op_ret)
{
PR_ERR("add syn_timer err");
return op_ret;
}
else
{
sys_start_timer(Syntimer, SYN_TIME*1000, TIMER_CYCLE);
} |
以上是开启一个定时器, 每2秒(SYN_TIME*1000)去查询连接云端状态,我们看下定时器的回调函数:
Plain Text
STATIC VOID syn_timer_cb(UINT timerID,PVOID pTimerArg)
{
PR_DEBUG("syn timer cb ...");
if( FALSE == tuya_get_cloud_stat() ) {
return;
}
PostSemaphore(Upload_SemHandle);
sys_stop_timer(Syntimer);
} |
可以看到其作用和上面的syn_active_data一样, 只是定时器是主动查询。
Product_Init();
C++
void Product_Init(void){
//设备初始化memset(&switch_dev, 0x0, sizeof(switch_dev));
//设置GPIO
PIN_FUNC_SELECT(WF_SWITCH_GPIO_MUX, WF_SWITCH_GPIO_FUNC);
GPIO_DIS_OUTPUT(WF_SWITCH_GPIO_PIN);//设置成输入模式//内部实现了按键轮询事件(实现了一键配网功能, 具体见代码)
Key_Init();
OPERATE_RET op_ret;
//用于延迟关闭检测定时器
op_ret = sys_add_timer(Timer_delayoff, NULL, &delayoffTimer);
if(OPRT_OK != op_ret) {
PR_ERR("add Timer_TaskKEY err");
//return op_ret;
}else{
sys_start_timer(delayoffTimer, 1000, TIMER_CYCLE);
}
} |
该函数是我们自己定义实现的,其函数内部主要是初始化了相关的gpio, 以及按键轮询事件定时器,主要是为了实现一键配网功能(以便更换一个网络,再次对设备进行配网)
其中switch_dev是设备属性的结构体,用于管理设备(我这里是开关为例子),
device_init
主函数
中的最后一个函数device_differ_init:
Nginx
op_ret = device_differ_init();
if (op_ret != OPRT_OK)
{
return op_ret;
} |
其作用是设备的一些个性化初始化, 其实和上面的Product_Init()函数一样,只是涂鸦sdk规范了结构而已。 具体看代码:
C++
STATIC OPERATE_RET device_differ_init(VOID){
OPERATE_RET op_ret;
TIMER_ID timer;
CreateAndStart(&Upload_TaskHandle, MSG_Upload_Poll_Task, NULL, 2048, TRD_PRIO_2, "msg_upload_task");
op_ret = sys_add_timer(wfl_timer_cb, NULL, &timer);
if (OPRT_OK != op_ret)
{
return op_ret;
}
else
{
sys_start_timer(timer, 300, TIMER_CYCLE);
}
return OPRT_OK;
} |
可以看到在这里我们创建了上面提及的设备状态上报任务MSG_Upload_Poll_Task, 以及创建了一个wifi状态定时查询的定时器:
PHP
STATIC VOID wfl_timer_cb(UINT timerID, PVOID pTimerArg)
{
STATIC UINT last_wf_stat = 0xffffffff;
GW_WIFI_STAT_E wf_stat = tuya_get_wf_status();
//The system is about to restart without monitoring!if (last_wf_stat != wf_stat)
{
PR_DEBUG("wf_stat:%d",wf_stat);
switch (wf_stat)
{
case STAT_UNPROVISION:
PR_DEBUG("STAT_UNPROVISION");
break;
case STAT_AP_STA_UNCONN:
case STAT_AP_STA_CONN:
PR_DEBUG("STAT_AP_STA_UNCONN");
break;
case STAT_STA_CONN:
PR_DEBUG("STAT_STA_CONN");
break;
default:
break;
}
last_wf_stat = wf_stat;
}
} |
在wifi定时查询状态回调中,可以实现不同状态,不同led状态显示, 方便用户根据led状态,知道设备目前是处于啥情况,是断网了呢, 还是成功连接着路由器,还是断开了云端等等(根据自己的情况处理即可)。
编译下载,控制设备
至此,主线的代码已经简单分析完了,剩余一些,如一键配网等函数,很简单,大家可以自行查阅代码,有任何不明白的都可私信或直接在github中提问都可。接下来我们就编译我们的工程,
根据上一篇的开发环境搭建,我们打开虚拟机,进入到sdk下的app,输入以下指令:
CSS
sh build_app.sh wifi_switch 1.0.0 |
编译过程:
可以到sdk路径/bin/upgrade目录中,查看我们编译得到的固件
说明:wifi_switch(1)_1.0.0.bin 是 烧录固件
wifi_switch(2)_1.0.0.bin 是OTA在线升级固件
接着我们烧录我们的固件,打开乐鑫的下载工具:
ESPFlashDownloadTool_v3.6.4.exe
根据以下设置(TYWE1S模组,若使用其他模组,根据实际情况下载):
好了,看到“FINSH 完成”就表明下载成功, 接着下载涂鸦智能app(可到涂鸦官网或手机应用搜索”涂鸦智能“自行下载,这里不方便贴出二维码~~)
打开app后,我们可以使用“手动搜索” 或 ”自动搜索“ 添加设备(当然此时设备要上电啦~)
成功添加后,可以进入我们的设备控制主界面:
之后我们就可以进行控制啦~ (同时我们可以将设备连接电脑串口,观察app控制时,串口接收到数据)
小结
好了,以上就是涂鸦sdk源码分析和下载验证,相信经过上面的介绍,小伙伴参看代码可以一步步实现自己的智能设备啦,开始行动吧~, 当然有什么不明白的地方,可以私信或下方评论。 另外如果大家对sdk这种方式理解有点困难(涉及一些操作系统知识,如信号量,互斥量,任务等概念), 之后我会另开一篇文章介绍如何使用mcu串口对接, 方便大家更好的入手。 OK, 今天先到此,感兴趣的小伙伴记得关注,收藏,转发~