当前位置: 代码网 > it编程>编程语言>C/C++ > 【嵌入式裸机开发】智能家居入门2(MQTT协议、微信小程序、STM32、ONENET云平台)

【嵌入式裸机开发】智能家居入门2(MQTT协议、微信小程序、STM32、ONENET云平台)

2024年08月06日 C/C++ 我要评论
这里给出前两篇使用http协议博客的网址:在第二篇的博客结尾提出了服务器经常掉线的问题,使用别人编写好的心跳包也没用,因为程序一直卡死在重连中,所以也可以参照本篇的解决办法:加上独立看门狗,隔一段时间喂狗,如果超过时间没喂狗,说明程序多半卡死,此时看门狗处理逻辑中执行初始化esp8266和连接服务器的操作。

此篇智能家居入门与前两篇类似,但是是使用mqtt协议接入onenet云平台,实现微信小程序与下位机的通信,这里相较于使用http协议的那两篇博客,在主程序中添加了独立看门狗防止程序卡死和服务器掉线问题。后续还有使用mqtt协议连接mqtt服务器的智能家居项目。

前言

这里给出前两篇使用http协议博客的网址:
①实现数据上云:
②实现小程序控制下位机:
在第二篇的博客结尾提出了服务器经常掉线的问题,使用别人编写好的心跳包也没用,因为程序一直卡死在重连中,所以也可以参照本篇的解决办法:加上独立看门狗,隔一段时间喂狗,如果超过时间没喂狗,说明程序多半卡死,此时看门狗处理逻辑中执行初始化esp8266和连接服务器的操作。
③独立看门狗:https://www.bilibili.com/video/bv1th411z7sn/?p=46&spm_id_from=pagedriver&vd_source=2a10d30b8351190ea06d85c5d0bfcb2a
④由于使用到了mqtt协议,想深入了解源码的话可以看看之前的的博客,对mqtt协议进行详解:

一、硬件模块

1、0.96寸oled液晶显示屏
2、dht11温湿度传感器
3、继电器
4、小风扇、小水泵
5、mq-4、mq-9
6、
7、jlink下载器
8、stm32f103c8t6
大部分在上面说的第一篇博客中有介绍,这里介绍一下继电器、小风扇、小水泵:
①继电器:
由于单片机的io口无法直接驱动小风扇和水泵,这里用继电器充当开关,使用继电器驱动小风扇和小水泵运动。
在这里插入图片描述输入端口:外接5v电源,黑色跳帽可以控制继电器是高电平触发还是低电平触发。
输出端口:从左往右看,分别是no1/no2,com1/com2,nc1/nc2,代表常开,公共端,常闭三种状态。
继电器的两种状态,开和关,当继电器触发时,为打开状态,non口就会被使能,当继电器为关闭状态时,ncn口就会被使能,中间作为公共端连接输出。

硬件连线:
继电器输出端:小风扇红黑两根线子,红线接入继电器的no1/no2口,继电器的com1/com2拿一根公母杜邦线引出来接入5v电源,小风扇的黑线接stm32的gnd口。
在这里插入图片描述
继电器输入端:
在这里插入图片描述
原文链接:

②小风扇、小水泵:
这两个小东西就只有电源线和底线,给5v输入即可转动起来:
在这里插入图片描述
在这里插入图片描述

二、连接服务器测试

如果想单独先使用esp8266测试是否能正常与服务器通信的话可以参考这篇博客,不会创建产品也可以看:

需要注意的点:
①创建产品时要选择mqtt协议那个目录下进行创建
②at指令中的ip地址和端口号要换成:183.230.40.39 6002
③需要记住的有:产品id、鉴权信息(创建产品时自己填写的)、master-apikey、设备id

三、两个协议的对比分析

1、代码结构上:

相较于使用http协议连接onenet,使用mqtt协议需要多几个源文件:onenet.c、cjson.c、mqttkit.c。
①onenet.c是与onenet平台的数据交互接口层,通过调用mqttkit.c和esp8266.c中的库函数实现与云平台的通信,
②mqttkit.c是mqtt协议库,是mqtt协议最底层,由onenet.c进行调用。前面两个都是大神张继瑞开源。
③cjson.c是一个用于处理json数据格式的轻量级c语言库,json(javascript object notation)是一种轻量级的数据交换格式,常用于在不同系统之间传输和存储数据。在对云平台下发数据进行解析时会用到(onenet.c)。
在这里插入图片描述

2、获取服务器数据上:

******使用http协议和mqtt协议连接onenet,最大的不同就是获取onenet云平台数据的方式:
http协议:直接构建http请求报文并调用esp8266_send_data函数发送http请求,并获取返回的字符串。
mqtt协议:不主动发送请求,使用esp8266_waitrecive函数等待,如果云平台有数据下发,那就存起来当作变量传入onenet_revpro函数,进行消息解析。

3、架构上:

在这里插入图片描述
"请求/响应"和"发布/订阅"是两种不同的通信模式,它们在系统架构和通信方式上有所不同。
①请求/响应:
特点: 在请求/响应模式中,通信的一方发送请求,而另一方回复响应。通信是单向的,有一个明确的请求者和一个响应者。
用途: 这种模式常见于客户端和服务器之间的通信。客户端发送请求,服务器处理请求并返回相应的响应。
②发布/订阅:
特点: 在发布/订阅模式中,消息的发布者将消息发送到一个主题(topic),而订阅者可以选择订阅特定主题以接收相关消息。通信是多对多的,发布者和订阅者之间是松耦合的。
用途: 这种模式常见于事件驱动系统、消息中间件和实时数据更新场景。发布者发布消息到主题,所有订阅该主题的订阅者都会收到消息。
由以上分析很容易得出一个结论:在做环境信息检测这种实时数据更新的项目上,使用mqtt协议更具优势。

四、下位机主要代码

1、接收并解析云平台下发数据:

主循环中使用以下代码接收并跳转:

		dataptr = esp8266_getipd(10);
		if(dataptr != null)
		{
			timecount = 0;
			onenet_revpro(dataptr);
		
		}

接收到数据后跳转至onenet.c的onenet_revpro函数中进行mqtt数据包(报文)解包和数据解析:

void onenet_revpro(unsigned char *cmd)
{
	
	mqtt_packet_structure mqttpacket = {null, 0, 0, 0};								//协议包
	
	char *req_payload = null;
	char *cmdid_topic = null;
	
	unsigned short req_len = 0;
  unsigned char type = 0;

	short result = 0;

	char *dataptr = null;
	char numbuf[10];
	int num = 0;

	
	cjson *json , *json_value;
  cjson *json1, *json_value1;
  cjson *json2, *json_value2;

	type = mqtt_unpacketrecv(cmd);

	switch(type)
	{
		case mqtt_pkt_cmd:															//命令下发
			oled_refresh_line("jinru111");
			result = mqtt_unpacketcmd(cmd, &cmdid_topic, &req_payload, &req_len);	//解出topic和消息体
			if(result == 0)
			{
				//打印收到的信息
				printf(  "cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);
				
				// 对数据包req_payload进行json格式解析
				json = cjson_parse(req_payload);
				
				if (!json)//如果json内容为空,则打印错误信息
					printf("error before: [%s]\n",cjson_geterrorptr());
				else
				{
					json_value = cjson_getobjectitem(json , "led0");//提取对应属性的数值
					
					if((json_value->valueint)==1)
					{
						gpio_setbits(gpiob,gpio_pin_12);	
					}
					else if((json_value->valueint)==0)			
						gpio_resetbits(gpiob,gpio_pin_12);	
				}
				//开关风扇
				json1 = cjson_parse(req_payload);
				if (!json1)
					printf("error before: [%s]\n",cjson_geterrorptr());
				else
				{
					json_value1 = cjson_getobjectitem(json1 , "feng");

					if((json_value1->valueint)==1)
					
						gpio_setbits(gpiob,gpio_pin_5);	
					
					else if((json_value1->valueint)==0)
						
						gpio_resetbits(gpiob,gpio_pin_5);	
				
				}
				//开关水泵
				json2 = cjson_parse(req_payload);
				if (!json2)
					printf("error before: [%s]\n",cjson_geterrorptr());
				else
				{
					json_value2 = cjson_getobjectitem(json2 , "shui");

					if((json_value2->valueint)==1)
					{
						gpio_setbits(gpiob,gpio_pin_6);	
					}
					else if((json_value2->valueint)==0)
						gpio_resetbits(gpiob,gpio_pin_6);	
				
				}

				if(mqtt_packetcmdresp(cmdid_topic, req_payload, &mqttpacket) == 0)	//命令回复组包
				{
					printf( "tips:	send cmdresp\r\n");
					
					esp8266_senddata(mqttpacket._data, mqttpacket._len);			//回复命令
					mqtt_deletebuffer(&mqttpacket);									//删包
				}
				cjson_delete(json);//释放位于堆中cjson结构体内存
				cjson_delete(json1);
			}
		
		break;
			
		case mqtt_pkt_puback:														//发送publish消息,平台回复的ack
		
			if(mqtt_unpacketpublishack(cmd) == 0)
				//printf(  "tips:	mqtt publish send ok\r\n");
			
		break;
		
		default:
			result = -1;
		break;
	}
	
	esp8266_clear();									//清空缓存
	
	if(result == -1)
		return;
	
	dataptr = strchr(req_payload, ':');					//搜索'}'

	if(dataptr != null && result != -1)					//如果找到了
	{
		dataptr++;
		
		while(*dataptr >= '0' && *dataptr <= '9')		//判断是否是下发的命令控制数据
		{
			numbuf[num++] = *dataptr++;
		}
		numbuf[num] = 0;
		
		num = atoi((const char *)numbuf);				//转为数值形式
	}

	
	
	if(type == mqtt_pkt_cmd || type == mqtt_pkt_publish)
	{
		mqtt_freebuffer(cmdid_topic);
		mqtt_freebuffer(req_payload);
	}
}

2、传感器数据上云:

主函数中读取传感器数值,每隔一段时间上传:

		else if(timecount >= 100)	//发送间隔
		{
			dht11_read_data(&tempvalue,&humidity);
			gas = ad_getvalue(adc_channel_2);
			ranqi = ad_getvalue(adc_channel_3);

			delay_ms(10);
					
			onenet_senddata();//发送数据给onenet
			esp8266_clear();	
			timecount = 0;
		}

调用onenet_senddata发送数据,在onenet_senddata函数中又调用onenet_fillbuf函数将要发送的数据拼接起来。

unsigned char onenet_fillbuf(char *buf)
{
	
	char text[32];
	
	//led0_flag=gpio_readinputdatabit(gpiob, gpio_pin_12);//读取led的开关状态(即对应引脚的)
	//led1_flag=gpio_readinputdatabit(gpiob, gpio_pin_13);

	memset(text, 0, sizeof(text));
	
	strcpy(buf, ",;");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "tempreture,%d;",tempvalue);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "humidity,%d;", humidity);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "tianranqi,%d;", gas);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "keranqiti,%d;", ranqi);
	strcat(buf, text);
	
	printf("buf_mqtt=%s\r\n",buf);
	return strlen(buf);

}

//==========================================================
//	函数名称:	onenet_senddata
//
//	函数功能:	上传数据到平台
//
//	入口参数:	type:发送数据的格式
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void onenet_senddata(void)
{
	
	mqtt_packet_structure mqttpacket = {null, 0, 0, 0};												//协议包
	
	char buf[128];
	
	short body_len = 0, i = 0;
	
	//printf( "tips:	onenet_senddata-mqtt\r\n");
	
	memset(buf, 0, sizeof(buf));//清空数组内容
	
	body_len = onenet_fillbuf(buf);																	//获取当前需要发送的数据流的总长度
	
	if(body_len)
	{
		if(mqtt_packetsavedata(devid, body_len, null, 5, &mqttpacket) == 0)							数据点上传组包,将数据封装成mqtt协议所要求的数据包格式
		{
		{
			for(; i < body_len; i++)
				mqttpacket._data[mqttpacket._len++] = buf[i];
			
			esp8266_senddata(mqttpacket._data, mqttpacket._len);									//上传数据到平台
			printf( "send %d bytes\r\n", mqttpacket._len);
			
			mqtt_deletebuffer(&mqttpacket);															//删包
		}
		else{
			printf(  "warn:	edp_newbuffer failed\r\n");
		}
	}
	
}

五、微信小程序主要代码

在这里插入图片描述
图中圈起来的地方需要根据自己的数据进行填写,在下面的调试信息栏可以看到有数据上传,查看数据流即可:
在这里插入图片描述

1、index.js

主要是增加了控制风扇和水泵的代码:

feng_kai:function(){
  //按钮发送命令控制硬件
   wx.request({
     url:'https://api.heclouds.com/cmds?device_id=1108129261',
     header: {
       'content-type': 'application/json',
       'api-key':'nv4cso3uqmzr2egqqiy49mrcfx8='
     },
     method: 'post',
     data:{"feng":1},
     success(res){
       console.log("成功",res.data)
     },
     fail(res){
       console.log("失败",res)
     }
   })
},

feng_guan:function(){
  //按钮发送命令控制硬件
   wx.request({
     url:'https://api.heclouds.com/cmds?device_id=1108129261',
     header: {
       'content-type': 'application/json',
       'api-key':'nv4cso3uqmzr2egqqiy49mrcfx8='
     },
     method: 'post',
     data:{"feng":0},
     success(res){
       console.log("成功",res.data)
     },
     fail(res){
       console.log("失败",res)
     }
   })
},

shui_kai:function(){
  //按钮发送命令控制硬件
   wx.request({
     url:'https://api.heclouds.com/cmds?device_id=1108129261',
     header: {
       'content-type': 'application/json',
       'api-key':'nv4cso3uqmzr2egqqiy49mrcfx8='
     },
     method: 'post',
     data:{"shui":1},
     success(res){
       console.log("成功",res.data)
     },
     fail(res){
       console.log("失败",res)
     }
   })
},

shui_guan:function(){
  //按钮发送命令控制硬件
   wx.request({
     url:'https://api.heclouds.com/cmds?device_id=1108129261',
     header: {
       'content-type': 'application/json',
       'api-key':'nv4cso3uqmzr2egqqiy49mrcfx8='
     },
     method: 'post',
     data:{"shui":0},
     success(res){
       console.log("成功",res.data)
     },
     fail(res){
       console.log("失败",res)
     }
   })
},

需要注意的是这里的url需要更换成:‘https://api.heclouds.com/cmds?device_id=11081xxxxxx’,并且文件开头的apikey和设备号也要换成自己的。

2、index.wxml

最后加上:

<button type="primary" style="margin-top: 20px;" bindtap="feng_kai">开风扇</button>
<button type="warn" bindtap="feng_guan">关风扇</button>

<button type="primary" style="margin-top: 20px;" bindtap="shui_kai">开水泵</button>
<button type="warn" bindtap="shui_guan">关水泵</button>
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com