• 【智能家居入门2】(MQTT协议、微信小程序、STM32、ONENET云平台)


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

    前言

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

    一、硬件模块

    1、0.96寸OLED液晶显示屏
    2、DHT11温湿度传感器
    3、继电器
    4、小风扇、小水泵
    5、MQ-4、MQ-9
    6、esp8266-01s或者esp-12f
    7、JLink下载器
    8、STM32F103C8T6
    大部分在上面说的第一篇博客中有介绍,这里介绍一下继电器、小风扇、小水泵:
    ①继电器:
    由于单片机的io口无法直接驱动小风扇和水泵,这里用继电器充当开关,使用继电器驱动小风扇和小水泵运动。
    在这里插入图片描述输入端口:外接5V电源,黑色跳帽可以控制继电器是高电平触发还是低电平触发。
    输出端口:从左往右看,分别是NO1/NO2,COM1/COM2,NC1/NC2,代表常开,公共端,常闭三种状态。
    继电器的两种状态,开和关,当继电器触发时,为打开状态,NOn口就会被使能,当继电器为关闭状态时,NCn口就会被使能,中间作为公共端连接输出。

    硬件连线:
    继电器输出端:小风扇红黑两根线子,红线接入继电器的NO1/NO2口,继电器的COM1/COM2拿一根公母杜邦线引出来接入5V电源,小风扇的黑线接STM32的GND口。
    在这里插入图片描述
    继电器输入端:
    在这里插入图片描述
    原文链接:https://blog.csdn.net/qq_51868810/article/details/127792611

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

    二、连接服务器测试

    如果想单独先使用esp8266测试是否能正常与服务器通信的话可以参考这篇博客,不会创建产品也可以看:
    https://blog.csdn.net/m0_71523511/article/details/135887108
    需要注意的点:
    ①创建产品时要选择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);
    		
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接收到数据后跳转至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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137

    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;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    调用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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    五、微信小程序主要代码

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

    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)
         }
       })
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

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

    2、index.wxml

    最后加上:

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    六、源码获取

    我用夸克网盘分享了「智能家居(MQTT+ONENET).rar」,点击链接即可保存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
    链接:https://pan.quark.cn/s/db251fd599bd

    提取码:9sR9

  • 相关阅读:
    (31)Verilog实现单bit数据时钟域转换【快到慢】
    最近石家庄爆火的社区团购模式系统,你知道吗?
    【车载开发系列】CAN总线知识进阶篇
    git区域与对象
    Vue事件绑定(v-on用法)
    Jenkins
    软件测试 | 软件测试面试题大全(带回答),offer拿来吧你......
    技术内幕 | StarRocks 支持 Apache Hudi 原理解析
    网络攻击中常见掩盖真实IP的攻击方式及虚假IP地址追踪溯源方法
    浅析Kubernetes架构之workqueue
  • 原文地址:https://blog.csdn.net/m0_71523511/article/details/136176283