基于STM32+ESP8266设计物联网产品(重点:支持微信小程序一键配网连接腾讯云平台)

Posted DS小龙哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于STM32+ESP8266设计物联网产品(重点:支持微信小程序一键配网连接腾讯云平台)相关的知识,希望对你有一定的参考价值。

一、环境介绍

编程软件: keil5

主控MCU: STM32F103C8T6

WIFI: ESP8266

协议:  MQTT

完整项目源码下载地址:https://download.csdn.net/download/xiaolong1126626497/19137788

二、前言

这里的 WIFI型号不重要、主控MCU不重要,连接的物联网平台也不重要。

要完成本章节的内容,只要会熟悉某款单片机的编程、了解基本的网络编程,明白MQTT协议、能读懂每个物联网云平台的帮助文档都可以完成最终的效果。

 

三、功能介绍

前面有几篇内容都介绍了如何使用在腾讯物联网平台创建设备,完成微信小程序与设备进行交互;这些设备代码里的连接的WIFI名称和密码都是固定,只能通过每次修改程序、编译、下载才能更改。一个正常的物联网智能设备,这样操作肯定是不合理的,所以这篇内容就完成如何使用微信小程序一键配网,完成设备的WIFI切换、连接。

现在我们购买的智能设备都有自己的配网方式,比如: 小米的很多设备,小爱音箱,摄像头,扫地机器人等。这些设备买回来之后,用户可以参考说明书,完成对设备的配置,让设备连接上家里的WIFI,完成网络连接。

本次我以智能锁为产品模型,在腾讯物联网平台创建一个设备,使用STM32F103系统板+ESP8266+LED灯完成智能锁产品的模拟开发;用户设备端可以按下指定的按键进入配网模式,打开腾讯官方的微信小程序,扫描产品二维码,根据步骤完成对设备的配网操作。

腾讯物联网支持了好几种配网模式,我这里选择的是“softAP”模式来完成配网操作。 

softAP 模式配网的原理介绍:  正常情况下我们买回来的新设备内部是没有我们自己家WIFI的信息的,也就是说这个设备上电之后自己不知道该连接哪一个WIFI;这时我们就需要想办法把我们自己家里的WIFI名称、WIFI密码告诉这个设备,这个设备就可以去连接了。  那问题是怎么去告诉设备这些信息?  设备一般都有进入配网模式的按钮,进入配网模式之后,会将设备内部的WIFI设置成“softAP”模式,也就是设备自己会创建一个WIFI热点出来并创建UDP服务器监听连接,这时我们打开腾讯官方的微信小程序,按照指引去连接这个WIFI,连上之后,微信小程序会通过UDP协议将WIFI的配置信息传输给设备WIFI,设备WIFI收到之后,再切换模式为STA模式,去连接目标WIFI,连接成功之后,登录云平台,绑定设备,完成配网。  这其中的交互协议,后面再细说。

四、在腾讯云平台上创建智能锁

本章节只会展示几个关键步骤,如果之前没有使用过腾讯物联网云平台可以参考这里学习一遍:https://blog.csdn.net/xiaolong1126626497/article/details/116902653

功能很简单,只有一个属性,就是锁的开关状态。

这里可以配置微信小程序的详细参数,配网的设置也这个页面上:

下面进行配网设置:

选择配网模式:

 

这个页面比较重要,需要将设备进入配网的方法告诉用户,引导用户去操作,完成进入配网模式:

保存之后,打开微信小程序“腾讯连连”,扫描右下角的这个二维码,进行配网,完成设备添加。(一般正常产品,会将这个二维码打印出来,贴在设备上,方便用户扫描)

(提示: 做这一步,要先设计好设备端的程序,设备上电能正常的运行,才能做)

下面是手机上的截图: (根据页面上的提示操作设备)

按下开发板子上的S2进入配网模式:

在串口上也可以看到提示信息。

这时继续操作微信小程序上的步骤,选择设备的WIFI进行连接: 之后在下一个页面会自动等待配网成功(没有截图),设备配网成功之后会出现提示,这时设备就已经在线了

打开控制台已经看到设备上线了: 

这时进入小程序页面,就可以对智能锁进行操作了:

到此,配网已经完成,接下来就介绍设备端的代码。

五、STM32设备端代码--这才是核心

关于配网的流程,在腾讯官网有详细介绍,看这里:https://cloud.tencent.com/document/product/1081/48404

由于关联代码较多,这里只提供主要的逻辑代码,其他的代码可以自己下载完整源码查看:https://download.csdn.net/download/xiaolong1126626497/19137788

main.c 代码:

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include <string.h>
#include "timer.h"
#include "bluetooth.h"
#include "esp8266.h"
#include "mqtt.h"

/*
智能锁(自己的设备)

MQTT服务器地址: 106.55.124.154
MQTT服务器端口: 1883
MQTT客户端ID: 3XM7FNOG4Llock
MQTT用户名: 3XM7FNOG4Llock;12010126;F8Q4P;1624710719
MQTT登录密码: 5d87e9a5bf8ae6295493c263b91aaebc4311f3e95763efe7f31be76c8578f9ec;hmacsha256

订阅主题: $thing/down/property/3XM7FNOG4L/lock
发布主题: $thing/up/property/3XM7FNOG4L/lock
发布消息: {"method":"report","clientToken":"123","params":{"lock":1}}
*/

#define SERVER_IP "106.55.124.154"//服务器IP
#define SERVER_PORT 1883 //端口号

#define CONNECT_WIFI  "_CMCC-Cqvn"   //将要连接的路由器名称 --不要出现中文、空格等特殊字符
#define CONNECT_PASS "_99pu58cb"     //将要连接的路由器密码

//腾讯物联网服务器的设备信息
#define MQTT_ClientID "3XM7FNOG4Llock"
#define MQTT_UserName "3XM7FNOG4Llock;12010126;F8Q4P;1624710719"
#define MQTT_PassWord "5d87e9a5bf8ae6295493c263b91aaebc4311f3e95763efe7f31be76c8578f9ec;hmacsha256"

//订阅与发布的主题
#define SET_TOPIC  "$thing/down/property/3XM7FNOG4L/lock"  //订阅
#define POST_TOPIC "$thing/up/property/3XM7FNOG4L/lock"  //发布

//微信小程序配网数据订阅与发布
#define SET_WEIXIN_TOPIC  "$thing/down/service/3XM7FNOG4L/lock"//订阅
#define POST_WEIXIN_TOPIC "$thing/up/service/3XM7FNOG4L/lock"//发布

char mqtt_message[200];//上报数据缓存区

int main()
{
   u32 time_cnt=0;
   u32 i;
   u8 key;
   u8 stat=0;
    
   //1.初始化需要使用的硬件
   LED_Init();
   BEEP_Init();
   KEY_Init();
    
   //2. 初始化串口1(打印调试信息)与串口3(与WIFI通信)
   USART1_Init(115200);
   TIMER1_Init(72,20000); //超时时间20ms
   USART3_Init(115200);//串口-WIFI
   TIMER3_Init(72,20000); //超时时间20ms
   USART1_Printf("正在初始化WIFI请稍等.\\n");
  
   //3. 检测WIFI硬件 
   while(1)
   {
        //如果硬件有问题. 蜂鸣器以300ms的频率报警
        if(ESP8266_Init())
        {
          delay_ms(300);
          BEEP=!BEEP;
          USART1_Printf("ESP8266硬件检测错误.\\n");  
        }
        else
        {
            BEEP=0; //关闭蜂鸣器
            break;  //硬件没有问题. 退出检测
        }
   }       
  
   //4. 上电如果检测到S2按键按下就表示需要进入配网状态
    if(KEY_S2)
    {
        delay_ms(100);
        if(KEY_S2)
        {
            while(1)//连接服务器
            {
                printf("进入配网模式.....\\n");
                BEEP=1;
                delay_ms(100);
                BEEP=0;
                
                //清除之前的WIFI连接信息(连接个无效的WIFI),防止默认连接上 上次的WIFI,导致配网错误
                ESP8266_SendCmd("AT+CWJAP=\\"666\\",\\"12345678\\"\\r\\n");
                delay_ms(200);
                
                stat=Esp8266_STA_TCPclinet_Init((u8 *)SERVER_IP,SERVER_PORT);
                if(stat==0 || stat==0x80)break;
                delay_ms(500);
                printf("stat=%d\\r\\n",stat);
            }
        }
        else
        {
            stat=0xFF;
        }
    }
    else
    {
        stat=0xFF;
    }
    
    //连接默认WIFI
    if(stat==0xFF)
    {
        printf("连接默认的WIFI.\\n");
         //非加密端口
        USART1_Printf("WIFI:%d\\n",ESP8266_STA_TCP_Client_Mode(CONNECT_WIFI,CONNECT_PASS,SERVER_IP,SERVER_PORT,1));
    }
    
    //2. MQTT协议初始化	
    MQTT_Init(); 
    //3. 连接OneNet服务器        
    while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord))
    {
        USART1_Printf("服务器连接失败,正在重试...\\n");
        delay_ms(500);
    }
    USART1_Printf("服务器连接成功.\\n");
    
    //3. 订阅主题
    if(MQTT_SubscribeTopic(SET_TOPIC,0,1))
    {
        USART1_Printf("主题订阅失败.\\n");
    }
    else
    {
        USART1_Printf("主题订阅成功.\\n");
    }        
   
    
    if(stat==0x80)//进入配网模式需要给微信小程序返回token值
    {
          //订阅微信topic
         if(MQTT_SubscribeTopic(SET_WEIXIN_TOPIC,0,1))printf("订阅失败\\r\\n"); 
        //返回平台数据,告知微信连连连接服务器成功
        snprintf(mqtt_message,sizeof(mqtt_message),"{\\"method\\":\\"app_bind_token\\",\\"clientToken\\":\\"client-1234\\",\\"params\\": {\\"token\\":\\"%s\\"}}",esp8266_info.token);
        MQTT_PublishData(POST_WEIXIN_TOPIC,mqtt_message,0);
        //Smart_home{"method":"app_bind_token_reply","clientToken":"client-1234","code":0,"status":"success"} 配网成功后微信小程序返回数据
    }
    
    while(1)
    {    
        key=KEY_Scan(0);
        if(key==2)
        {
            time_cnt=0;
            sprintf(mqtt_message,"{\\"method\\":\\"report\\",\\"clientToken\\":\\"123\\",\\"params\\":{\\"lock\\":1}}");
            MQTT_PublishData(POST_TOPIC,mqtt_message,0);
            USART1_Printf("开锁.\\r\\n");
        }
        else if(key==3)
        {
            time_cnt=0;
            sprintf(mqtt_message,"{\\"method\\":\\"report\\",\\"clientToken\\":\\"123\\",\\"params\\":{\\"lock\\":0}}");
            MQTT_PublishData(POST_TOPIC,mqtt_message,0);
            USART1_Printf("关锁\\r\\n");
        }  

        if(USART3_RX_FLAG)
        {
            USART3_RX_BUFFER[USART3_RX_CNT]='\\0';
            for(i=0;i<USART3_RX_CNT;i++)
            {
                USART1_Printf("%c",USART3_RX_BUFFER[i]);
            }
            
            //关锁
            if(strstr((char*)USART3_RX_BUFFER,"\\"lock\\":0"))
            {
                LED4=1;
            }
            //开锁
            else if(strstr((char*)USART3_RX_BUFFER,"\\"lock\\":1"))
            {
                LED4=0;
            }
            
            USART3_RX_CNT=0;
            USART3_RX_FLAG=0;
        }

        //定时发送心跳包,保持连接
        delay_ms(10);
        time_cnt++;
        if(time_cnt==500)
        {
            LED1=!LED1;
            MQTT_SentHeart();//发送心跳包
            time_cnt=0;
        }
    }
}

ESP8266的核心代码:

//存放ESP8266的详细信息
struct ESP8266 esp8266_info;

/*SoftAP配网*/
u8 ESP8266_SoftAP_MOde(void)
{
    u8 token[]="{\\"cmdType\\":2,\\"productId\\":\\"3XM7FNOG4L\\",\\"deviceName\\":\\"lock\\",\\"protoVersion\\":\\"2.0\\"}\\r\\n";//连接状态信息
    char *p=NULL;
    char data[256];
    char buff[100];
    u8 i=0;
    u32 time1=0,time2=0;
    USART3_RX_CNT=0;
    USART3_RX_FLAG=0;
    while(1)
    {
        if(USART3_RX_FLAG)
        {
            USART3_RX_BUFFER[USART3_RX_CNT]='\\0';
            printf("rx=%s",USART3_RX_BUFFER);
            //+IPD,97,192.168.4.2,52021:{"cmdType":1,"ssid":"wbyq_wifi","password":"12345678","token":"df4a4c90abee98c9a443ae8ffd8cc16b"
            p=strstr((char *)USART3_RX_BUFFER,"+IPD");
            if(p)
            {
                strcpy(data,p);//将接收到的数据拷贝一份保存
                p+=strlen("+IPD");
                p+=1;
                while(*p!=',' && *p!='\\0')p++;
                p++;//跳过字符',',获取到IP地址起始位置
                i=0;
                //IP地址解析
                while(*p!=',' && *p!='\\0')
                {
                   buff[i++]=*p++;
                }
                buff[i]='\\0';
                strcpy((char *)esp8266_info.esp8266_ip,buff);
                //端口号解析
                p++;
                i=0;
                while(*p!=':' && *p!='\\0')
                {
                   buff[i++]=*p++; 
                }
                buff[i]='\\0';
                esp8266_info.esp8266_prot=atoi(buff);//字符串转整数
                //printf("ip=%s:%d\\r\\n",esp8266_info.esp8266_ip,esp8266_info.esp8266_prot);
                printf("ret:%d\\r\\n",Esp8266_UDP_SendData((u8*)esp8266_info.esp8266_ip,esp8266_info.esp8266_prot,token));//上报连接状态
            }
           ESP8266_GetData(data,(char *)esp8266_info.esp8266_name,"ssid");//WIFI名
           ESP8266_GetData(data,(char *)esp8266_info.esp8266_key,"password");//密码
           ESP8266_GetData(data,(char *)esp8266_info.token,"token");//token数据,需要返回给平台
           printf("wifi_name:%s\\r\\n",esp8266_info.esp8266_name);
           printf("wifi_key:%s\\r\\n",esp8266_info.esp8266_key); 
           printf("wifi_token:%s\\r\\n",esp8266_info.token);            
           LED1=1;
           return 0;
        }
        delay_ms(1);
        time1++;
        time2++;
        if(time2>=100)
        {
           time2=0;
           LED1=!LED1;
        }  
        if(time1>=1000*300)
        {
            LED1=1;
            break;//超时退出
        }
    }
    return 1;
}


/*******************************************************************************************************************
**形参: wifi_name --WIFI名
**      password  --密码
**      remote_ip  --远端IP地址(255.255.255.255为广播地址)
**      remote_prot  --远端端口号
**      localhost    ---本地端口号
**返回值:0 --成功,
**       其它值 --失败
**示例:ESP8266_UDP_STA_Mode("360WIFI_123","12345678","172.20.7.2",10500,8080);
*********************************************************************************************************************/
u8 ESP8266_UDP_STA_Mode(u8 *wifi_name,u8 *password,u8 *remote_ip,u16 remote_prot,u16 localprot)
{
    char buff[100];
    USARTx_StringSend(USART3,"+++"); //退出透传模式
    delay_ms(1000);
    
    printf("重启模块.......\\r\\n");
    USARTx_StringSend(USART3,"AT+RST\\r\\n");
    delay_ms(1000);
    delay_ms(1000); 
    printf("关回显.......\\r\\n");
    if(ESP8266_SendCmd("ATE0\\r\\n"))return 2; 
    printf("设置为STA模式.......\\r\\n");
    if(ESP8266_SendCmd("AT+CWMODE=1\\r\\n"))return 3;   
    printf("连接WIFI.......\\r\\n");
    snprintf(buff,sizeof(buff),"AT+CWJAP=\\"%s\\",\\"%s\\"\\r\\n",wifi_name,password);
    if(ESP8266_SendCmd(buff))return 5;   
    printf("查询IP.......\\r\\n");
    if(ESP8266_SendCmd("AT+CIFSR\\r\\n"))return 6; 
    printf("建立UDP连接.....\\r\\n");
    snprintf(buff,sizeof(buff),"AT+CIPSTART=\\"UDP\\",\\"%s\\",%d,%d,0\\r\\n",remote_ip,remote_prot,localprot);
    if(ESP8266_SendCmd(buff))return 7; 
    printf("设置透传.......\\r\\n");
    if(ESP8266_SendCmd("AT+CIPMODE=1\\r\\n"))return 8; 
    printf("发送数据.......\\r\\n");
    USARTx_StringSend(USART3,"AT+CIPSEND\\r\\n");
    return 0;
}


/****************STA+TCPclinet初始化*************
**
**
const char *STA_TCPCLINET[]=
{
	"AT\\r\\n",//测试指令
	"ATE0\\r\\n",//关回显
	"AT+CWMODE=1\\r\\n",//设置STA模式
	"AT+RST\\r\\n",//模块复位
	"ATE0\\r\\n",//关回显
	"AT+CWJAP=\\"HUAWEIshui\\",\\"asdfghjkl12\\"\\r\\n",//连接wifi
	"AT+CIPMUX=0\\r\\n",//设置单连接
	"AT+CIFSR\\r\\n",//查询IP
	"AT+CIPSTART=\\"TCP\\",\\"192.168.43.204\\",8080\\r\\n",//连接服务器
	"AT+CIPMODE=1\\r\\n",//设置透传模式
	"AT+CIPSEND\\r\\n",//开始发送数据
};
返回值: 0x7f --退出透传模式失败
**      0x80 --进入配网模式正常退出
**      0    --未进入配网模式正常退出
**      其他值  --异常退出
*****************************************************/
u8 Esp8266_STA_TCPclinet_Init(u8 *server_ip,u16 server_port)
{
	char buff[100];
	/*退出透传模式*/
	u8 i=0;
    u8 stat=0;
    u32 id;
	for(i=0;i<5;i++)
	{
		USARTx_StringSend(USART3,"+++");//退出透传模式
		delay_ms(100);
		if(Esp8266_SendCmdCheckStat("AT\\r\\n","OK\\r\\n")==0)
		{
			i=0;
			break;
		}
	}
	if(i!=0)
	{
		printf("退出透传模式失败\\r\\n");
		return 0x7f;
	}
	printf("1.模块复位\\r\\n");
	if(Esp8266_SendCmdCheckStat("AT+RST\\r\\n","OK\\r\\n"))return 1;	
	delay_ms(1000);
	delay_ms(1000);
	printf("2.关回显\\r\\n");
	if(Esp8266_SendCmdCheckStat("ATE0\\r\\n","OK\\r\\n"))return 2;  
    if(ESP8266_GetWifi_Stat())//查询WIFI连接状态,未连接成功则进入配网模式
    {
        BEEP=1;
        delay_ms(100);
        BEEP=0;
        delay_ms(100);
        BEEP=1;
        delay_ms(100);
        BEEP=0;
        stat=1;//进入配网模式标志位 
        //查询IP地址  
        printf("3.设置模式AP\\r\\n");
        if(Esp8266_SendCmdCheckStat("AT+CWMODE=2\\r\\n","OK\\r\\n"))return 3;	
        printf("4.设置IP地址\\r\\n");
        if(Esp8266_SendCmdCheckStat("AT+CIPAP=\\"192.168.4.1\\",\\"192.168.4.1\\",\\"255.255.255.0\\"\\r\\n","OK"))return 4;
        printf("4.设置热点信息\\r\\n");
        id=*(vu32*)(0x1FFFF7E8);//使用STM32的ID作为WIFI名
        snprintf((char *)esp8266_info.esp8266_name,sizeof(esp8266_info.esp8266_name),"wbyq_%d",id);
        snprintf(buff,sizeof(buff),"AT+CWSAP=\\"%s\\",\\"12345678\\",1,4\\r\\n",esp8266_info.esp8266_name);
        printf("wif_name:%s\\r\\n",esp8266_info.esp8266_name);
        
        if(Esp8266_SendCmdCheckStat(buff,"OK\\r\\n"))return 5; 
        printf("5.显示端口.......\\r\\n");
        if(Esp8266_SendCmdCheckStat("AT+CIPDINFO=1\\r\\n","OK"))return 6;     
        printf("6.设置要连接的UDP\\r\\n"); 
        if(Esp8266_SendCmdCheckStat("AT+CIPSTART=\\"UDP\\",\\"192.168.4.255\\",8266,8266,0\\r\\n","OK\\r\\n"))return 7; 
        printf("7.获取微信小程序传递过来的热点信息\\r\\n");
        if(ESP8266_SoftAP_MOde())return 8;
        printf("8.设置模式STA\\r\\n");
        if(Esp8266_SendCmdCheckStat("AT+CWMODE=1\\r\\n","OK\\r\\n"))return 9;	
        printf("9.模块复位\\r\\n");
        if(Esp8266_SendCmdCheckStat("AT+RST\\r\\n","OK\\r\\n"))return 10;	
        delay_ms(1000);
        delay_ms(1000);
        printf("10.连接WIFI\\r\\n");
        snprintf((char *)buff,sizeof(buff),"AT+CWJAP=\\"%s\\",\\"%s\\"\\r\\n",esp8266_info.esp8266_name,esp8266_info.esp8266_key);//字符串拼接
        if(Esp8266_SendCmdCheckStat(buff,"WIFI GOT IP"))return 11;        
    }
	printf("11.设置单连接\\r\\n");
	if(Esp8266_SendCmdCheckStat("AT+CIPMUX=0\\r\\n","OK"))return 12;		
	snprintf(buff,sizeof(buff),"AT+CIPSTART=\\"TCP\\",\\"%s\\",%d\\r\\n",server_ip,server_port);
//	printf("buff:%s\\r\\n",buff);
	printf("12.连接服务器\\r\\n");
	if(Esp8266_SendCmdCheckStat(buff,"OK"))return 13;	
	printf("13.配置透传模式\\r\\n");
	if(Esp8266_SendCmdCheckStat("AT+CIPMODE=1\\r\\n","OK\\r\\n"))return 14;	
	printf("14.开始发送数据\\r\\n");
	if(Esp8266_SendCmdCheckStat("AT+CIPSEND\\r\\n",">"))return 15;	
	if(stat)return 0x80;//进入配网模式并且正常退出
    else return 0;//未进入配网模式,正常退出
}

/**************************获取WIFI连接状态信息***************************/
u8 ESP8266_GetWifi_Stat(void)
{
    u16 i=0;
    u16 time=0;
    u16 time2=0;
    USART3_RX_CNT=0;
    USART3_RX_FLAG=0;
    USARTx_StringSend(USART3,"AT+CWJAP?\\r\\n");//查询WIFI连接状态
    while(1)
    {
       if(USART3_RX_FLAG)
       {
           USART3_RX_BUFFER[USART3_RX_CNT]='\\0';
           printf("rx=%s\\r\\n",USART3_RX_BUFFER);
           if(strstr((char *)USART3_RX_BUFFER,"+CWJAP") || strstr((char *)USART3_RX_BUFFER,"WIFI GOT IP"))
           {
                USART3_RX_CNT=0;
                USART3_RX_FLAG=0;
                LED1=1;
                return 0;
           }
           else
           {
              USART3_RX_CNT=0;
              USART3_RX_FLAG=0;
              memset(USART3_RX_BUFFER,0,sizeof(USART3_RX_BUFFER));
           }
       }
       delay_ms(10);
       i++;
       time++;
       time2++;
       if(time>=1000)
       {
            time=0;
            USARTx_StringSend(USART3,"AT+CWJAP?\\r\\n");
       }
       if(time2>=300)
       {
            LED1=!LED1;
           time2=0;
       }
       if(i>=100*60)
       {
           LED1=1;
           break;
       }
    }
    return 1;
}

 

以上是关于基于STM32+ESP8266设计物联网产品(重点:支持微信小程序一键配网连接腾讯云平台)的主要内容,如果未能解决你的问题,请参考以下文章

基于STM32L431设计的云端绿化管理系统(ESP8266+腾讯物联网云平台)

基于STM32L431设计的云端绿化管理系统(ESP8266+阿里云物联网平台)

#yyds干货盘点# 基于STM32+ESP8266+华为云IoT设计的智能门锁

基于华为云物联网设计的浇花神器(STM32+ESP8266)

#yyds干货盘点# 基于STM32+ESP8266+华为云设计的智能家居控制系统

基于华为云设计的智能家居控制系统(STM32+ESP8266)