基于STM32与OneNet平台的智能家居系统设计(代码开源含自制APP代码)

2023-05-16

        前言:本文为手把手教学的基础物联网开发设计,项目包含对下位机MCU对外设数据读取与控制)和上位机包含服务平台和APP端)的设计。下位机选取STM32作为MCU,外设有LED灯DHT11温湿度传感器。上位机则选用中国移动旗下的OneNet平台作为服务器,考虑到未来物联网的开发大多数是基于手机APP的。(OneNet平台自家的APP已经下架)所以,笔者在这里使用uniapp这款简单易入手的软件教大家制作属于自己的物联网上位机。本项目下位机代码基于HAL库编程,上位机的APP则基于vue(很简单),保证一篇文章实现基础的物联网开发。(本文有代码开源!

        实验硬件:STM32F103ZET6;0.96寸OLED(128×64);ESP8266,DHT11;LED;KEY

        硬件实物图:

        效果图:

        引脚连接:

OLED模块引脚:

VCC --> 3.3V

GND --> GND

SCL --> PB10

SDA --> PB11

ESP8266模块引脚:

VCC --> 3.3V

GND --> GND

RX--> PB10

TX --> PB11

RST --> PB9

EN --> PB7

DHT11传感器引脚:

VCC --> 3.3V

GND --> GND

DATA-->PE0

一、物联网

1.1 物联网简介

       物联网(Internet of Things,简称IoT)是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器、激光扫描器等各种装置与技术,实时采集任何需要监控、 连接、互动的物体或过程,采集其声、光、热、电、力学、化学、生物、位置等各种需要的信息,通过各类可能的网络接入,实现物与物、物与人的泛在连接,实现对物品和过程的智能化感知、识别和管理。物联网是一个基于互联网、传统电信网等的信息承载体,它让所有能够被独立寻址的普通物理对象形成互联互通的网络 。

        总而言之,物联网就是利用现代互联网技术实现端对端的数据互联与控制

1.2 物联网开发

        目前,物联网开发的形式是多种多样的。总的来说,一般都需借助特定的网络服务平台为基础实现数据的上传与下发(如果只考虑内网是可以不需要的,比如ESP32CAM)。

        实力雄厚或者有一定背景的公司通常考虑可能自建网络协议服务器,专属服务自家的物联网产品开发。当然,也有不少企业会选择借助他人网络服务平台去实现自家的物联网开发。

        这里比较著名的网络服务平台有:中国移动旗下的OneNet阿里巴巴旗下阿里云以及机智云平台

平台分析:

机智云:机智云作为物联网开发服务平台的元老,一直致力于完善和搭建快速高效的服务机制,其有一套自己快速开发适配的物联网实现流程。但是,在笔者使用的过程中也存在着一些弊端。比如:

        (1)其需要给ESP8266等WIFI模块刷上自家的固件才可使用;

        (2)状态极其不稳定,很容易断联或者死活连不上;

        (3)受限于开发模式,对于产品自我开发有一定限制;

OneNet和阿里云平台:这2大平台背靠强大的资源和技术支持,其服务稳定。设定的开发框架也更多样化,可以提供开发者更多的发挥空间。

OneNet服务平台: 

阿里云物联网平台: 

二、OneNet平台使用

        从多元化和产品稳定性方面考虑,作者将以中国移动旗下的OneNet服务平台为案例进行教学讲解。(其实本来打算以机智云出一篇案例的,结果后来发现之前能正常联动的MCU和APP动不动就宕机。后来,索性直接就以OneNet这个框架更开放的平台为案例教学)

2.1 OneNet准备

1、注册OneNet平台账号(网址:OneNET - 中国移动物联网开放平台 (10086.cn));

2、 登入后选择控制台,进入后点击全部产品服务,选择多协议接入;(我们使用MQTT,既可以上传数据也可以下发数据控制,而且都是免费的)

3、选择MQTT(旧版)之后添加产品,按照自己实际需求填写产品内容;

4、点击所创建的产品,添加几个设备(免费版用户上限10个设备)

5、注意设备ID,鉴权信息以及接入方式这3个属性;

6、关于数据流模块可以设置,可以不设置,反正最后通讯正常的情况下会收到需要的数据流;

2.2 OneNet调试

在设置好OneNet平台设备后,其实可以借助该平台自带的API调试工具进行调试检测(前提:下位机已经成果接通了)。

这里的调试使用API函数的介绍和使用可以参考文档中心(一个合格的嵌入式工程师是一定需要学会自己去查看技术支持文档,而且OneNet提供的文档内容还是非常详尽的)。

OneNet技术文档网址:OneNET - 中国移动物联网开放平台 (10086.cn)

网络协议通讯关键函数

服务器或上位机查询读取设备历史数据:

API函数:

请求方式:GET

URL: http://api.heclouds.com/devices/device_id/datapoints

服务器或上位机下发主题报文(控制下位机):

API函数:

请求方式:POST

URL: http://api.heclouds.com/mqtt?topic=xxx

        以上2个网络通讯的API函数至关重要,就是实现常规情况下OneNet物联网开发的关键性技术支持。情况允许的条件下,建议读者朋友们去好好研读一下技术文档,将会为之后的开发大大助力

三、下位机外设驱动

3.1 ESP8266模块

        作者采用的ESP8266模块ESP8266NodeMCU,是需要进行烧入AT固件,才能实现目标网络通讯。作为常见的物联网开发模块,ESP8266的出现大大降低了物联网开发的难度系数,也普及了物联网的发展。

        AT指令最早在蓝牙模块上接触过,所谓AT指令实质上就是一些起控制作用的特殊字符串。模块可以通过AT指令控制搭配使用源代码API函数开发,总体开发速度快,难度较低。

        不同厂商芯片的AT固件可能有所不同,但是指令基本一致(作者使用的是乐鑫的)

说明:由于篇幅有限,这里就不和大家单独详细介绍AT指令。指令的详细参数及使用说明请参考官方文档:ESP8266 AT指令集。

3.2 OLED模块

        本项目中0.96寸OLED模块的使用仅为显示DHT11传感器采集到的温湿度信息,以此来对比是否和服务器端以及上位机APP端的数据一致性。对其使用有不是太了解的读者朋友可以参考,作者另一篇基础教学博客:(2条消息) 【强烈推荐】基于stm32的OLED各种显示实现(含动态图)_混分巨兽龙某某的博客-CSDN博客_oled显示图片程序

        本项目的代码都是基于作者以前基础教学上的项目代码搭建而成,保证读者朋友可以实现快速复现。 

3.3 DHT11模块

        本项目中DHT11为下位机MCU采集周围环境温度和湿度的传感器,当然,条件允许的情况下还可以附加很多环境传感器(比如:烟雾传感器,环境光传感器,二氧化碳传感器等等)。当然得益于OneNet平台的布局,本项目教学的底层逻辑支持读者朋友的自我DIY,实现自主化的物联网产品设计。

        DHT11模块驱动参考博客:基于stm32的太空人温湿度时钟项目——DHT11(HAL库)_混分巨兽龙某某的博客-CSDN博客

3.4 KEY和LED

        KEY和LED都是源于作者正点原子精英版开发板上自备的(如果和作者同款开发板移植开发将会特别简单快速),属于最基本的GPIO操作相信各位应该都是掌握的

特别注意:

        (1)这里的KEY按键从设计逻辑上就可以看出应该是需要采用外部中断的;

        (2)KEY按下之后会改变LED的亮灭状态,为了同步上位机此时的LED状态,所以需要触发串口通讯中断(考虑嵌套中断情况时候中断优先级的安排)。

四、CubeMX配置

1、RCC配置外部高速晶振(精度更高)——HSE;

2、SYS配置:Debug设置成Serial Wire否则可能导致芯片自锁);

3、TIM2配置:由上面可知DHT11的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;

4、I2C2配置:作为OLED的通讯方式;

5、UART1和UART3配置:MCU分别与电脑和ESP8266通讯(记得开启串口通信中断);

6、设置KEY0按键PE4为外部中断(根据自己的开发板来确定)

7、GPIO配置:PE0设置为DHT11的DATA端,PE5为LED,并且设置ESP8266的EN和RST(PB7和PB9);

8、时钟树配置 

五、代码与解析

5.1 OLED与DHT11模块代码

        受篇幅限制OLED与DHT11部分的代码,这里就不展示了。如果有不懂这部分原理与代码的读者朋友可以参考本人的另一篇博客。博客地址:基于stm32的太空人温湿度时钟项目——DHT11(HAL库)_混分巨兽龙某某的博客-CSDN博客

5.2 ESP8266模块代码

        ESP8266部分的代码主要是借助串口通讯AT指令ESP8266模块刷入AT固件的)与OneNet平台进行信息交互(包含ESP8266初始化、数据发送,指令发送和数据缓存清除等)。

esp8266.h代码:

#ifndef _ESP8266_H_
#define _ESP8266_H_

#include "main.h"
#include "usart.h"
#include<string.h>
#include<stdio.h>
#include<stdbool.h>

#define     ESP8266_WIFI_INFO		"AT+CWJAP=\"NJUST\",\"768541ly\"\r\n"          //连接上自己的wifi热点:WiFi名和密码
#define     ESP8266_ONENET_INFO		"AT+CIPSTART=\"TCP\",\"183.230.40.39\",6002\r\n" //连接上OneNet的MQTT

#define     OK		        0	    //接收完成标志
#define     OUTTIME	        1	    //接收未完成标志



void ESP8266_Clear(void);           //清空缓存

void ESP8266_Init(void);            //esp8266初始化


_Bool ESP8266_SendCmd(char *cmd, char *res);//发送数据

unsigned char *ESP8266_GetIPD(unsigned short timeOut);
void ESP8266_SendData(unsigned char *data, unsigned short len);

#endif

esp8266.c代码:

#include "esp8266.h"

unsigned char ESP8266_Buf[128];                         //定义一个数组作为esp8266的数据缓冲区
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;     //定义两个计数值:此次和上一次
unsigned char a_esp8266_buf;

/**
  * @brief esp8266初始化
  * @param 无
  * @retval 无
  */
void ESP8266_Init(void)
{
  ESP8266_Clear();
	
	printf("1. 测试AT启动\r\n");            //AT:测试AT启动
	while(ESP8266_SendCmd("AT\r\n", "OK"))
		HAL_Delay(500);
	
	printf("2. 设置WiFi模式(CWMODE)\r\n");        //查询/设置 Wi-Fi 模式:设置WiFi模式为Station模式
	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
		HAL_Delay(500);
	
	printf("3. AT+CWDHCP\r\n");     //启用/禁用 DHCP
	while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
		HAL_Delay(500);
	
	printf("4. 连接WiFi热点(CWJAP)\r\n");        
	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
		HAL_Delay(500);
	
	printf("5. 建立TCP连接(CIPSTART)\r\n");
	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
		HAL_Delay(500);
	
	printf("6. ESP8266 Init OK\r\n");
}    


/**
  * @brief  清空缓存
  * @param  无
  * @retval 无
  */
void ESP8266_Clear(void)
{
    memset(ESP8266_Buf, 0, sizeof(ESP8266_Buf));    //将数组中的元素全部初始化为0,
}    
 
/**
  * @brief  等待接收完成
  * @param  无
  * @retval OK:表示接收完成;OUTTIME:表示接收超时完成
  *         进行循环调用,检测接收是否完成
  */
_Bool ESP8266_WaitRecive(void)
{
    if(esp8266_cnt == 0) 							//如果当前接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
		return OUTTIME;
		
	if(esp8266_cnt == esp8266_cntPre)				//如果上一次的值和这次相同,则说明接收完毕
	{
		esp8266_cnt = 0;							//清0接收计数
			
		return OK;								    //返回接收完成标志
	}
	else                                            //如果不相同,则将此次赋值给上一次,并返回接收未完成标志
    {        
        esp8266_cntPre = esp8266_cnt;				
        
        return OUTTIME;								
    }
}

/**
  * @brief 发送命令
  * @param cmd:表示命令;res:需要检查的返回指令
  * @retval 0:表示成功;1:表示失败
  */
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
	
	unsigned char timeOut = 200;

	HAL_UART_Transmit(&huart3, (unsigned char *)cmd, strlen((const char *)cmd),0xffff);

	while(timeOut--)
	{
		if(ESP8266_WaitRecive() == OK)							//如果收到数据
		{
			printf("%s",ESP8266_Buf);
			if(strstr((const char *)ESP8266_Buf, res) != NULL)		//如果检索到关键词,清空缓存
			{
				ESP8266_Clear();							
				
				return 0;
			}
		}
		HAL_Delay(10);
	}
	return 1;
}

/**
  * @brief 数据发送
  * @param data:待发送的数据;len:待发送的数据长度
  * @retval 无
  */
void ESP8266_SendData(unsigned char *data, unsigned short len)
{

	char cmdBuf[32];
	
	ESP8266_Clear();								//清空接收缓存
	sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);		//发送命令,sprintf()函数用于将格式化的数据写入字符串
	if(!ESP8266_SendCmd(cmdBuf, ">"))				//收到‘>’时可以发送数据
	{
		HAL_UART_Transmit(&huart3, data, len,0xffff);		//发送设备连接请求数据
	}
}

/**
  * @brief 获取平台返回的数据
  * @param 等待的时间
  * @retval 平台返回的数据,不同网络设备返回的格式不同,需要进行调试,如:ESP8266的返回格式为:"+IPD,x:yyy",x表示数据长度,yyy表示数据内容
  */
unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{

	char *ptrIPD = NULL;
	
	do
	{
		if(ESP8266_WaitRecive() == OK)								//如果接收完成
		{
			ptrIPD = strstr((char *)ESP8266_Buf, "IPD,");				//搜索“IPD”头
			if(ptrIPD == NULL)											//如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
			{
				//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
			}
			else
			{
				ptrIPD = strchr(ptrIPD, ':');							//找到':'
				if(ptrIPD != NULL)
				{
					ptrIPD++;
					return (unsigned char *)(ptrIPD);
				}
				else
					return NULL;
				
			}
		}
		
		HAL_Delay(5);													//延时等待
	} while(timeOut--);
	
	return NULL;		//超时还未找到,返回空指针
}


/**
  * @brief 串口2收发中断回调函数
  * @param
  * @retval
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 
	if(esp8266_cnt >= 255)  //溢出判断,超过一个字节
	{
		esp8266_cnt = 0;
		memset(ESP8266_Buf,0x00,sizeof(ESP8266_Buf));
		HAL_UART_Transmit(&huart3, (uint8_t *)"数据溢出", 10,0xFFFF); 	
        
	}
	else
	{
		ESP8266_Buf[esp8266_cnt++] = a_esp8266_buf;   //接收数据转存
	
	}
	
	HAL_UART_Receive_IT(&huart3, (uint8_t *)&a_esp8266_buf, 1);   //再开启接收中断
}

代码总结:

        ESP8266模块的代码基于HAL库实现,主要是利用AT指令去使下位机(STM32+ESP8266)连接上WIFI,并且与OneNet平台进行MQTT协议通信TCP连接IP地址和对应端口)。

特别注意:

        使用ESP8266进行通讯时,当数据量较大的时候一定要编写缓存清除代码否则,很有可能出现死机等情况)。当然,这个时候可以搭配SD NAND(贴片式TF卡)去存储传输的数据流。同时,利用这些保存在SD卡中的数据,可以在下位机制作精美的数据历史信息UI,极大的拓展了产品价值。

5.3 OneNet与Cjson代码

        OneNet部分的代码就是实现MQTT协议去传输数据流给OneNet平台,并且订阅上位机发送的Topic主题,利用Cjson代码去解析收到的数据信息,根据上位机发送Topic主题对应的数据控制下位机MCU实现操作(这里订阅的主题为{"LED_SW"},LED控制主题,各位可以根据自己的情况改动)。

onenet.c代码:

#include "onenet.h"
#include "dht11.h"
#include <string.h>
#include <stdio.h>

//CJSON库
#include "cJSON.h"

#define PROID		"549063"        	//产品ID
#define AUTH_INFO	"environment"	//鉴权信息
#define DEVID		"1004695102"			//设备ID

extern unsigned char esp8266_buf[128];

//float sht20_info_tempreture = 12;
//float sht20_info_humidity = 15;

extern int tempreture;
extern int humidity;

//==========================================================
//	函数名称:	OneNet_DevLink
//
//	函数功能:	与onenet创建连接
//
//	入口参数:	无
//
//	返回参数:	1-成功	0-失败
//
//	说明:		与onenet平台建立连接
//==========================================================
_Bool OneNet_DevLink(void)
{
	
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};					//协议包,协议类型初始化

	unsigned char *dataPtr;
	
	_Bool status = 1;
	
	printf("OneNet_DevLink\r\n"
							"PROID: %s,	AUIF: %s,	DEVID:%s\r\n"
                        , PROID, AUTH_INFO, DEVID);
	
	if(MQTT_PacketConnect(PROID, AUTH_INFO, DEVID, 256, 0, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0)
	{
		ESP8266_SendData(mqttPacket._data, mqttPacket._len);			//上传平台
		dataPtr = ESP8266_GetIPD(250);									//等待平台响应
		if(dataPtr != NULL)
		{
			if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK)
			{
				switch(MQTT_UnPacketConnectAck(dataPtr))
				{
					case 0:printf("Tips:	连接成功\r\n");status = 0;break;
					
					case 1:printf("WARN:	连接失败:协议错误\r\n");break;
					case 2:printf("WARN:	连接失败:非法的clientid\r\n");break;
					case 3:printf("WARN:	连接失败:服务器失败\r\n");break;
					case 4:printf("WARN:	连接失败:用户名或密码错误\r\n");break;
					case 5:printf("WARN:	连接失败:非法链接(比如token非法)\r\n");break;
					
					default:printf("ERR:	连接失败:未知错误\r\n");break;
				}
			}
		}
		
		MQTT_DeleteBuffer(&mqttPacket);								//删包
	}
	else
		printf("WARN:	MQTT_PacketConnect Failed\r\n");
	
	return status;
	
}

unsigned char OneNet_FillBuf(char *buf)
{
	
	char text[32];
	uint16_t LED1_FLAG = !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5);		//读取当前LED1的状态
	
	memset(text, 0, sizeof(text));
	strcpy(buf, ",;");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Tempreture,%d;", tempreture);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Humidity,%d;", humidity);
	strcat(buf, text);
	
//	memset(text, 0, sizeof(text));
//	sprintf(text, "key:%d;", LED1_FLAG);
//	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "LED,%d", LED1_FLAG);
	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)							//封包
		{
			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");
	}
	
}

//==========================================================
//	函数名称:	OneNet_RevPro
//
//	函数功能:	平台返回数据检测
//
//	入口参数:	dataPtr:平台返回的数据
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void OneNet_RevPro(unsigned char *cmd)
{
	
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};								//协议包
	
	char *req_payload = NULL;
	char *cmdid_topic = NULL;
	
	unsigned short topic_len = 0;
	unsigned short req_len = 0;
	
	unsigned char type = 0;
	unsigned char qos = 0;
	static unsigned short pkt_id = 0;
	
	short result = 0;

	char *dataPtr = NULL;
	char numBuf[10];
	int num = 0;
	
	cJSON* cjson;
	int value;
	
	type = MQTT_UnPacketRecv(cmd);
	switch(type)
	{
		case MQTT_PKT_CMD:															//命令下发
			
			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);
				
				if(MQTT_PacketCmdResp(cmdid_topic, req_payload, &mqttPacket) == 0)	//命令回复组包
				{
					printf("Tips:	Send CmdResp\r\n");
					
					ESP8266_SendData(mqttPacket._data, mqttPacket._len);			//回复命令
					MQTT_DeleteBuffer(&mqttPacket);									//删包
				}
			}
		
		break;
			
		case MQTT_PKT_PUBLISH:														//接收的Publish消息
		
			result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
			if(result == 0)
			{
				printf("topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
																	cmdid_topic, topic_len, req_payload, req_len);
				
				
				
				//JSON字符串到cJSON格式
				cjson = cJSON_Parse(req_payload); 
				//判断cJSON_Parse函数返回值确定是否打包成功
				if(cjson == NULL){
//						printf("json pack into cjson error...");
					  printf("json pack into cjson error...\r\n");
				}
				else{
					//获取字段值
				//cJSON_GetObjectltem返回的是一个cJSON结构体所以我们可以通过函数返回结构体的方式选择返回类型!
				value = cJSON_GetObjectItem(cjson,"LED")->valueint;
				
				if(value)	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
				else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
					
				}
				 
				//delete cjson
				cJSON_Delete(cjson);

				
				
				switch(qos)
				{
					case 1:															//收到publish的qos为1,设备需要回复Ack
					
						if(MQTT_PacketPublishAck(pkt_id, &mqttPacket) == 0)
						{
							printf("Tips:	Send PublishAck\r\n");
							ESP8266_SendData(mqttPacket._data, mqttPacket._len);
							MQTT_DeleteBuffer(&mqttPacket);
						}
					
					break;
					
					case 2:															//收到publish的qos为2,设备先回复Rec
																					//平台回复Rel,设备再回复Comp
						if(MQTT_PacketPublishRec(pkt_id, &mqttPacket) == 0)
						{
							printf( "Tips:	Send PublishRec\r\n");
							ESP8266_SendData(mqttPacket._data, mqttPacket._len);
							MQTT_DeleteBuffer(&mqttPacket);
						}
					
					break;
					
					default:
						break;
				}
			}
		
		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);
	}

}

/************************************************************/		


//==========================================================
//	函数名称:	OneNet_Subscribe
//
//	函数功能:	订阅
//
//	入口参数:	topics:订阅的topic
//				topic_cnt:topic个数
//
//	返回参数:	SEND_TYPE_OK-成功	SEND_TYPE_SUBSCRIBE-需要重发
//
//	说明:		
//==========================================================
void OneNet_Subscribe(const char *topics[], unsigned char topic_cnt)
{
	
	unsigned char i = 0;
	
	MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};							//协议包
	
	for(; i < topic_cnt; i++)
//	UsartPrintf(USART_DEBUG, "Subscribe Topic: %s\r\n", topics[i]);
	printf("Subscribe Topic: %s\r\n", topics[i]);
	if(MQTT_PacketSubscribe(MQTT_SUBSCRIBE_ID, MQTT_QOS_LEVEL2, topics, topic_cnt, &mqttPacket) == 0)
	{
		ESP8266_SendData(mqttPacket._data, mqttPacket._len);					//向平台发送订阅请求
		
		MQTT_DeleteBuffer(&mqttPacket);											//删包
	}

}

①、注意这3个数据(包含:产品ID、创建的某个设备ID与该设备鉴权信息)替换为自己OneNet账号下的信息 ;

#include "onenet.h"
#include "dht11.h"
#include <string.h>
#include <stdio.h>

//CJSON库
#include "cJSON.h"

#define PROID		"549063"        	//产品ID
#define AUTH_INFO	"environment"	//鉴权信息
#define DEVID		"1004695102"			//设备ID

②、在OneNet_FillBuf(char *buf)函数中创建自己的数据流与需要同步的控制Topic主题(LED)

unsigned char OneNet_FillBuf(char *buf)
{
	
	char text[32];
	uint16_t LED1_FLAG = !HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_5);		//读取当前LED1的状态
	
	memset(text, 0, sizeof(text));
	strcpy(buf, ",;");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Tempreture,%d;", tempreture);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "Humidity,%d;", humidity);
	strcat(buf, text);
	
//	memset(text, 0, sizeof(text));
//	sprintf(text, "key:%d;", LED1_FLAG);
//	strcat(buf, text);

	memset(text, 0, sizeof(text));
	sprintf(text, "LED,%d", LED1_FLAG);
	strcat(buf, text);
	printf("buf_mqtt=%s\r\n",buf);

	
	return strlen(buf);  

}

③、利用Cjson代码去解析上位机发送的关键字LED),然后通过关键字后对应的数字觉得下位机操作;

				//JSON字符串到cJSON格式
				cjson = cJSON_Parse(req_payload); 
				//判断cJSON_Parse函数返回值确定是否打包成功
				if(cjson == NULL){
                //printf("json pack into cjson error...");
					  printf("json pack into cjson error...\r\n");
				}
				else{
					//获取字段值
				//cJSON_GetObjectltem返回的是一个cJSON结构体所以我们可以通过函数返回结构体的方式选择返回类型!
				value = cJSON_GetObjectItem(cjson,"LED")->valueint;
				
				if(value)	HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_RESET);
				else HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);
					
				}
				 
				//delete cjson
				cJSON_Delete(cjson);

Cjson代码:

        Cjson其实是一种常用的网络信息解析代码,读者朋友在使用的时候不一定需要彻底读懂。只需要学会利用Cjson去解析服务器发送的数据信息以及Cjson代码的移植

        cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议

Cjson的下载地址:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C

5.4 MQTT代码

5.4.1 MQTT介绍

        MQTTMessage Queuing Telemetry Transport消息队列遥测传输协议),是一种基于发布/订阅publish/subscribe)模式的“轻量级”通讯协议(因此MQTT常用于物联网开发中的低功耗长期在线通讯),该协议构建于TCP/IP协议上,由IBM在1999年发布。

MQTT最大优点:用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。

5.4.2  MQTT实现原理

实现MQTT协议需要客户端服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份发布者(Publish)代理(Broker)(服务器)订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

  • (1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
  • (2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

MQTT详细介绍:MQTT 入门介绍 | 菜鸟教程 (runoob.com)

5.4.3 MQTT代码与使用

        对于初学者来说直接完成MQTT协议的编写时不现实的,而且实际开发过程中大部分都是对MQTT代码进行局部修改。OneNet社区平台提供了很多MQTT协议代码,以供开发者直接使用(相当于基于API函数开发)。同时,OneNet社区平台也有许多开发者提供了各式各样框架下的OneNet物联网开发方案与代码(可以直接借鉴使用)。

OneNet社区中的开发实例代码(文末代码开源中打包了这些实例代码):

MqttKit.c:

//协议头文件
#include "MqttKit.h"

//C库
#include <string.h>
#include <stdio.h>


#define CMD_TOPIC_PREFIX		"$creq"


//==========================================================
//	函数名称:	EDP_NewBuffer
//
//	函数功能:	申请内存
//
//	入口参数:	edpPacket:包结构体
//				size:大小
//
//	返回参数:	无
//
//	说明:		1.可使用动态分配来分配内存
//				2.可使用局部或全局数组来指定内存
//==========================================================
void MQTT_NewBuffer(MQTT_PACKET_STRUCTURE *mqttPacket, uint32 size)
{
	
	uint32 i = 0;

	if(mqttPacket->_data == NULL)
	{
		mqttPacket->_memFlag = MEM_FLAG_ALLOC;
		
		mqttPacket->_data = (uint8 *)MQTT_MallocBuffer(size);
		if(mqttPacket->_data != NULL)
		{
			mqttPacket->_len = 0;
			
			mqttPacket->_size = size;
			
			for(; i < mqttPacket->_size; i++)
				mqttPacket->_data[i] = 0;
		}
	}
	else
	{
		mqttPacket->_memFlag = MEM_FLAG_STATIC;
		
		for(; i < mqttPacket->_size; i++)
			mqttPacket->_data[i] = 0;
		
		mqttPacket->_len = 0;
		
		if(mqttPacket->_size < size)
			mqttPacket->_data = NULL;
	}

}

//==========================================================
//	函数名称:	MQTT_DeleteBuffer
//
//	函数功能:	释放数据内存
//
//	入口参数:	edpPacket:包结构体
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void MQTT_DeleteBuffer(MQTT_PACKET_STRUCTURE *mqttPacket)
{

	if(mqttPacket->_memFlag == MEM_FLAG_ALLOC)
		MQTT_FreeBuffer(mqttPacket->_data);
	
	mqttPacket->_data = NULL;
	mqttPacket->_len = 0;
	mqttPacket->_size = 0;
	mqttPacket->_memFlag = MEM_FLAG_NULL;

}

int32 MQTT_DumpLength(size_t len, uint8 *buf)
{
	
	int32 i = 0;
	
	for(i = 1; i <= 4; ++i)
	{
		*buf = len % 128;
		len >>= 7;
		if(len > 0)
		{
			*buf |= 128;
			++buf;
		}
		else
		{
			return i;
		}
	}

	return -1;
}

int32 MQTT_ReadLength(const uint8 *stream, int32 size, uint32 *len)
{
	
	int32 i;
	const uint8 *in = stream;
	uint32 multiplier = 1;

	*len = 0;
	for(i = 0; i < size; ++i)
	{
		*len += (in[i] & 0x7f) * multiplier;

		if(!(in[i] & 0x80))
		{
			return i + 1;
		}

		multiplier <<= 7;
		if(multiplier >= 2097152)		//128 * *128 * *128
		{
			return -2;					// error, out of range
		}
	}

	return -1;							// not complete

}

//==========================================================
//	函数名称:	MQTT_UnPacketRecv
//
//	函数功能:	MQTT数据接收类型判断
//
//	入口参数:	dataPtr:接收的数据指针
//
//	返回参数:	0-成功		其他-失败原因
//
//	说明:		
//==========================================================
uint8 MQTT_UnPacketRecv(uint8 *dataPtr)
{
	
	uint8 status = 255;
	uint8 type = dataPtr[0] >> 4;				//类型检查
	
	if(type < 1 || type > 14)
		return status;
	
	if(type == MQTT_PKT_PUBLISH)
	{
		uint8 *msgPtr;
		uint32 remain_len = 0;
		
		msgPtr = dataPtr + MQTT_ReadLength(dataPtr + 1, 4, &remain_len) + 1;
		
		if(remain_len < 2 || dataPtr[0] & 0x01)					//retain
			return 255;
		
		if(remain_len < ((uint16)msgPtr[0] << 8 | msgPtr[1]) + 2)
			return 255;
		
		if(strstr((int8 *)msgPtr + 2, CMD_TOPIC_PREFIX) != NULL)	//如果是命令下发
			status = MQTT_PKT_CMD;
		else
			status = MQTT_PKT_PUBLISH;
	}
	else
		status = type;
	
	return status;

}

//==========================================================
//	函数名称:	MQTT_PacketConnect
//
//	函数功能:	连接消息组包
//
//	入口参数:	user:用户名:产品ID
//				password:密码:鉴权信息或apikey
//				devid:设备ID
//				cTime:连接保持时间
//				clean_session:离线消息清除标志
//				qos:重发标志
//				will_topic:异常离线topic
//				will_msg:异常离线消息
//				will_retain:消息推送标志
//				mqttPacket:包指针
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint8 MQTT_PacketConnect(const int8 *user, const int8 *password, const int8 *devid,
						uint16 cTime, uint1 clean_session, uint1 qos,
						const int8 *will_topic, const int8 *will_msg, int32 will_retain,
						MQTT_PACKET_STRUCTURE *mqttPacket)
{
	
	uint8 flags = 0;
	uint8 will_topic_len = 0;
	uint16 total_len = 15;
	int16 len = 0, devid_len = strlen(devid);
	
	if(!devid)
		return 1;
	
	total_len += devid_len + 2;
	
	//断线后,是否清理离线消息:1-清理	0-不清理--------------------------------------------
	if(clean_session)
	{
		flags |= MQTT_CONNECT_CLEAN_SESSION;
	}
	
	//异常掉线情况下,服务器发布的topic------------------------------------------------------
	if(will_topic)
	{
		flags |= MQTT_CONNECT_WILL_FLAG;
		will_topic_len = strlen(will_topic);
		total_len += 4 + will_topic_len + strlen(will_msg);
	}
	
	//qos级别--主要用于PUBLISH(发布态)消息的,保证消息传递的次数-----------------------------
	switch((unsigned char)qos)
	{
		case MQTT_QOS_LEVEL0:
			flags |= MQTT_CONNECT_WILL_QOS0;							//最多一次
		break;
		
		case MQTT_QOS_LEVEL1:
			flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS1);	//最少一次
		break;
		
		case MQTT_QOS_LEVEL2:
			flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_QOS2);	//只有一次
		break;
		
		default:
		return 2;
	}
	
	//主要用于PUBLISH(发布态)的消息,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它。如果不设那么推送至当前订阅的就释放了
	if(will_retain)
	{
		flags |= (MQTT_CONNECT_WILL_FLAG | MQTT_CONNECT_WILL_RETAIN);
	}
	
	//账号为空 密码为空---------------------------------------------------------------------
	if(!user || !password)
	{
		return 3;
	}
	flags |= MQTT_CONNECT_USER_NAME | MQTT_CONNECT_PASSORD;
	
	total_len += strlen(user) + strlen(password) + 4;
	
	//分配内存-----------------------------------------------------------------------------
	MQTT_NewBuffer(mqttPacket, total_len);
	if(mqttPacket->_data == NULL)
		return 4;
	
	memset(mqttPacket->_data, 0, total_len);
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------连接请求类型---------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_CONNECT << 4;
	
	//固定头部----------------------剩余长度值-----------------------------------------------
	len = MQTT_DumpLength(total_len - 5, mqttPacket->_data + mqttPacket->_len);
	if(len < 0)
	{
		MQTT_DeleteBuffer(mqttPacket);
		return 5;
	}
	else
		mqttPacket->_len += len;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------协议名长度 和 协议名--------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 0;
	mqttPacket->_data[mqttPacket->_len++] = 4;
	mqttPacket->_data[mqttPacket->_len++] = 'M';
	mqttPacket->_data[mqttPacket->_len++] = 'Q';
	mqttPacket->_data[mqttPacket->_len++] = 'T';
	mqttPacket->_data[mqttPacket->_len++] = 'T';
	
	//可变头部----------------------protocol level 4-----------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 4;
	
	//可变头部----------------------连接标志(该函数开头处理的数据)-----------------------------
    mqttPacket->_data[mqttPacket->_len++] = flags;
	
	//可变头部----------------------保持连接的时间(秒)----------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(cTime);
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(cTime);
	 
/*************************************消息体************************************************/

	//消息体----------------------------devid长度、devid-------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(devid_len);
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(devid_len);
	
	strncat((int8 *)mqttPacket->_data + mqttPacket->_len, devid, devid_len);
	mqttPacket->_len += devid_len;
	
	//消息体----------------------------will_flag 和 will_msg---------------------------------
	if(flags & MQTT_CONNECT_WILL_FLAG)
	{
		unsigned short mLen = 0;
		
		if(!will_msg)
			will_msg = "";
		
		mLen = strlen(will_topic);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_topic, mLen);
		mqttPacket->_len += mLen;
		
		mLen = strlen(will_msg);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(mLen);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(mLen);
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, will_msg, mLen);
		mqttPacket->_len += mLen;
	}
	
	//消息体----------------------------use---------------------------------------------------
	if(flags & MQTT_CONNECT_USER_NAME)
	{
		unsigned short user_len = strlen(user);
		
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(user_len);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(user_len);
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, user, user_len);
		mqttPacket->_len += user_len;
	}

	//消息体----------------------------password----------------------------------------------
	if(flags & MQTT_CONNECT_PASSORD)
	{
		unsigned short psw_len = strlen(password);
		
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(psw_len);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(psw_len);
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, password, psw_len);
		mqttPacket->_len += psw_len;
	}

	return 0;

}

//==========================================================
//	函数名称:	MQTT_PacketDisConnect
//
//	函数功能:	断开连接消息组包
//
//	入口参数:	mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_PacketDisConnect(MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 2);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_DISCONNECT << 4;
	
	//固定头部----------------------剩余长度值-----------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 0;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketConnectAck
//
//	函数功能:	连接消息解包
//
//	入口参数:	rev_data:接收的数据
//
//	返回参数:	1、255-失败		其他-平台的返回码
//
//	说明:		
//==========================================================
uint8 MQTT_UnPacketConnectAck(uint8 *rev_data)
{

	if(rev_data[1] != 2)
		return 1;
	
	if(rev_data[2] == 0 || rev_data[2] == 1)
		return rev_data[3];
	else
		return 255;

}

//==========================================================
//	函数名称:	MQTT_PacketSaveData
//
//	函数功能:	数据点上传组包
//
//	入口参数:	devid:设备ID(可为空)
//				send_buf:json缓存buf
//				send_len:json总长
//				type_bin_head:bin文件的消息头
//				type:类型
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_PacketSaveData(const int8 *devid, int16 send_len, int8 *type_bin_head, uint8 type, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	if(MQTT_PacketPublish(MQTT_PUBLISH_ID, "$dp", NULL, send_len + 3, MQTT_QOS_LEVEL1, 0, 1, mqttPacket) == 0)
	{
		mqttPacket->_data[mqttPacket->_len++] = type;					//类型
		
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(send_len);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(send_len);
	}
	else
		return 1;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_PacketSaveBinData
//
//	函数功能:	为禁止文件上传组包
//
//	入口参数:	name:数据流名字
//				file_len:文件长度
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_PacketSaveBinData(const int8 *name, int16 file_len, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	uint1 result = 1;
	int8 *bin_head = NULL;
	uint8 bin_head_len = 0;
	int8 *payload = NULL;
	int32 payload_size = 0;
	
	bin_head = (int8 *)MQTT_MallocBuffer(13 + strlen(name));
	if(bin_head == NULL)
		return result;
	
	sprintf(bin_head, "{\"ds_id\":\"%s\"}", name);
	
	bin_head_len = strlen(bin_head);
	payload_size = 7 + bin_head_len + file_len;
	
	payload = (int8 *)MQTT_MallocBuffer(payload_size - file_len);
	if(payload == NULL)
	{
		MQTT_FreeBuffer(bin_head);
		
		return result;
	}
	
	payload[0] = 2;						//类型
		
	payload[1] = MOSQ_MSB(bin_head_len);
	payload[2] = MOSQ_LSB(bin_head_len);
	
	memcpy(payload + 3, bin_head, bin_head_len);
	
	payload[bin_head_len + 3] = (file_len >> 24) & 0xFF;
	payload[bin_head_len + 4] = (file_len >> 16) & 0xFF;
	payload[bin_head_len + 5] = (file_len >> 8) & 0xFF;
	payload[bin_head_len + 6] = file_len & 0xFF;
	
	if(MQTT_PacketPublish(MQTT_PUBLISH_ID, "$dp", payload, payload_size, MQTT_QOS_LEVEL1, 0, 1, mqttPacket) == 0)
		result = 0;
	
	MQTT_FreeBuffer(bin_head);
	MQTT_FreeBuffer(payload);
	
	return result;

}

//==========================================================
//	函数名称:	MQTT_UnPacketCmd
//
//	函数功能:	命令下发解包
//
//	入口参数:	rev_data:接收的数据指针
//				cmdid:cmdid-uuid
//				req:命令
//
//	返回参数:	0-成功		其他-失败原因
//
//	说明:		
//==========================================================
uint8 MQTT_UnPacketCmd(uint8 *rev_data, int8 **cmdid, int8 **req, uint16 *req_len)
{

	int8 *dataPtr = strchr((int8 *)rev_data + 6, '/');	//加6是跳过头信息
	
	uint32 remain_len = 0;
	
	if(dataPtr == NULL)									//未找到'/'
		return 1;
	dataPtr++;											//跳过'/'
	
	MQTT_ReadLength(rev_data + 1, 4, &remain_len);		//读取剩余字节
	
	*cmdid = (int8 *)MQTT_MallocBuffer(37);				//cmdid固定36字节,多分配一个结束符的位置
	if(*cmdid == NULL)
		return 2;
	
	memset(*cmdid, 0, 37);								//全部清零
	memcpy(*cmdid, (const int8 *)dataPtr, 36);			//复制cmdid
	dataPtr += 36;
	
	*req_len = remain_len - 44;							//命令长度 = 剩余长度(remain_len) - 2 - 5($creq) - 1(\) - cmdid长度
	*req = (int8 *)MQTT_MallocBuffer(*req_len + 1);		//分配命令长度+1
	if(*req == NULL)
	{
		MQTT_FreeBuffer(*cmdid);
		return 3;
	}
	
	memset(*req, 0, *req_len + 1);						//清零
	memcpy(*req, (const int8 *)dataPtr, *req_len);		//复制命令
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_PacketCmdResp
//
//	函数功能:	命令回复组包
//
//	入口参数:	cmdid:cmdid
//				req:命令
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_PacketCmdResp(const int8 *cmdid, const int8 *req, MQTT_PACKET_STRUCTURE *mqttPacket)
{
	
	uint16 cmdid_len = strlen(cmdid);
	uint16 req_len = strlen(req);
	_Bool status = 0;
	
	int8 *payload = MQTT_MallocBuffer(cmdid_len + 7);
	if(payload == NULL)
		return 1;
	
	memset(payload, 0, cmdid_len + 7);
	memcpy(payload, "$crsp/", 6);
	strncat(payload, cmdid, cmdid_len);

	if(MQTT_PacketPublish(MQTT_PUBLISH_ID, payload, req, strlen(req), MQTT_QOS_LEVEL0, 0, 1, mqttPacket) == 0)
		status = 0;
	else
		status = 1;
	
	MQTT_FreeBuffer(payload);
	
	return status;

}

//==========================================================
//	函数名称:	MQTT_PacketSubscribe
//
//	函数功能:	Subscribe消息组包
//
//	入口参数:	pkt_id:pkt_id
//				qos:消息重发次数
//				topics:订阅的消息
//				topics_cnt:订阅的消息个数
//				mqttPacket:包指针
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint8 MQTT_PacketSubscribe(uint16 pkt_id, enum MqttQosLevel qos, const int8 *topics[], uint8 topics_cnt, MQTT_PACKET_STRUCTURE *mqttPacket)
{
	
	uint32 topic_len = 0, remain_len = 0;
	int16 len = 0;
	uint8 i = 0;
	
	if(pkt_id == 0)
		return 1;
	
	//计算topic长度-------------------------------------------------------------------------
	for(; i < topics_cnt; i++)
	{
		if(topics[i] == NULL)
			return 2;
		
		topic_len += strlen(topics[i]);
	}
	
	//2 bytes packet id + topic filter(2 bytes topic + topic length + 1 byte reserve)------
	remain_len = 2 + 3 * topics_cnt + topic_len;
	
	//分配内存------------------------------------------------------------------------------
	MQTT_NewBuffer(mqttPacket, remain_len + 5);
	if(mqttPacket->_data == NULL)
		return 3;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_SUBSCRIBE << 4 | 0x02;
	
	//固定头部----------------------剩余长度值-----------------------------------------------
	len = MQTT_DumpLength(remain_len, mqttPacket->_data + mqttPacket->_len);
	if(len < 0)
	{
		MQTT_DeleteBuffer(mqttPacket);
		return 4;
	}
	else
		mqttPacket->_len += len;
	
/*************************************payload***********************************************/
	
	//payload----------------------pkt_id---------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
	
	//payload----------------------topic_name-----------------------------------------------
	for(i = 0; i < topics_cnt; i++)
	{
		topic_len = strlen(topics[i]);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
		
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topics[i], topic_len);
		mqttPacket->_len += topic_len;
		
		mqttPacket->_data[mqttPacket->_len++] = qos & 0xFF;
	}

	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketSubscrebe
//
//	函数功能:	Subscribe的回复消息解包
//
//	入口参数:	rev_data:接收到的信息
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint8 MQTT_UnPacketSubscribe(uint8 *rev_data)
{
	
	uint8 result = 255;

	if(rev_data[2] == MOSQ_MSB(MQTT_SUBSCRIBE_ID) && rev_data[3] == MOSQ_LSB(MQTT_SUBSCRIBE_ID))
	{
		switch(rev_data[4])
		{
			case 0x00:
			case 0x01:
			case 0x02:
				//MQTT Subscribe OK
				result = 0;
			break;
			
			case 0x80:
				//MQTT Subscribe Failed
				result = 1;
			break;
			
			default:
				//MQTT Subscribe UnKnown Err
				result = 2;
			break;
		}
	}
	
	return result;

}

//==========================================================
//	函数名称:	MQTT_PacketUnSubscribe
//
//	函数功能:	UnSubscribe消息组包
//
//	入口参数:	pkt_id:pkt_id
//				qos:消息重发次数
//				topics:订阅的消息
//				topics_cnt:订阅的消息个数
//				mqttPacket:包指针
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint8 MQTT_PacketUnSubscribe(uint16 pkt_id, const int8 *topics[], uint8 topics_cnt, MQTT_PACKET_STRUCTURE *mqttPacket)
{
	
	uint32 topic_len = 0, remain_len = 0;
	int16 len = 0;
	uint8 i = 0;
	
	if(pkt_id == 0)
		return 1;
	
	//计算topic长度-------------------------------------------------------------------------
	for(; i < topics_cnt; i++)
	{
		if(topics[i] == NULL)
			return 2;
		
		topic_len += strlen(topics[i]);
	}
	
	//2 bytes packet id, 2 bytes topic length + topic + 1 byte reserve---------------------
	remain_len = 2 + (topics_cnt << 1) + topic_len;
	
	//分配内存------------------------------------------------------------------------------
	MQTT_NewBuffer(mqttPacket, remain_len + 5);
	if(mqttPacket->_data == NULL)
		return 3;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_UNSUBSCRIBE << 4 | 0x02;
	
	//固定头部----------------------剩余长度值-----------------------------------------------
	len = MQTT_DumpLength(remain_len, mqttPacket->_data + mqttPacket->_len);
	if(len < 0)
	{
		MQTT_DeleteBuffer(mqttPacket);
		return 4;
	}
	else
		mqttPacket->_len += len;
	
/*************************************payload***********************************************/
	
	//payload----------------------pkt_id---------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
	
	//payload----------------------topic_name-----------------------------------------------
	for(i = 0; i < topics_cnt; i++)
	{
		topic_len = strlen(topics[i]);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
		
		strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topics[i], topic_len);
		mqttPacket->_len += topic_len;
	}

	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketUnSubscribe
//
//	函数功能:	UnSubscribe的回复消息解包
//
//	入口参数:	rev_data:接收到的信息
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint1 MQTT_UnPacketUnSubscribe(uint8 *rev_data)
{
	
	uint1 result = 1;

	if(rev_data[2] == MOSQ_MSB(MQTT_UNSUBSCRIBE_ID) && rev_data[3] == MOSQ_LSB(MQTT_UNSUBSCRIBE_ID))
	{
		result = 0;
	}
	
	return result;

}

//==========================================================
//	函数名称:	MQTT_PacketPublish
//
//	函数功能:	Pulish消息组包
//
//	入口参数:	pkt_id:pkt_id
//				topic:发布的topic
//				payload:消息体
//				payload_len:消息体长度
//				qos:重发次数
//				retain:离线消息推送
//				own:
//				mqttPacket:包指针
//
//	返回参数:	0-成功		其他-失败
//
//	说明:		
//==========================================================
uint8 MQTT_PacketPublish(uint16 pkt_id, const int8 *topic,
						const int8 *payload, uint32 payload_len,
						enum MqttQosLevel qos, int32 retain, int32 own,
						MQTT_PACKET_STRUCTURE *mqttPacket)
{

	uint32 total_len = 0, topic_len = 0;
	uint32 data_len = 0;
	int32 len = 0;
	uint8 flags = 0;
	
	//pkt_id检查----------------------------------------------------------------------------
	if(pkt_id == 0)
		return 1;
	
	//$dp为系统上传数据点的指令--------------------------------------------------------------
	for(topic_len = 0; topic[topic_len] != '\0'; ++topic_len)
	{
		if((topic[topic_len] == '#') || (topic[topic_len] == '+'))
			return 2;
	}
	
	//Publish消息---------------------------------------------------------------------------
	flags |= MQTT_PKT_PUBLISH << 4;
	
	//retain标志----------------------------------------------------------------------------
	if(retain)
		flags |= 0x01;
	
	//总长度--------------------------------------------------------------------------------
	total_len = topic_len + payload_len + 2;
	
	//qos级别--主要用于PUBLISH(发布态)消息的,保证消息传递的次数-----------------------------
	switch(qos)
	{
		case MQTT_QOS_LEVEL0:
			flags |= MQTT_CONNECT_WILL_QOS0;	//最多一次
		break;
		
		case MQTT_QOS_LEVEL1:
			flags |= 0x02;						//最少一次
			total_len += 2;
		break;
		
		case MQTT_QOS_LEVEL2:
			flags |= 0x04;						//只有一次
			total_len += 2;
		break;
		
		default:
		return 3;
	}
	
	//分配内存------------------------------------------------------------------------------
	if(payload != NULL)
	{
		if(payload[0] == 2)
		{
			uint32 data_len_t = 0;
			
			while(payload[data_len_t++] != '}');
			data_len_t -= 3;
			data_len = data_len_t + 7;
			data_len_t = payload_len - data_len;
			
			MQTT_NewBuffer(mqttPacket, total_len + 3 - data_len_t);
			
			if(mqttPacket->_data == NULL)
				return 4;
			
			memset(mqttPacket->_data, 0, total_len + 3 - data_len_t);
		}
		else
		{
			MQTT_NewBuffer(mqttPacket, total_len + 3);
			
			if(mqttPacket->_data == NULL)
				return 4;
			
			memset(mqttPacket->_data, 0, total_len + 3);
		}
	}
	else
	{
		MQTT_NewBuffer(mqttPacket, total_len + 3);
		
		if(mqttPacket->_data == NULL)
			return 4;
		
		memset(mqttPacket->_data, 0, total_len + 3);
	}
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = flags;
	
	//固定头部----------------------剩余长度值-----------------------------------------------
	len = MQTT_DumpLength(total_len, mqttPacket->_data + mqttPacket->_len);
	if(len < 0)
	{
		MQTT_DeleteBuffer(mqttPacket);
		return 5;
	}
	else
		mqttPacket->_len += len;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------写入topic长度、topic-------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(topic_len);
	mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(topic_len);
	
	strncat((int8 *)mqttPacket->_data + mqttPacket->_len, topic, topic_len);
	mqttPacket->_len += topic_len;
	if(qos != MQTT_QOS_LEVEL0)
	{
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_MSB(pkt_id);
		mqttPacket->_data[mqttPacket->_len++] = MOSQ_LSB(pkt_id);
	}
	
	//可变头部----------------------写入payload----------------------------------------------
	if(payload != NULL)
	{
		if(payload[0] == 2)
		{
			memcpy((int8 *)mqttPacket->_data + mqttPacket->_len, payload, data_len);
			mqttPacket->_len += data_len;
		}
		else
		{
			memcpy((int8 *)mqttPacket->_data + mqttPacket->_len, payload, payload_len);
			mqttPacket->_len += payload_len;
		}
	}
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketPublish
//
//	函数功能:	Publish消息解包
//
//	入口参数:	flags:MQTT相关标志信息
//				pkt:指向可变头部
//				size:固定头部中的剩余长度信息
//
//	返回参数:	0-成功		其他-失败原因
//
//	说明:		
//==========================================================
uint8 MQTT_UnPacketPublish(uint8 *rev_data, int8 **topic, uint16 *topic_len, int8 **payload, uint16 *payload_len, uint8 *qos, uint16 *pkt_id)
{
	
	const int8 flags = rev_data[0] & 0x0F;
	uint8 *msgPtr;
	uint32 remain_len = 0;

	const int8 dup = flags & 0x08;

	*qos = (flags & 0x06) >> 1;
	
	msgPtr = rev_data + MQTT_ReadLength(rev_data + 1, 4, &remain_len) + 1;
	
	if(remain_len < 2 || flags & 0x01)							//retain
		return 255;
	
	*topic_len = (uint16)msgPtr[0] << 8 | msgPtr[1];
	if(remain_len < *topic_len + 2)
		return 255;
	
	if(strstr((int8 *)msgPtr + 2, CMD_TOPIC_PREFIX) != NULL)	//如果是命令下发
		return MQTT_PKT_CMD;
	
	switch(*qos)
	{
		case MQTT_QOS_LEVEL0:									// qos0 have no packet identifier
			
			if(0 != dup)
				return 255;

			*topic = MQTT_MallocBuffer(*topic_len + 1);			//为topic分配内存
			if(*topic == NULL)
				return 255;
			
			memset(*topic, 0, *topic_len + 1);
			memcpy(*topic, (int8 *)msgPtr + 2, *topic_len);		//复制数据
			
			*payload_len = remain_len - 2 - *topic_len;			//为payload分配内存
			*payload = MQTT_MallocBuffer(*payload_len + 1);
			if(*payload == NULL)								//如果失败
			{
				MQTT_FreeBuffer(*topic);						//则需要把topic的内存释放掉
				return 255;
			}
			
			memset(*payload, 0, *payload_len + 1);
			memcpy(*payload, (int8 *)msgPtr + 2 + *topic_len, *payload_len);
			
		break;

		case MQTT_QOS_LEVEL1:
		case MQTT_QOS_LEVEL2:
			
			if(*topic_len + 2 > remain_len)
				return 255;
			
			*pkt_id = (uint16)msgPtr[*topic_len + 2] << 8 | msgPtr[*topic_len + 3];
			if(pkt_id == 0)
				return 255;
			
			*topic = MQTT_MallocBuffer(*topic_len + 1);			//为topic分配内存
			if(*topic == NULL)
				return 255;
			
			memset(*topic, 0, *topic_len + 1);
			memcpy(*topic, (int8 *)msgPtr + 2, *topic_len);		//复制数据
			
			*payload_len = remain_len - 4 - *topic_len;
			*payload = MQTT_MallocBuffer(*payload_len + 1);		//为payload分配内存
			if(*payload == NULL)								//如果失败
			{
				MQTT_FreeBuffer(*topic);						//则需要把topic的内存释放掉
				return 255;
			}
			
			memset(*payload, 0, *payload_len + 1);
			memcpy(*payload, (int8 *)msgPtr + 4 + *topic_len, *payload_len);
			
		break;

		default:
			return 255;
	}
	
	if(strchr((int8 *)topic, '+') || strchr((int8 *)topic, '#'))
		return 255;

	return 0;

}

//==========================================================
//	函数名称:	MQTT_PacketPublishAck
//
//	函数功能:	Publish Ack消息组包
//
//	入口参数:	pkt_id:packet id
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败原因
//
//	说明:		当收到的Publish消息的QoS等级为1时,需要Ack回复
//==========================================================
uint1 MQTT_PacketPublishAck(uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 4);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_PUBACK << 4;
	
	//固定头部----------------------剩余长度-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 2;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------pkt_id长度-----------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = pkt_id >> 8;
	mqttPacket->_data[mqttPacket->_len++] = pkt_id & 0xff;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketPublishAck
//
//	函数功能:	Publish Ack消息解包
//
//	入口参数:	rev_data:收到的数据
//
//	返回参数:	0-成功		1-失败原因
//
//	说明:		
//==========================================================
uint1 MQTT_UnPacketPublishAck(uint8 *rev_data)
{

	if(rev_data[1] != 2)
		return 1;

	if(rev_data[2] == MOSQ_MSB(MQTT_PUBLISH_ID) && rev_data[3] == MOSQ_LSB(MQTT_PUBLISH_ID))
		return 0;
	else
		return 1;

}

//==========================================================
//	函数名称:	MQTT_PacketPublishRec
//
//	函数功能:	Publish Rec消息组包
//
//	入口参数:	pkt_id:packet id
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败原因
//
//	说明:		当收到的Publish消息的QoS等级为2时,先收到rec
//==========================================================
uint1 MQTT_PacketPublishRec(uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 4);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_PUBREC << 4;
	
	//固定头部----------------------剩余长度-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 2;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------pkt_id长度-----------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = pkt_id >> 8;
	mqttPacket->_data[mqttPacket->_len++] = pkt_id & 0xff;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketPublishRec
//
//	函数功能:	Publish Rec消息解包
//
//	入口参数:	rev_data:接收到的数据
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_UnPacketPublishRec(uint8 *rev_data)
{

	if(rev_data[1] != 2)
		return 1;

	if(rev_data[2] == MOSQ_MSB(MQTT_PUBLISH_ID) && rev_data[3] == MOSQ_LSB(MQTT_PUBLISH_ID))
		return 0;
	else
		return 1;

}

//==========================================================
//	函数名称:	MQTT_PacketPublishRel
//
//	函数功能:	Publish Rel消息组包
//
//	入口参数:	pkt_id:packet id
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败原因
//
//	说明:		当收到的Publish消息的QoS等级为2时,先收到rec,再回复rel
//==========================================================
uint1 MQTT_PacketPublishRel(uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 4);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_PUBREL << 4 | 0x02;
	
	//固定头部----------------------剩余长度-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 2;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------pkt_id长度-----------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = pkt_id >> 8;
	mqttPacket->_data[mqttPacket->_len++] = pkt_id & 0xff;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketPublishRel
//
//	函数功能:	Publish Rel消息解包
//
//	入口参数:	rev_data:接收到的数据
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_UnPacketPublishRel(uint8 *rev_data, uint16 pkt_id)
{

	if(rev_data[1] != 2)
		return 1;

	if(rev_data[2] == MOSQ_MSB(pkt_id) && rev_data[3] == MOSQ_LSB(pkt_id))
		return 0;
	else
		return 1;

}

//==========================================================
//	函数名称:	MQTT_PacketPublishComp
//
//	函数功能:	Publish Comp消息组包
//
//	入口参数:	pkt_id:packet id
//				mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败原因
//
//	说明:		当收到的Publish消息的QoS等级为2时,先收到rec,再回复rel
//==========================================================
uint1 MQTT_PacketPublishComp(uint16 pkt_id, MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 4);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_PUBCOMP << 4;
	
	//固定头部----------------------剩余长度-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 2;
	
/*************************************可变头部***********************************************/
	
	//可变头部----------------------pkt_id长度-----------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = pkt_id >> 8;
	mqttPacket->_data[mqttPacket->_len++] = pkt_id & 0xff;
	
	return 0;

}

//==========================================================
//	函数名称:	MQTT_UnPacketPublishComp
//
//	函数功能:	Publish Comp消息解包
//
//	入口参数:	rev_data:接收到的数据
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_UnPacketPublishComp(uint8 *rev_data)
{

	if(rev_data[1] != 2)
		return 1;

	if(rev_data[2] == MOSQ_MSB(MQTT_PUBLISH_ID) && rev_data[3] == MOSQ_LSB(MQTT_PUBLISH_ID))
		return 0;
	else
		return 1;

}

//==========================================================
//	函数名称:	MQTT_PacketPing
//
//	函数功能:	心跳请求组包
//
//	入口参数:	mqttPacket:包指针
//
//	返回参数:	0-成功		1-失败
//
//	说明:		
//==========================================================
uint1 MQTT_PacketPing(MQTT_PACKET_STRUCTURE *mqttPacket)
{

	MQTT_NewBuffer(mqttPacket, 2);
	if(mqttPacket->_data == NULL)
		return 1;
	
/*************************************固定头部***********************************************/
	
	//固定头部----------------------头部消息-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = MQTT_PKT_PINGREQ << 4;
	
	//固定头部----------------------剩余长度-------------------------------------------------
	mqttPacket->_data[mqttPacket->_len++] = 0;
	
	return 0;

}

5.5 KEY与LED代码

        KEY按键需要使用中断处理函数去控制LED灯,同时,如果需要及时上传服务器LED灯状态,同步上位机的LED灯(中断嵌套中断需要注意中断优先级,切不可无序的中断嵌套)。

KEY中断控制代码:

__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
	if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) == 0)
	{
//		HAL_Delay(10);
//		if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4) == 0)
//		{
			HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
//			OneNet_SendData();			//不可以无序中断嵌套,注意优先级
//		}
	}
	
}

5.6 main函数

        main函数主要包括:初始化ESP8266模块OLED模块打开串口中断连接OneNet平台订阅目标主题等。

main.c:

    //参数定义
    unsigned short timeCount = 0;	//发送间隔变量
	unsigned char *dataPtr = NULL;
	const char *topics[] = {"LED_SW"};    //需要订阅的变量

    //.....
    OLED_Init();
	OLED_CLS();	
//	HAL_Delay(1000); 

	printf("This is a mqtt text \r\n");
	HAL_UART_Receive_IT(&huart3, (uint8_t *)&a_esp8266_buf, 1);
	ESP8266_Init();
	
	while(OneNet_DevLink())			//连接OneNET
	HAL_Delay(500);
	printf("½ÓÈëonenet³É¹¦");
//	OneNet_SendData();//发送onenet
	OneNet_Subscribe(topics, 1);    //订阅主题
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		if(++timeCount >= 50)	
		{
			
			printf("OneNet_SendData\r\n");
			OneNet_SendData();									//发送下位机数据
			
			timeCount = 0;
			ESP8266_Clear();
		}
		
		dataPtr = ESP8266_GetIPD(0);
		if(dataPtr != NULL)
			OneNet_RevPro(dataPtr);
		
		
		DHT11();	
  }

六、上位机APP

        受限于文章篇幅限制,作者默认读者朋友对uniapp以及vue编程有一定的基础与了解。如果读者朋友对其了解有限,可以去B站补补知识或者参考作者另一篇上位机详细教程博客

        其实,OneNet平台是拥有自己的上位机网页版与APP)。但是,后续APP项目被嘎了(目前,已经无法使用新的OneNet的自备APP)。而且基于已有上位机框架去开发,其UI和功能是会收到一定程度上的限制的。

OneNet平台网页端上位机:

如果仅需要制作网页端上位机的,读者朋友可以参考这篇博客,作者力赞:基于STM32设计的智能家居系统(采用ESP8266+OneNet云平台)_DS小龙哥的博客-CSDN博客_基于stm32的智能家居

6.1 uniapp

        uni-app 是一个使用 Vue.js (opens new window)开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。

        uniapp框架自由度高,开源性好并且免费。官方网址具有超详细的案例教程和技术文档,社区插件和交流活跃,非常建议各位读者朋友试一试!!!

        uniapp官网网址:uni-app官网 (dcloud.net.cn)

6.2 APP编程与调试

        这里建议大家写上位机APP的时候可以做一个简易的数据流接收调试demo,代码如下:

index.vue:

<template>
	<view class="content">
		<image class="logo" src="/static/logo.png"></image>
		<view class="text-area">
			<text class="title">{{title}}</text>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				title: 'Hello'
			}
		},
		onLoad() {
			uni.request({
			    url: 'http://api.heclouds.com/devices/1004695102/datapoints', //仅为示例,并非真实接口地址。
			    
			    header: {
			        'api-key': 'yADWnwACOo9EzEf0D=nq=54drSw=' //自定义请求头信息
			    },
				method: 'GET',
				
			    success: (res) => {
			        console.log(res.data);
					console.log(res.data.data.datastreams[2].id, res.data.data.datastreams[2].datapoints[0].value);
			    }
			});

		},
		methods: {

		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

数据流接收情况:

解析:

注意:

         这里其实就是简单的使用OneNet平台的接口API函数去在APP中读取数据量,读者朋友可以根据自己的实际情况在控制台console打印一下自己实际情况下数据量的信息。然后通过变量再去保存赋值这些需要显示的下位机数据。

        基于vue代码去实现APP的UI制作非常简单,很多框架和组件都给大家设计好了,稍微有点代码功底的朋友将会很容易就上手。

项目index.vue:

<template>
	<view class="wrapper">
		<view class="device-area">
			<view class="device-cart">
				<view class="device-info">
					<view class="device-name">温度</view>
					<image class="device-logo" src="/static/Temp.png"></image>
				</view>
				<view class="device-data">{{Temp}}℃</view>
			</view>
			
			<view class="device-cart">
				<view class="device-info">
					<view class="device-name">湿度</view>
					<image class="device-logo" src="/static/Humi.png"></image>
				</view>
				<view class="device-data">{{Humi}}%</view>
			</view>
			
			<view class="device-cart">
				<view class="device-info">
					<view class="device-name">台灯</view>
					<image class="device-logo" src="/static/Lamp.png"></image>
				</view>
				<switch :checked="status" @change="onLedSwitch" color="#2b9939"/>
			</view>
		
		
			<view class="content">
				<image class="logo" src="/static//beautiful.png"></image>
				<view class="text-area">
					<text class="title">{{title}}</text>
				</view>
			</view>
		
			
		</view>
		
	</view>
</template>

<script>
	export default {
		data() {
			return {
				Temp: 0,
				Humi: 0,
				Led: false,
				status: false,
				title: 'ikun'
			}
		},
		onShow() {
			let that = this
			this.GetDatapoints()
			setInterval(function(){
				that.GetDatapoints()
				}, 3000);			//请求快一点3S
		},
		
		onLoad() {
					
		},
		methods: {
			GetDatapoints: function(){ 
				uni.request({
				    url: 'http://api.heclouds.com/devices/1004695102/datapoints?', //仅为示例,并非真实接口地址。
				
				    header: {
				        'api-key': 'yADWnwACOo9EzEf0D=nq=54drSw=' //自定义请求头信息
				    },
					method: 'GET',
				    success: (res) => {
				  //       console.log(res.data);
						// console.log(res.data.data.datastreams[1].id, res.data.data.datastreams[1].datapoints[0].value);
						// console.log(res.data.data.datastreams[0].id, res.data.data.datastreams[0].datapoints[0].value);
						this.Temp = res.data.data.datastreams[2].datapoints[0].value;		//解析数据
						this.Humi = res.data.data.datastreams[1].datapoints[0].value;		//解析数据
						this.Led = res.data.data.datastreams[3].datapoints[0].value;
						// console.log(res.data.data.datastreams[3].id, Boolean(res.data.data.datastreams[3].datapoints[0].value));
						// console.log(res.data.data.datastreams[3].id, res.data.data.datastreams[3].datapoints[0].value);
						// console.log(this.Led);
						if(this.Led == 1)
						{
							this.status = true;
							// console.log(this.status);
						}
						else
						{
							this.status = false;
							// console.log(this.status);
						}
						// console.log(Boolean(this.Led));
						// console.log(this.Led);
					}
				});
			},
			onLedSwitch(event) {
				console.log(event.detail.value);
				let sw = event.detail.value;
				if(sw) {
					uni.request({
					    url: 'http://api.heclouds.com/mqtt?topic=LED_SW', 
					
					    header: {
					        'api-key': 'wWUFkpg2dNRcJmzBXgY8ATMacRM=' //Master-key
					    },
						method: 'POST',
						data: {"LED":1},
					    success: (res) => {
					        console.log("LED on!");
					    }
					});
				}
				else {
					uni.request({
					    url: 'http://api.heclouds.com/mqtt?topic=LED_SW', 
					    header: {
					        'api-key': 'wWUFkpg2dNRcJmzBXgY8ATMacRM=' //Master-key
					    },
						method: 'POST',
						data: {"LED":0},
					    success: (res) => {
					        console.log("LED off!");
					    }
					});
				}
			}

		}
	}
</script>

<style>
	.wrapper {
		padding: 30rpx;
	}

	.device-area {
		display: flex;
		justify-content: space-between;
		flex-wrap: wrap;
	}

	.device-cart {
		width: 320rpx;
		height: 150rpx;
		border-radius: 30rpx;
		margin-top: 30rpx;
		display: flex;
		justify-content: space-around;
		align-items: center;
		/* background-color: #8f8f94; */
		box-shadow: 0 0 15rpx #ccc;
	}

	.device-info {
		font-size: 20rpx;
		/* background-color: #8f8f94; */
	}
	
	.device-name{
		text-align: center;
		color: #6d6d6d;
	}
	.device-logo{
		width: 70rpx;
		height: 70rpx;
		margin-top: 10rpx;
	}
	.device-data{
		font-size: 50rpx;
		color: #6d6d6d;
		
	}
	
	/* 鸡你太美logo 我是ikun,不需要的可以删除下面4段 */
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}
	
	.logo {
		height: 357rpx;
		width: 357rpx;
		margin-top: 100rpx;
/* 		align-items: center; */
		margin-left: 150rpx;
		margin-right: 40rpx;
		margin-bottom: 50rpx;
	}
	
	.text-area {
		display: flex;
		justify-content: center;
		margin-top: 0rpx;
		margin-left: 150rpx;
		margin-right: 40rpx;
	}
	
	.title {
		font-size: 72rpx;
		color: #8f8f94;
	}
</style>

运行调试效果:

解析:

 注意点:

        switch下的checked绑定的其实布尔型弱布尔型)——true或false,我们的下位机采用C语言编程,C语言不存在布尔型。所以,上传的数据为0或者1,这个时候默认一直是true的(这一点卡了本人很久)。所以,在APP编程的使用,笔者进行了变量判断status的值为true还是false,这一点希望后来者注意一下。

        调试完毕之后就可以将uniapp编译完成的APP下载到手机上实现实时物联网操作,这部分不熟悉也可以参考作者与本章节提到的那篇博客。 

七、项目效果

基于STM32的智能家居系统设计

八、代码开源

代码地址:基于STM32与OneNet平台的智能家居系统设计-智能家居文档类资源-CSDN文库

如果积分不够的朋友,点波关注评论区留下邮箱作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于STM32与OneNet平台的智能家居系统设计(代码开源含自制APP代码) 的相关文章

  • 【RTD】AD7793驱动程序

    文章目录 1 前言2 AD7793驱动程序2 1 spi访问接口2 2 寄存器和常用配置值2 3 初始化2 4 原始数据获取2 5 阻值换算 3 使用4 完整工程代码 1 前言 前面文章主要描述AD7793分别与两线 三线 四线RTD连接电
  • 【RTD】二分法查找和分段线性插值算法在RTD中应用

    文章目录 1 前言2 二分法查找2 1 复杂度2 2 实现 3 分段线性插值4 RTD实例 1 前言 处理器通过RTD采集电路 xff08 芯片 xff09 精确获得当前RTD电阻值后 xff0c 再结合RTD与温度线性关系表 xff0c
  • 24系列EEPROM/FRAM通用驱动库移植到RT-Thread

    文章目录 1 前言2 接口实现2 1 i2c收发函数实现2 2 页写延时函数2 3 写保护函数2 4 设备注册 3 对接RT Thread设备驱动3 1 标准设备驱动接口3 2 注册到RT Thread3 3 导出到msh3 4 测试 4
  • 【RT-Thread】TCA9534 8位I/O扩展器驱动软件包

    文章目录 1 简介1 1 目录结构1 2 许可证 2 芯片介绍3 支持情况4 使用说明4 1 依赖4 2 获取软件包4 3 初始化4 4 访问设备4 5 msh finsh测试查看设备注册执行sample 5 代码仓库 1 简介 tca95
  • 【代码质量】RAII在C++编程中的必要性

    文章目录 1 前言2 什么是RAII3 为什么用RAII4 RAII应用5 小结 1 前言 C C 43 43 相比其他高级编程语言 xff0c 具有指针的概念 xff0c 指针即是内存地址 C C 43 43 可以通过指针来直接访问内存空
  • C++ RAII典型应用之lock_guard和unique_lock模板

    文章目录 1 前言2 lock guard3 lock guard使用4 unique lock5 相关文章 1 前言 常用的线程间同步 通信 xff08 IPC xff09 方式有锁 xff08 互斥锁 读写锁 自旋锁 xff09 屏障
  • 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)

    前言 xff1a 此文为笔者FreeRTOS专栏 下的第一篇基础性教学文章 xff0c 其主要目的为 xff1a 帮助读者朋友快速搭建出属于自己的公版FreeRTOS系统 xff0c 实现后续在实时操作系统FreeRTOS上的开发与运用 操
  • 通过sysinfo获取Linux系统状态信息

    系统运行状态信息是我们关注的重点 xff0c 通过当前系统的输出信息 xff0c 如内存大小 进程数量 运行时间等 xff0c 以便分析CPU负载 软硬件资源占用情况 xff0c 确保系统高效和稳定 Linux系统中 xff0c 提供sys
  • Keil AC5/Keil AC6/IAR指定数据绝对存储地址

    文章目录 1 前言2 实现方法3 例子 1 前言 编译过程中 xff0c 指定数据绝对存储地址在实际项目中会经常使用到或者必须用到 xff0c 这样使得项目实现某些功能可以非常灵活 xff0c 常用的场景有 xff1a IAP升级时候 xf
  • 嵌入式开发常用到的在线工具

    文章目录 IP地址计算常用加解密 xff0c AES DSE Base64 MD5异或 xff08 BCC xff09 校验CRC计算十六进制格式化字符串Json格式化HTML运行器常用在线编译器 xff08 C C 43 43 C JAV
  • STM32H7xx 串口DMA发送&接收(LL库)

    文章目录 1 前言2 STM32H7实现2 1 关键步骤2 2 注意事项 3 代码仓库 1 前言 关于串口DMA收发实现 xff0c 不同CPU其套路都是类似的 xff0c 不同之处在于寄存器配置 依赖BSP库等差异 串口DMA收发详细实现
  • 正交编码器溢出处理

    文章目录 1 正交编码器1 1 参数特性1 2 应用范围 2 正交编码器使用2 1 溢出问题2 2 中断模式2 3 循环模式延伸 1 正交编码器 正交编码器一般指的是增量式光栅 xff08 磁栅 xff09 编码器 xff0c 通常有三路输
  • PX4多旋翼期望姿态矩阵生成算法

    1 PX4多旋翼期望姿态生成算法 1 1 求期望体轴X轴向量1 2 求期望体轴Y轴向量1 3 求期望姿态矩阵1 4 求期望姿态角 1 PX4多旋翼期望姿态生成算法 PX4多旋翼期望姿态生成采用旋转矩阵方法 xff0c 基本思路为根据外环解算
  • git安装

    Git介绍 分布式 xff1a Git版本控制系统是一个分布式的系统 xff0c 是用来保存工程源代码历史状态的命令行工具 保存点 xff1a Git的保存点可以追踪源码中的文件 并能得到某一个时间点上的整个工程项目的状态 xff1b 可以
  • linux更改ssh连接方式将publickey改为用户名密码登录

    1 vim etc ssh sshd config 2 PermitRootLogin no 改为 PermitRootLogin yes 3 service restart sshd
  • dsp2812 pmsm foc之速度环电流环

    61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 速度环PI 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • leetcode(c++)

    放假没事刷几道leetcode xff0c 一些常见典型题的答案和解析 平时python用的比较多 xff0c 但分析复杂度的时候用python编程不方便 xff0c 所以刷题的时候用了c 43 43 C 43 43 基础 C 43 43
  • 基于STM32的超声波雷达项目【可拟合构建平面地图】(代码开源)

    前言 xff1a 本文为手把手教学基于STM32的超声波雷达 项目 HC SR04雷达 本次项目采用的是STM32作为MCU xff0c 搭配常用的HC SR04超声波模块与舵机SG90实现模拟雷达检测 的效果 模拟了雷达图UI 可以拟合构
  • android sdk manager不显示更新,只显示已安装,解决办法

    启动 Android SDK Manager xff0c 打开主界面 xff0c 依次选择 Tools Options xff0c 弹出 Android SDK Manager Settings 窗口 xff1b 在 Android SDK
  • detectron2学习:KeyError: “No object named ‘XXXXX‘ found in ‘BACKBONE‘ registry!“

    问题来源 在使用FB的框架detectron2改写模型的时候碰到了KeyError 34 No object named 39 XXXXX 39 found in 39 BACKBONE 39 registry 34 的bug 分析 xff

随机推荐

  • linux内核网络协议栈--linux网络设备理解(十三)

    网络层次 linux网络设备驱动与字符设备和块设备有很大的不同 字符设备和块设备对应 dev下的一个设备文件 而网络设备不存在这样的设备文件 网络设备使用套接字socket访问 xff0c 虽然也使用read write系统调用 xff0c
  • makefile使用--命令(三)

    一 Make的概念 Make这个词 xff0c 英语的意思是 34 制作 34 Make命令直接用了这个意思 xff0c 就是要做出某个文件 比如 xff0c 要做出文件a txt xff0c 就可以执行下面的命令 span class t
  • 【笔记】ubuntu18.04 ros melodic turtlebot3 源码下,导航gmapping仿真

    编写本笔记原因 xff1a 源码编译没问题 xff0c 但是在运行roslaunch turtlebot3 slam turtlebot3 slam launch slam methods 61 cartgrapher时出现下面这个错误 x
  • 从kernel层面分析synchronized、volatile,进大厂必备硬核小伎俩(上)

    synchronized volatile对于java程序员来说再熟悉不过了 xff0c 但 是你知道这两个关键字底层是如何实现的吗 xff08 甚至在操作系层面是 通过什么指令来实现的 xff09 xff1f 以及与其相关的术语 xff1
  • linux内核原理剖析——内存寻址(一)

    最近总想分享点硬核的原创文章出来 xff0c 一是硬核技术是一个程序员真正应该修炼 的内功 xff1b 二是修炼硬核技能是通往架构师领域的必经之路 本系列文章将分享关 于linux内核设计原理相关的内容 xff0c 希望能打通我们的七经八脉
  • linux内核原理剖析——磁盘寻址、分区

    继上一篇 lt lt linux内核原理剖析 内存寻址 xff08 一 xff09 gt gt 之后 xff0c 发现大家 对底层技术关注度比较高之后 xff0c 今天继上一篇的内存寻址一文后 xff0c 补 充一篇关于更为底层的 磁盘寻址
  • Redis集群从搭建到设计,总有一些你不曾了解的东西

    Redis集群是Redis服务器高可用的设计模型 xff0c 也是我们线上应用最多的Redis部署架构 本文主要针对Redis集 群入门搭建 Redis集群节点及其底层数据结构 hash槽 重新分片 消息等核心操作及原理进行分享 本文是基于
  • java成神之路学习线路

    自己总结了下java后端学习线路 xff0c 也是我八年的工作学习积累 xff0c 供各位同学参考 线路图还不全 xff0c 之后我会逐渐补全 下面思维导图中的技术 xff0c 我争取在2020年的博文中都分享给大家 xff0c 形成一个系
  • 实时操作系统系统FreeRTOS的学习(1)——任务

    前言 xff1a 在本专栏 FreeRTOS 中已经为读者朋友详细介绍了FreeRTOS以及关于FreeRTOS于STM32下的手动移植 从今天开始将带领大家系统学习FreeRTOS xff0c 这款常见的轻量化小型 实时操作系统 当然 x
  • 万字博文,Spring系列之抽丝剥茧Spring源码(一)

    当5G来临 xff0c 当211高校已经开启人工智能课程 xff0c 当甲骨文大批量裁员 xff0c 大家的心是否像我一样为之一颤呢 xff1f 当科技不断发展 xff0c 技术迅速迭代 xff0c 程序员愈发年轻化的今天 xff0c 而作
  • 面试大厂必读、小厂吊打面试官必读书籍推荐

    通过我多年在互联网公司摸爬滚打 xff0c 以及近三年一直担任公司技术面试管经历 xff0c 本文结合我的工作经历 xff0c 分享给众多从事以及将要从事java后端软件开发的程序员们推荐一些必读书籍 xff0c 但并不只限于技术类书籍 深
  • 梦回大学,因它难以入睡,今一文总结之

    依稀记得10年前的大学第一门课就是它 c语言 xff0c 而指针作为c语言的核心之一 xff0c 当时一直难以理解 xff0c 从而转战java 而今10余年过去了 xff0c 依然还是放不下它 xff0c 也同时为了阅读openJDK源码
  • spring-framework核心链路全景图V0.1(持续更新,小白慎入)

    关于spring framework源码 xff0c 业界一直没有一个完整的链路流程全景图来供阅读学习spring framework源码的爱好者来参考 基于这个目的 xff0c 特梳理了spring framework相关的流程链路图 x
  • 真实互联网线上系统JVM内存溢出排查流程(文末彩蛋)

    起因 xff1a 近期在工作中发生因jvm内存溢出导致线上应用进程崩溃 xff0c 导致服务瞬间瘫痪 期间发现集群中每台应用服务器JVM内存使用率高达96 左右 xff0c 存在瞬间内存打满 xff0c 导致服务瘫痪情况 根据经验分析 xf
  • make: warning:  Clock skew detected.  Your build may be incomplete.

    这种问题一般是从一个设备拷贝到另一个设备上 xff0c 编译出现问题 xff1a 这种问题的原因是时钟偏差导致的 xff0c 就是检测到两个设备系统之间的时间上存在差距 xff1a 解决方案 xff1a find type f xargs
  • docker registry.docker-cn.com无法访问

    有时候我们在pull镜像的时候总是很慢 xff0c 这个时候网上的答案是把镜像的地址改成registry docker cn com以提高镜像拉取速度 34 registry mirrors 34 34 http registry dock
  • Java中的有序集合

    面试的时候经常会被问道这样一个问题 xff1a Java集合中哪些是有序的 xff0c 哪些不是 xff1f 我们通常的回答是List LinkedHashMap LinkedHashSet TreeMap TreeSet是有序的 xff0
  • MySQL中的char、varchar(10)、varchar(1000)的区别

    这里写自定义目录标题 tipsMySQL中的varchar与char的区别varchar 100 与varchar 1000 的区别参考 tips 若无特殊指明 xff0c 文中提到的存储空间指的都是占用磁盘空间 MySQL中的varcha
  • validation参数检验 - 注解介绍

    文章目录 Maven 依赖注解介绍javax validation 中的注解 xff08 22个 xff09 Null NotNullNotBlankNotEmptySizeAssertFalse AssertTrueDecimalMax
  • 基于STM32与OneNet平台的智能家居系统设计(代码开源含自制APP代码)

    前言 xff1a 本文为手把手教学的基础物联网开发设计 xff0c 项目包含对下位机 xff08 MCU对外设数据读取与控制 xff09 和上位机 xff08 包含服务平台和APP端 xff09 的设计 下位机选取STM32作为MCU xf