龙空技术网

物联网之——基于涂鸦sdk二次开发源码分析(三)

嵌入式笔记v 2097

前言:

当前咱们对“c语言operate函数”都比较讲究,兄弟们都需要了解一些“c语言operate函数”的相关文章。那么小编在网络上网罗了一些有关“c语言operate函数””的相关知识,希望咱们能喜欢,兄弟们一起来学习一下吧!

万物互联iot,你准备好了嘛

前言

之前我们介绍了如何在涂鸦智能平台创建智能设备, 以及开发环境搭建, 今天我们就来分析源码,一步步了解对接涂鸦云过程以及API接口,文章所涉及的工程源码,我会上传到我的github:,需要的小伙伴自行下载。喜欢diy的小伙伴可以参考此工程,结合以下的源码分析,可以改造成自己的智能设备,实现远程控制。好了,进入我们的正题~

分析首先需要知道应用入口函数是: OPERATE_RET device_init(VOID) ,在device.c文件中。

	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;}

分析:

1. tuya_device_init(PRODECT_KEY, device_cb, USER_SW_VER);

功能:该函数主要完成涂鸦设备初始化。

参数1:设备pid,pid在涂鸦平台创建智能设备时获取,大家根据自己创建的设备为准

参数2:在app操作,下发指令给设备的回调函数:

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)函数,完成实际对设备的控制:

/************************************************************  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能实时。

//================上报设备状态的任务====================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();就是实现设备状态上报:

/************************************************************  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

STATIC VOID syn_active_data(VOID){	PR_DEBUG("sys active data");	if(tuya_get_cloud_stat())		PostSemaphore(Upload_SemHandle);	else		PR_DEBUG("cloud unconnect !!!");}

其函数主要是判断下设备是否成功连接云端,如果不是,则直接退出,如果是,则上报设备状态。

继续device_init代码块:

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)去查询连接云端状态,我们看下定时器的回调函数:

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();

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:

op_ret = device_differ_init();	if (op_ret != OPRT_OK)	{		return op_ret;	}

其作用是设备的一些个性化初始化, 其实和上面的Product_Init()函数一样,只是涂鸦sdk规范了结构而已。 具体看代码:

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状态定时查询的定时器:

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,输入以下指令:

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, 今天先到此,感兴趣的小伙伴记得关注,收藏,转发~

标签: #c语言operate函数