前言

开通实例,创建产品设备可参考blog

开通阿里云物联网平台实例 – 青木ckyficon-default.png?t=N7T8https://www.ckyf.site/index.php/2024/01/12/%E5%BC%80%E9%80%9A%E9%98%BF%E9%87%8C%E4%BA%91%E7%89%A9%E8%81%94%E7%BD%91%E5%B9%B3%E5%8F%B0%E5%AE%9E%E4%BE%8B/

有关MQTT协议内容介绍及AT指令集相关知识,可浏览主页长空有风-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_64899638?spm=1011.2124.3001.5343

 1 MQTT协议

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。

MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,因此易于实现。这些特点使得它对很多场景来说都是很好的选择,包括受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT),这些场景要求很小的代码封装或者网络带宽非常昂贵。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。

MQTT消息发布步骤:

1)发布者通过TCP/IP协议连接到代理服务器;

2)发布者向代理服务器发送CONNECT消息,请求连接;

3)代理服务器接收CONNECT消息后,将进行连接验证和身份认证;

4)连接验证和身份认证完成后,代理服务器向发布者发送CONNACK(连续报文确认)消息,表示连接已建立;

5)发布者向代理服务器发送PUBLISH消息,包含发布的消息内容和指定的主题。

MQTT消息订阅步骤:

1)订阅者通过TCP/IP协议连接到MQTT代理服务器;

2)订阅者想代理服务器发送CONNECT消息,请求连接;

3)代理服务器接收CONNECT消息后,将进行连接验证和身份认证;

4)连接验证和身份认证完成后,代理服务器向订阅者发送CONNACK(连续报文确认)消息,表示连接已建立;

5)订阅者向代理服务器发送SUBSCIRBE消息后,指定要订阅的主题和效应的QoS级别;

6)代理服务器接收到SUBSCRIBE消息后,根据订阅者请求进行主题订阅;

7)代理服务器向订阅者发送SUBACK(订阅请求报文确认)消息,表示订阅已被接受;

8)当有新的消息发布到订阅者订阅的主题上时,代理服务器将消息推送给订阅者;

MQTT消息取消订阅:

1)订阅者可以随时发送UNSUBSCRIBE消息,取消对某个或者多个主题的订阅;

2)代理服务器接受到UNSUBSCRIBE消息后,删除响应的订阅关系。

2 Espressif AT 指令集功能以及使用方法

指令集主要分为: 基础 AT 命令、 Wifi 功能 AT 命令、 TCP/IP 工具箱 AT 命令等。

可参考→【免费】EspressifAT指令集v018资源-CSDN文库

3 stm32硬件配置

设备功能
STM32F103C8T6MCU
OLED屏幕显示连接
ESP8266PB12\PB13\PB14\PB15
传感器

温湿度传感器(SHT30)

光照传感器
可燃气体传感器
执行器蜂鸣器
电磁锁(LY-011A)
调光灯(RGB)

4 stm32代码设计

ESP8266_at.c中设计在透传模式下使用指定协议(TCP)连接到服务器,基本包括检查ESP8266是否正常、初始化ESP8266、恢复出厂设置、连接热点和使用指定协议(TCP/UDP)连接到服务器等功能

/**
 * 功能:初始化ESP8266
 * 参数:None
 * 返回值:初始化结果,非0为初始化成功,0为失败
 */
uint8_t ESP8266_Init(void)
{
	//清空发送和接收数组
	memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));
	memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
	
	ESP8266_ExitUnvarnishedTrans();		//退出透传
	delay_ms(500);
	ESP8266_ATSendString("AT+RST\r\n");
	delay_ms(800);
	if(ESP8266_Check()==0)              //使用AT指令检查ESP8266是否存在
	{
		return 0;
	}
	
	memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));    //清空接收缓冲
	ESP8266_ATSendString("ATE0\r\n");     	//关闭回显 
	if(FindStr((char*)ESP8266_rxbuf,"OK",500)==0)  //设置不成功
	{
			return 0;      
	}
	return 1;                         //设置成功
}

/**
 * 功能:恢复出厂设置
 * 参数:None
 * 返回值:None
 * 说明:此时ESP8266中的用户设置将全部丢失回复成出厂状态
 */
void ESP8266_Restore(void)
{
	ESP8266_ExitUnvarnishedTrans();          	//退出透传
  delay_ms(500);
	ESP8266_ATSendString("AT+RESTORE\r\n");		//恢复出厂 	
}

/**
 * 功能:连接热点
 * 参数:
 *         ssid:热点名
 *         pwd:热点密码
 * 返回值:
 *         连接结果,非0连接成功,0连接失败
 * 说明: 
 *         失败的原因有以下几种(UART通信和ESP8266正常情况下)
 *         1. WIFI名和密码不正确
 *         2. 路由器连接设备太多,未能给ESP8266分配IP
 */
uint8_t ESP8266_ConnectAP(char* ssid,char* pswd)
{
	uint8_t cnt=5;
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));     
		ESP8266_ATSendString("AT+CWMODE_CUR=1\r\n");              //设置为STATION模式	
		if(FindStr((char*)ESP8266_rxbuf,"OK",200) != 0)
		{
			break;
		}             		
	}
	if(cnt == 0)
		return 0;

	cnt=2;
	while(cnt--)
	{                    
		memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));//清空发送缓冲
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));//清空接收缓冲
		sprintf((char*)ESP8266_txbuf,"AT+CWJAP_CUR=\"%s\",\"%s\"\r\n",ssid,pswd);//连接目标AP
		ESP8266_ATSendString((char*)ESP8266_txbuf);	
		if(FindStr((char*)ESP8266_rxbuf,"OK",8000)!=0)                      //连接成功且分配到IP
		{
			return 1;
		}
	}
	return 0;
}

//开启透传模式
static uint8_t ESP8266_OpenTransmission(void)
{
	//设置透传模式
	uint8_t cnt=2;
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));    
		ESP8266_ATSendString("AT+CIPMODE=1\r\n");  
		if(FindStr((char*)ESP8266_rxbuf,"OK",200)!=0)
		{	
			return 1;
		}
	}
	return 0;
}

/**
 * 功能:使用指定协议(TCP/UDP)连接到服务器
 * 参数:
 *         mode:协议类型 "TCP","UDP"
 *         ip:目标服务器IP
 *         port:目标是服务器端口号
 * 返回值:
 *         连接结果,非0连接成功,0连接失败
 * 说明: 
 *         失败的原因有以下几种(UART通信和ESP8266正常情况下)
 *         1. 远程服务器IP和端口号有误
 *         2. 未连接AP
 *         3. 服务器端禁止添加(一般不会发生)
 */
uint8_t ESP8266_ConnectServer(char* mode,char* ip,uint16_t port)
{
	uint8_t cnt;
   
	ESP8266_ExitUnvarnishedTrans();                   //多次连接需退出透传
	delay_ms(500);

	//连接服务器
	cnt=2;
	while(cnt--)
	{
		memset(ESP8266_txbuf,0,sizeof(ESP8266_txbuf));//清空发送缓冲
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));//清空接收缓冲   
		sprintf((char*)ESP8266_txbuf,"AT+CIPSTART=\"%s\",\"%s\",%d\r\n",mode,ip,port);
		ESP8266_ATSendString((char*)ESP8266_txbuf);
		if(FindStr((char*)ESP8266_rxbuf,"CONNECT",500) !=0 )
		{
			break;
		}
	}
	if(cnt == 0) 
		return 0;
	
	//设置透传模式
	if(ESP8266_OpenTransmission()==0) return 0;
	
	//开启发送状态
	cnt=2;
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲   
		ESP8266_ATSendString("AT+CIPSEND\r\n");//开始处于透传发送状态
		if(FindStr((char*)ESP8266_rxbuf,">",200)!=0)
		{
			return 1;
		}
	}
	return 0;
}

/**
 * 功能:主动和服务器断开连接
 * 参数:None
 * 返回值:
 *         连接结果,非0断开成功,0断开失败
 */
uint8_t DisconnectServer(void)
{
	uint8_t cnt;
	
	ESP8266_ExitUnvarnishedTrans();	//退出透传
	delay_ms(500);
	
	while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲   
		ESP8266_ATSendString("AT+CIPCLOSE\r\n");//关闭链接

		if(FindStr((char*)ESP8266_rxbuf,"CLOSED",200)!=0)//操作成功,和服务器成功断开
		{
			break;
		}
	}
	if(cnt) return 1;
	return 0;
}

ESP8266_MQTT.c中不涉及硬件问题,进行对MQTT协议的包装,包括MQTT连续服务器的打包函数、MQTT订阅/取消订阅数据打包函数、MQTT发布数据打包函数等内容

//MQTT连续服务器的打包函数
uint8_t MQTT_Connect(char *ClientID,char *Username,char *Password)
{
	int ClientIDLen = strlen(ClientID);
	int UsernameLen = strlen(Username);
	int PasswordLen = strlen(Password);
	int DataLen;
	MQTT_TxLen=0;
	//可变报头+Payload  每个字段包含两个字节的长度标识
	DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
	
	//固定报头
	//控制报文类型
	ESP8266_txbuf[MQTT_TxLen++] = 0x10;		//MQTT Message Type CONNECT
	//剩余长度(不包括固定头部)
	do
	{
		uint8_t encodedByte = DataLen % 128;
		DataLen = DataLen / 128;
		// if there are more data to encode, set the top bit of this byte
		if ( DataLen > 0 )
			encodedByte = encodedByte | 128;
		ESP8266_txbuf[MQTT_TxLen++] = encodedByte;
	}while ( DataLen > 0 );
    	
	//可变报头
	//协议名
	ESP8266_txbuf[MQTT_TxLen++] = 0;        		// Protocol Name Length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = 4;        		// Protocol Name Length LSB    
	ESP8266_txbuf[MQTT_TxLen++] = 'M';        	// ASCII Code for M    
	ESP8266_txbuf[MQTT_TxLen++] = 'Q';        	// ASCII Code for Q    
	ESP8266_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T    
	ESP8266_txbuf[MQTT_TxLen++] = 'T';        	// ASCII Code for T    
	//协议级别
	ESP8266_txbuf[MQTT_TxLen++] = 4;        		// MQTT Protocol version = 4    
	//连接标志
	ESP8266_txbuf[MQTT_TxLen++] = 0xc2;        	// conn flags 
	ESP8266_txbuf[MQTT_TxLen++] = 0;        		// Keep-alive Time Length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = 60;        	// Keep-alive Time Length LSB  60S心跳包  

	ESP8266_txbuf[MQTT_TxLen++] = BYTE1(ClientIDLen);// Client ID length MSB    
	ESP8266_txbuf[MQTT_TxLen++] = BYTE0(ClientIDLen);// Client ID length LSB  	
	memcpy(&ESP8266_txbuf[MQTT_TxLen],ClientID,ClientIDLen);
	MQTT_TxLen += ClientIDLen;
	
	if(UsernameLen > 0)
	{   
		ESP8266_txbuf[MQTT_TxLen++] = BYTE1(UsernameLen);		//username length MSB    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE0(UsernameLen);    	//username length LSB    
		memcpy(&ESP8266_txbuf[MQTT_TxLen],Username,UsernameLen);
		MQTT_TxLen += UsernameLen;
	}
	
	if(PasswordLen > 0)
	{    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE1(PasswordLen);		//password length MSB    
		ESP8266_txbuf[MQTT_TxLen++] = BYTE0(PasswordLen);    	//password length LSB  
		memcpy(&ESP8266_txbuf[MQTT_TxLen],Password,PasswordLen);
		MQTT_TxLen += PasswordLen; 
	}    
	
	//uint8_t cnt=2;
	uint8_t wait;
	//while(cnt--)
	{
		memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf));
		MQTT_SendBuf(ESP8266_txbuf,MQTT_TxLen);
		wait=30;//等待3s时间
		while(wait--)
		{
			//CONNECT
			if(ESP8266_rxbuf[0]==parket_connetAck[0] && ESP8266_rxbuf[1]==parket_connetAck[1]) //连接成功			   
			{
				return 1;//连接成功
			}
			delay_ms(100);			
		}
	}
	return 0;
}

main.c中设计主要的连接参数

//WIFI配置
#define WIFI_NAME               "xxxxx"           //wifi名称 
#define WIFI_PASSWD             "xxxxxxxxx"       //wifi密码
//阿里云服务器的登陆配置         阿里云物联网设备参数
#define MQTT_BROKERADDRESS	    "****mqttHostUrl****"
#define MQTT_CLIENTID 		    "****clientId********"
#define MQTT_USARNAME 		    "****username******"
#define MQTT_PASSWD 			"****password******"
//在头文件中添加订阅和发布的话题
#define	MQTT_PUBLISH_TOPIC "/sys/*ProductKey*/*devicename*/thing/event/property/post" 
#define MQTT_SUBSCRIBE_TOPIC "/sys/*ProductKey*/*devicename*/thing/service/property/set"

错误代码模式输出

/******************************  进入错误模式代码  *****************************/

//进入错误模式等待手动重启
void Enter_ErrorMode(uint8_t mode)
{
	GPIO_SetBits(GPIOG,GPIO_Pin_11);
	while(1)
	{
		switch(mode){
			case 0:user_main_error("ESP8266初始化失败!\r\n");break;
			case 1:user_main_error("ESP8266连接热点失败!\r\n");break;
			case 2:user_main_error("ESP8266连接阿里云服务器失败!\r\n");break;
			case 3:user_main_error("ESP8266阿里云MQTT登陆失败!\r\n");break;
			case 4:user_main_error("ESP8266阿里云MQTT订阅主题失败!\r\n");break;
			default:user_main_info("Nothing\r\n");break;
		}
		user_main_info("请重启开发板");
		
		delay_ms(200);
	}
}

MQTT业务初始化

//MQTT初始化函数
void ES8266_MQTT_Init(void)
{
	uint8_t status=0;
	//初始化
	if(ESP8266_Init())
	{
		user_main_info("ESP8266初始化成功!\r\n");
		status++;
	}
	else Enter_ErrorMode(0);
	//连接热点
	if(status==1)
	{
		if(ESP8266_ConnectAP(WIFI_NAME,WIFI_PASSWD))
		{
			user_main_info("ESP8266连接热点成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(1);
	}
	//连接阿里云IOT服务器
	if(status==2)
	{
		if(ESP8266_ConnectServer("TCP",MQTT_BROKERADDRESS,1883)!=0)
		{
			user_main_info("ESP8266连接阿里云服务器成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(2);
	}
	//登陆MQTT
	if(status==3)
	{
		if(MQTT_Connect(MQTT_CLIENTID, MQTT_USARNAME, MQTT_PASSWD) != 0)
		{
			user_main_info("ESP8266阿里云MQTT登陆成功!\r\n");
			status++;
		}
		else Enter_ErrorMode(3);
	}
	//订阅主题
	if(status==4)
	{
		if(MQTT_SubscribeTopic(MQTT_SUBSCRIBE_TOPIC,0,1) != 0)
		{
			user_main_info("ESP8266阿里云MQTT订阅主题成功!\r\n");
		}
		else Enter_ErrorMode(4);
	}
}

设置临时字串用以接收连接成功后,服务器应答信号或者指令信息,将消息处理,可通过串口收到消息提示(此处可用于网络侧控制执行器)

char temp_str[30];    // 临时子串
// 从母串中获取与子串长度相等的临时子串
void ReadStrUnit(char * str,char *temp_str,int idx,int len)  
{
    int index;
    for(index = 0; index < len; index++)
    {
        temp_str[index] = str[idx+index];
    }
    temp_str[index] = '\0';
}

int GetSubStrPos(char *str1,char *str2)
{
    int idx = 0;
    int len1 = strlen(str1);
    int len2 = strlen(str2);

    if( len1 < len2)
    {
        //printf("error 1 \n"); // 子串比母串长
        return -1;
    }
    while(1)
    {
        ReadStrUnit(str1,temp_str,idx,len2);    // 不断获取的从 母串的 idx 位置处更新临时子串
        if(strcmp(str2,temp_str)==0)break;      // 若临时子串和子串一致,结束循环
        idx++;                                  // 改变从母串中取临时子串的位置
        if(idx>=len1)return -1;                 // 若 idx 已经超出母串长度,说明母串不包含该子串
    }
    return idx;    // 返回子串第一个字符在母串中的位置
}
//处理MQTT下发的消息
void deal_MQTT_message(uint8_t* buf,uint16_t len)
{
	memset(ESP8266_rxbuf,0,sizeof(ESP8266_rxbuf)); //清空接收缓冲  
	usart_rxcounter=0;
	user_main_info("MQTT收到消息,数据长度=%d \n",len);
}

单片机属性上报(仅以温湿度为例)

//单片机状态上报
void TempAndHumi_StatusReport(void)
{
	float t=0.0, h=0.0;	
	SHT30_GetValue(&t, &h);	// 采集温湿度
		
	//上报一次数据
	sprintf(mqtt_message,
	"{\"params\":{\"temperature\":%.1f,\"humi\":%.1f}}",(float)t,(float)h);

	MQTT_PublishData(MQTT_PUBLISH_TOPIC,mqtt_message,0);
}

主程序中设置连接与上报功能

while(1)
	{
		//运行灯闪烁
		if(Counter_RUNLED_Blink++>COUNTER_LEDBLINK)
		{
			Counter_RUNLED_Blink = 0;
			LED_Toggle();
		}
		
		//运行状态打印
		if(Counter_RUNInfo_Send++>COUNTER_RUNINFOSEND)
		{
			Counter_RUNInfo_Send = 0;
			user_main_info("程序正在运行!\r\n");
		}
		
		//心跳包发送
		if(Counter_MQTT_Heart++>COUNTER_MQTTHEART)
		{
			Counter_MQTT_Heart = 0;
			MQTT_SentHeart();
		}
		
		//本机状态上报
		if(Counter_StatusReport++>COUNTER_STATUSREPORT)
		{
			Counter_StatusReport = 0;
			TempAndHumi_StatusReport();
		}
		
		//如果接收缓存有数据
		if(usart_rxcounter)
		{
			deal_MQTT_message(ESP8266_rxbuf,usart_rxcounter);
			OLED_ShowString(0,4,"CONNECTING",16);
		}

		delay_ms(LOOPTIME);
	}

5 实验步骤

1.建立keil工程

2.添加相关代码

3.配置工程设置

4.填写个人MQTT参数

5连接ST-Link仿真器,下载程序

6 实验结果

实验程序烧写完毕后,使用MiniUSB线连接串口,使用串口工具查看打印信息。串口2正常打印出程序工作信息;

阿里云物联网平台可以观测到在线状态和日志信息,在设备的物模型数据中,打开实时刷新功能,即可在云端观测到当前温湿度。

无线节点模块OLED显示CONNECTING,GPIO_LED闪烁。

7.源码文件及相关

00-STM32-WIFI-阿里云物联网平台实验

01-STM32-WIFI-阿里云物联网平台温湿度sht30实验

02-STM32-WIFI-阿里云物联网平台可燃气体检测实验

03-STM32-WIFI-阿里云物联网平台光照采集实验

04-STM32-WIFI-阿里云物联网平台蜂鸣器控制实验

05-STM32-WIFI-阿里云物联网平台电磁锁控制实验

06-STM32-WIFI-阿里云物联网平台调光灯控制实验

更多推荐