基于ESP32搭建物联网服务器十二(使用MQTT协议与ESP32互动)

这篇具有很好参考价值的文章主要介绍了基于ESP32搭建物联网服务器十二(使用MQTT协议与ESP32互动)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在之前的文章中:基于ESP32搭建物联网服务器十一(用WEB页面控制引脚(GPIO)功能)_esp32webserver 控制io_你的幻境的博客-CSDN博客

已经简单地介绍了MQTT协议,对比于其它网络协议,MQTT协议在物联网的开发中,它的特点使它适用于大多数受限的环境。例如网络代价昂贵,带宽低、不可靠,在嵌入设备中运行,处理器和内存资源有限。

MQTT介绍

下面深入了解一下MQTT协议的特点和优势,下图是一个MQTT的概念图:

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

如图所示,MQTT基于一个MQTT服务器(MQTT Broker),所有设备或客户端都可以是一个发布设备同时也可以是一个订阅设备,所以,只要你的设备可以连接在同一个MQTT服务器,都可以给其它设备进行发布任务或接收其它设备发布的数据,实现一对多的消息发布,完美地解决设备或应用程序的耦合。

MQTT消息服务质量

MQTT发布的消息有三种服务质量:

QoS0:最多一次,可能导至发布的消息丢失

QoS1:至少一次,可能导至发布的消息多次发布

QoS2:确保只有一次,保证消息到达对方并且只到达一次

QoS等级越高,系统消耗也越高,在应用时可以根据需求选择合适的QoS等级。

MQTT遗嘱标记

服务器与客户端通信时,当遇到异常或客户端心跳超时的情况,MQTT服务器会替客户端发布一个遗嘱消息。当然如果服务器收到来自客户端的断开的消息(如自主选择断开连接),则不会触发遗嘱消息的发送。 在重要的应用或设备上使用该标记,可以在发生网络故障或网络波动,设备在保持连接周期内未能通讯,连接被服务端关闭,设备意外掉电,设备尝试进行不被允许的操作而被服务端关闭连接,例如订阅自身权限以外的主题等异常时通知第三方。

心跳机制

客户端可以设置一个心跳时间间隔,客户端会周期性地给服务发送一个心跳请求(PINGREQ),服务器收到请求后会回复并响应心跳请求(PINGRESP)。如果客户端在发送心跳请求(PINGREQ)后,没有收到服务端的心跳响应(PINGRESP),那么客户端就会认为自己与服务端的连接已经被断开了。

更多MQTT的介绍可以到首页 | MQTT中文网了解

在ESP32上使用MQTT协议

在ESP32上使用MQTT协议,可以用的库比较多,这里选择pubsubclient ,该库可以找到的资料与文档说明都比较详细,唯一的不足是该库只能发布QoS0的消息和订阅Qos0和Qos1的消息。

该库的项目地址:GitHub - knolleary/pubsubclient: A client library for the Arduino Ethernet Shield that provides support for MQTT.

API文档(英文)地址:Arduino Client for MQTT

主要函数和方法

PubSubClient (server, port, [callback], client, [stream])

创建一个完全配置的客户端实例。

参数:

server IPAddress, uint8_t[] 或 const char[] -服务器地址

port int - 要连接的端口

callback function* (可选) -一个指向消息回调函数的指针,消息到达客户端时调用该函数

client -使用的网络客户端,例如WiFiClient

stream Stream (可选) - 将接收到的消息写入的流

boolean connect (clientID, [username, password], [willTopic, willQoS, willRetain, willMessage], [cleanSession])

客户端连接到服务器

参数:

clientID const char[] - 连接到服务器时使用的客户端ID

Credentials - (可选)

username const char[] - 要使用的用户名。如果为NULL,则不使用用户名或密码

password const char[] - 要使用的密码。如果为NULL,表示不使用密码

Will - (可选)

willTopic const char[] - 遗嘱消息要使用的主题

willQoS int: 0,1 or 2 - 遗嘱消息将要使用的服务质量

willRetain boolean - 遗嘱是否应以保留标记公布

willMessage const char[] - 遗嘱消息的有效负载

cleanSession boolean (可选) - 是否连接clean-session

返回值:

false - 连接失败

true - 连接成功

boolean connected ()

检查客户端是否连接到服务器端。

返回

false - 未连接

true - 已连接

boolean loop ()

需要循环调用该函数,以便客户端用来处理传入消息并维护与服务器的连接。

返回值:

false - 客户端不在连接状态

true - 客户端处于连接状态

boolean publish (topic, payload, [length], [retained])

将消息发布到指定的主题。

参数:

topic const char[] - 要发布的主题

payload const char[], byte[] - 要发布的消息

length unsigned int (可选) - 有效载荷的长度。如果有效负载是byte[],则为必选项。

retained boolean (可选) - 是否保留消息

false - 不保留

true - 保留

返回:

false - 发布失败,要么连接丢失,要么消息长度超长

true - 发布成功

boolean subscribe (topic, [qos])

定阅发送到主题的消息

参数:

topic const char[] - 订阅的主题

qos int: 只能选择 0 或 1 (可选) - 订阅消息的服务质量

返回:

false - 订阅失败,要么连接丢失,要么消息超长

true - 订阅成功

ESP32连接MQTT服务器

pubsubclient这个库的头文件为PubSubClient.h,同时,该库需要使用一个网络客户端,这里直接使用WiFi库来创建一个网络客户端实例,所以同时要引入WiFi.h这个头文件。

同时,我们创建几个变量,来保存连接服务器所需要的数据,如服务器地址和连接端口。


#include <WiFi.h>
#include <PubSubClient.h>

const char*   mqttServer = "broker.emqx.io";        //MQTT服务器地址,一个公共的免费MQTT服务器
const int     mqttPort = 1883;                      //连接的端口

/***********************************************************************************
 * 创建一个网络客户端,用来创建MQTT客户端实例
 ***********************************************************************************/
WiFiClient espClient;

/***********************************************************************************
* 创建一个完全配置的客户端实例。
* 参数
*  mqttServer - 服务器地址
*  mqttPort - 要连接的端口
*  callback - 一个指向消息回调函数的指针,当消息到达此客户端创建的订阅时调用该函数
*  espClient - 使用的网络客户端,例如WiFiClient
***********************************************************************************/
PubSubClient mqtt_client(mqttServer,mqttPort,callback,espClient);

客户端已经设置好,后面我们需要利用connect ()来连接服务器,正常情况下,连接服务器前,我们先确定ESP32是否已连接网络,因为这里我们是用WIFI来连接网络的,所以这里写一个函数来方便调用


/***********************************************************************************
 * 函数:连接mqtt服务器
 * 返回:
 *  返回连接状态
***********************************************************************************/
boolean connect_MQTT(){
      if(WiFi.status() == WL_CONNECTED){                             //WIFI是否连接
        if(mqtt_client.connect("ESP32Client")){                      //连接服务器,客户端ID可以自定义
          Serial.println("连接MQTT服务器成功");                        //输出连接状态
          mqtt_client.publish("ESP32", "ESP32已连接MQTT服务器");      //发布一个消息到ESP32主题
          mqtt_client.subscribe("ESP32");                           //订阅一个ESP32主题
        }
      }
      return mqtt_client.connected();                              //返回连接状态
}

当设备连接上服务器后,大多数情况下,我们会把设备的一些数据发布,比如ESP32的引脚状态,从传感器获取到的数据等。所以,我们还需要创建一个定时发布消息的函数。


/***********************************************************************************
 * 函数:定时发布消息到指定主题
***********************************************************************************/
long lastSendTime = 0;                                               //最后发送时间,该变量为全局变量

void regularPublish(){
    long publishNow = millis();                                       //得到当前时间
    if (publishNow - lastSendTime > 5000){                            //当前时间-最后发送时间>5000时
      lastSendTime = publishNow;                                      //把最后发送时间设为当前时间
      mqtt_client.publish("ESP32", "来自esp32定时发布的消息");            //发布一个消息到ESP32主题
    }
}

除了发布消息,我们也需要ESP32响应其它客户端发布的消息,来控制ESP32或响应对方的指令,如从手机端或电脑发送一个指令来控制ESP32的引脚状态,从而实现多远端来实现远程控制。该函数设定为当ESP32收到一个第一个字符为1的消息后,设置ESP32的2号引脚设为高电平,而收到的字符为0时2号引脚设为低电平。


/***********************************************************************************
 * 函数:回调函数,收到消息时的动作,参数是固定的
 * 参数:
 * char* topic:主题
 * byte* payload:消息内容
 * unsigned int length:消息内容长度
***********************************************************************************/
void callback(char* topic, byte* payload, unsigned int length) {
  if(payload[0]=='1'){
    digitalWrite(2,1);     //2号引脚设为高电平
    Serial.println("2号引脚设为高");
  }else if(payload[0]=='0'){
    Serial.println("2号引脚设为低");
    digitalWrite(2,0);     //2号引脚设为低电平
  }else{
    return;
  }
}

该回调函数对应之前创建客户端实例里第三个参数。


PubSubClient mqtt_client(mqttServer,mqttPort,callback,espClient);

对于在线设备,我们需要考虑设备或网络异常的情况,让设备出现意外掉线等情况下自动可以重新连接服务器,这里用一个函数来调用之前连接MQTT服务器的函数,该函数在设备在线的情况下运行loop()函数,来接收已订阅的主题所收到的消息,如果设备出现异常,会每5秒调用之前连接MQTT服务器的函数来重新连接服务器。


/***********************************************************************************
 * 函数:mqtt循环
 * 如果连接断开,每5秒进行一次重连
***********************************************************************************/
long lastAttemptTime = 0;               //该变量为全局变量,用来保存最后连接的时间

void mqtt_loop(){
  if(!mqtt_client.connected()){         //如果未连接MQTT服务器
    long now = millis();                //得到当前时间
    if (now - lastAttemptTime > 5000){  //当前时间-最后连接时间>5000时
      lastAttemptTime = now;            //把最后连接时间设为当前时间
      mqtt_client.setKeepAlive(60);     //心跳时间设为60秒
      if(connect_MQTT()){               //如果连接服务器成功
        lastAttemptTime = 0;            //最后连接时间设为0
      }
    }
  }else{
    regularPublish();                   //定时发布消息
    mqtt_client.loop();                 //MQTT循环
  }
}

完整实现代码

ESP32_web_server_12_mqtt.ino


#include "ESPAsyncWebServer.h"
AsyncWebServer server(80);            //创建一个服务器对象,WEB服务器端口:80
void setup() {
  Serial.begin(9600);                 //串口波特率初始化
  LittleFS_begin();                   //LittleFS文件系统初始化
  connect_NET();                      //网络初始化
  web_server();                       //WEB服务器初始化
  GPIO_begin();                       //引脚初始化
  connect_MQTT();                     //连接MQTT服务器                   
}

void loop() {
  DNS_request_loop();                 //DNS服务请求处理
  mqtt_loop();                        //mqtt服务循环
}

mqtt_server.ino


#include <WiFi.h>
#include <PubSubClient.h>
/***********************************************************************************
 * 库:https://github.com/knolleary/pubsubclient
 * 该库只能发布QoS0消息,可以订阅QoS0或QoS1消息
 * 最大消息,包括头,默认为256个字节,可以通过PubSubClient::setBufferSize(size)重新配置
 * 心跳间隔默认为15秒,可以通过PubSubClient::setKeepAlive(keepAlive)重新配置
 * 默认mqtt版本为3.1.1。
 ***********************************************************************************/
const char*  mqttServer = "broker.emqx.io";         //MQTT服务器地址,一个公共的免费MQTT服务器
const int     mqttPort = 1883;                      //连接的端口
const char*   mqttUser = "yourMQTTuser";            //公共的MQTT服务器一般都不要求帐号登陆,这里可以不设置
const char*   mqttPassword = "yourMQTTpassword";    //公共的MQTT服务器一般都不要求帐号登陆,这里可以不设置
long          lastAttemptTime = 0;                  //最后更新时间
long          lastSendTime = 0;                     //最后发送时间

/***********************************************************************************
 * 创建一个网络客户端,用来创建MQTT客户端实例
 ***********************************************************************************/
WiFiClient espClient;

/***********************************************************************************
* 创建一个完全配置的客户端实例。
* 参数
*  mqttServer - 服务器地址
*  mqttPort - 要连接的端口
*  callback - 一个指向消息回调函数的指针,当消息到达此客户端创建的订阅时调用该函数
*  espClient - 使用的网络客户端,例如WiFiClient
***********************************************************************************/
PubSubClient mqtt_client(mqttServer,mqttPort,callback,espClient);

/***********************************************************************************
 * 函数:定时发布消息到指定主题
***********************************************************************************/
void regularPublish(){
    long publishNow = millis();                                       //得到当前时间
    if (publishNow - lastSendTime > 5000){                            //当前时间-最后发送时间>5000时
      lastSendTime = publishNow;                                      //把最后发送时间设为当前时间
      mqtt_client.publish("ESP32", "来自esp32定时发布的消息");            //发布一个消息到ESP32主题
    }
}

/***********************************************************************************
 * 函数:回调函数,收到消息时的动作,参数是固定的
 * 参数:
 * char* topic:主题
 * byte* payload:消息内容
 * unsigned int length:消息内容长度
***********************************************************************************/
void callback(char* topic, byte* payload, unsigned int length) {
  /*
  String str;
  payload[length]='\0';                           //缓冲区最后加入结束字符
  str = String((char *)payload);                  //转为字符串
  Serial.print("Message arrived [");
  Serial.print(topic);                            // 打印主题信息
  Serial.print("] ");
  Serial.print(str);
  Serial.println();
  Serial.print("收到的指令:");
  Serial.println(payload[0]);
  */
  if(payload[0]=='1'){
    digitalWrite(2,1);                          //2号引脚设为高电平
    Serial.println("2号引脚设为高");
  }else if(payload[0]=='0'){
    Serial.println("2号引脚设为低");
    digitalWrite(2,0);                          //2号引脚设为低电平
  }else{
    return;
  }
}

/***********************************************************************************
 * 函数:连接mqtt服务器
 * 返回:
 *  返回连接状态
***********************************************************************************/
boolean connect_MQTT(){
      if(WiFi.status() == WL_CONNECTED){                           //WIFI是否连接
        if(mqtt_client.connect("mqttx_72b06547")){                    //连接服务器,如果成功
          Serial.println("连接MQTT服务器成功");                      //输出连接状态
          mqtt_client.publish("ESP32", "ESP32已连接MQTT服务器");    //发布一个消息到ESP32主题
          mqtt_client.subscribe("ESP32");                         //订阅一个ESP32主题
        }
      }
      return mqtt_client.connected();                             //返回连接状态
}


/***********************************************************************************
 * 函数:mqtt循环
 * 如果连接断开,每5秒进行一次重连
***********************************************************************************/
void mqtt_loop(){
  if(!mqtt_client.connected()){         //如果未连接MQTT服务器
    long now = millis();                //得到当前时间
    if (now - lastAttemptTime > 5000){  //当前时间-最后尝试时间>5000时
      lastAttemptTime = now;            //把最后尝试时间设为当前时间
      mqtt_client.setKeepAlive(60);     //心跳时间设为60秒
      if(connect_MQTT()){               //如果连接服务器成功
        lastAttemptTime = 0;            //最后尝试时间设为0
      }
    }
  }else{
    regularPublish();                   //定时发布消息
    mqtt_client.loop();                 //MQTT循环
  }
}

web_server.ino


#include "ESPAsyncWebServer.h"
#include <ArduinoJson.h>
#include <LittleFS.h> 

/***********************************************************************************
 * 函数:引脚初始化
***********************************************************************************/
void GPIO_begin(){
  pinMode(2, OUTPUT);                           //引脚2设置为输出模式
}

/***********************************************************************************
 * 函数:响应按键回调函数
***********************************************************************************/
void GPIO_button(AsyncWebServerRequest *request){
    int pin_state = digitalRead(2);
    String state;
    digitalWrite(2,(!pin_state));               //每次按下循环地改变引脚状态
    if(digitalRead(2)){
      state = "开";
    }else{
      state = "关";
    }
    request->send(200,"text/plain",state);      //把状态发送回页面
    Serial.print("引脚状态改变为:");
    Serial.println(pin_state);
}


/**************************************************************************************
 * 函数:字符串写入文件,文件如果存在,将被清零并新建,文件不存在,将新建该文件
 * path:    文件的绝对路径
 * str:     要写入的字符串
 *************************************************************************************/
void str_write(String path, String str){
    Serial.println("写入文件");
    File wf = LittleFS.open(path,"w");                           //以写入模式打开文件
    if(!wf){                                                     //如果无法打开文件
      Serial.println("打开文件写入时错误");  //显示错误信息
      return;                                                    //无法打开文件直接返回
    }
    wf.print(str);                                               //字符串写入文件
    wf.close();                                                  //关闭文件
    File rf = LittleFS.open(path,"r");                           //以读取模式打开文件
    Serial.print("FILE:");Serial.println(rf.readString());       //读取文件
    rf.close();                                                  //关闭文件 
}


// /**********************************************************************************
//  * 函数:把收到的POST数据格式化为JSON格式的字符串
//  *********************************************************************************/
//String format_json(AsyncWebParameter* post_data , int len){
//  String json_name = post_data->name().c_str();               //得到名称
//  String json_value = post_data->value.c_str();               //得到值
//  StaticJsonDocument<len> json_obj;                           //创建一个JSON对象
//  json_obj[json_name] = json_value;                           //写入一个名称和值
//  String json_str;
//  serializeJson(wifi_json, wifi_json_str);                    //生成JOSN的字符串
//  return json_str;                                            //返回JOSN字符串
//}

 /**********************************************************************************
  * 函数:响应网站/setwifi目录的POST请求,收到请求后,运行get_WIFI_set_CALLback回调函数
  * 获取并格式化收到的POST数据
  *********************************************************************************/
void get_WIFI_set_CALLback(AsyncWebServerRequest *request){
   Serial.println("收到设置WIFI按钮");
  if(request->hasParam("wifiname",true)){
    AsyncWebParameter* wifiname = request->getParam("wifiname",true);                        //获取POST数据
    AsyncWebParameter* wifipassword = request->getParam("wifipassword",true);            //获取POST数据
    String wn  = wifiname->name().c_str();
    String wnv = wifiname->value().c_str();
    String wp  = wifipassword->name().c_str();
    String wpv = wifipassword->value().c_str();
    //把SSID和password写成一个JSON格式
    StaticJsonDocument<200> wifi_json;                                            //创建一个JSON对象,wifi_json
    wifi_json[wn] = wnv;                                                          //写入一个建和值
    wifi_json[wp] = wpv;                                                          //写入一个键和值
    String wifi_json_str;                                                         //定义一个字符串变量
    serializeJson(wifi_json, wifi_json_str);                                      //生成JOSN的字符串
    str_write("/WIFIConfig.conf",wifi_json_str);                                  //字符串写入
  }
}


/**********************************************************************************
 * 函数:从文件path中读取字符串
 * path:      文件的绝对路径
 * return:    返回读取的字符串
 *********************************************************************************/
String str_read(String path){
    Serial.println("读取文件");
    File rf = LittleFS.open(path,"r");                 //以读取模式打开文件
    if(!rf){                                           //如果无法打开文件
      Serial.println("打开文件读取时错误");               //显示错误信息
      return "";                                       //无法打开文件直接返回
    }
    String str = rf.readString();                      //读取字符串
    rf.close();                                        //关闭文件 
    return str;
}


/***************************************************************************************
 * 函数:解析JSON字符串,从JSON字符串名称得到该值
 * str:   JSON字符串
 * Name:  JSON集合的名称
 * return: 返回值的字符串
 ***************************************************************************************/
String analysis_json(String str, String Name){
  DynamicJsonDocument doc(str.length()*2);    //定义一个JSON对象
  DeserializationError error = deserializeJson(doc, str);                   //尝试反序列数据,如果失败生成error错误码,成功时将字符串str生成一个JSON对象doc
  if(error){                                                                //如果反序列数据失败
    Serial.print("JSON解析失败:");                                            //输出错误信息
    Serial.println(error.f_str());                                          //输出错误信息
    return "";                                                              //返回
  }
  if(!doc.containsKey(Name)){                                               //键是否存在
    Serial.println("不存在键");
    return "";                                                              //返回
  }
    return doc[Name].as<String>();                                          //返回读取到的字符串
}


/***********************************************************************************
 * 函数:/WIFIConfig.conf文件中读取设置数据并连接WIFI
***********************************************************************************/
void wifi_connect(){
  Serial.println("在conf文件中读取数据并连接WIFI");
  String str = str_read("/WIFIConfig.conf");                                            //读取文件内容
  String wifiname = analysis_json(str,"wifiname");                                      //解析WIFI名称
  String wifipassword = analysis_json(str,"wifipassword");                              //解析WIFI名称
  connect_WIFI(wifiname, wifipassword);                                                 //连接WIFI
}


/***********************************************************************************
 * web服务器初始化
***********************************************************************************/
void web_server(){
  Serial.println("初始化WEB服务器");
  server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");            //响应网站根目录的GET请求,返回文件index.html
  server.on("/setwifi"  ,HTTP_POST , get_WIFI_set_CALLback);                //响应设置WIFI按钮的请求
  server.on("/GPIO2", HTTP_GET, GPIO_button);                               //响应改变引脚按钮的请求
  server.begin();                                                                               //初始化
}


/********************************************************************************
 * LittleFS文件系统初始化
 *********************************************************************************/
void LittleFS_begin(){
  Serial.println();
  Serial.println("初始化文件系统");
  if(!LittleFS.begin(true)){
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }
}

wifi_connect.ino


/*WIFI连接*/
#include <WiFi.h>
#include <DNSServer.h>

DNSServer dnsserver;

/***********************************************************************************
 * 函数:连接WIFI
 * ssid:        WIFI名称
 * password:    WIFI密码
 * return:      连接成功返回true
***********************************************************************************/
void connect_WIFI(String ssid, String password){
  WiFi.begin(ssid.c_str(), password.c_str());               //连接WIFI
  Serial.print("连接WIFI");
  //循环,10秒后连接不上跳出循环
  int i = 0;
  while(WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(500);
    i++;
    if(i>20){
      Serial.println();
      Serial.println("WIFI连接失败");
      return;
    }
  }
  Serial.println();
  IPAddress local_IP = WiFi.localIP();
  Serial.print("WIFI连接成功,本地IP地址:");                   //连接成功提示
  Serial.println(local_IP); 
}


/***********************************************************************************
 * 设置AP和STA共存模式,设置DNS服务器
***********************************************************************************/
void connect_NET(){
  const byte DNS_PORT = 53;                     //DNS端口
  const String url = "ESPAP.com";               //域名
  IPAddress APIp(10,0,10,1);                    //AP IP
  IPAddress APGateway(10,0,10,1);               //AP网关
  IPAddress APSubnetMask(255,255,255,0);        //AP子网掩码
  const char* APSsid = "esp32_AP";              //AP SSID
  const char* APPassword = "12345678";          //AP wifi密码
  wifi_connect();                               //连接WIFI
  WiFi.mode(WIFI_AP_STA);                             //打开AP和STA共存模式
  WiFi.softAPConfig(APIp, APGateway, APSubnetMask);   //设置AP的IP地址,网关和子网掩码
  WiFi.softAP(APSsid, APPassword, 6);                 //设置AP模式的登陆名和密码
  dnsserver.start(DNS_PORT, url, APIp);               //设置DNS的端口、网址、和IP
  Serial.print("AP模式IP地址为:");
  Serial.println(WiFi.softAPIP());
}


/***********************************************************************************
 * DNS处理请求的循环
***********************************************************************************/
void DNS_request_loop(){
    dnsserver.processNextRequest();
}

以下文件需要用插件上传到SPIFFS文件系统

data\index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="mystyle.css">
<title>EPS32教程</title>
</head>
<body>
    <div id="pal">
        <form name="wifiset" onsubmit="return validateForm()" action="\setwifi" method="post" target="myframe">
            <label for="wifiname">WIFI SSID</label>
            <input name="wifiname" type="text" value="ESP32">
            <label for="wifipassward">WIFI PASSWARD</label>
            <input name="wifipassword" type="text" value="ESP32">
            <input type='submit' value='设置WIFI'>
        </form>
        <iframe src="" width="200" height="200" frameborder="0" name="myframe" style="display:NONE" ></iframe>
        <input type="button" value="控制GPIO" onclick="get_request('sw','/GPIO2')">
        <p id="sw">关</p>
    </div>
</body>

<script>
/******************************
表单验证,WIFI名称输入框为空时提示
******************************/
function validateForm(){
var wifiname=document.forms["wifiset"]["wifiname"].value;    //得到name输入框的文字
//var wifipassword=document.forms["wifiset"]["password"].value;
if (wifiname==null || wifiname==""){                    //如果输入框为空
  alert("WIFI SSID必需输入");                            //显示提示
  return false;
  }
}

/******************************
函数:向path发送GET请求,并得到后台send()方法发送过来的值,把得到的值写到ElementId所指向的元素
注意,发送GET请求后台的响应是必要的。
ElementId:    要得到数据的元素ID
path:        请求的路径
******************************/
function get_request(ElementId , path){
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {                      //如果请求就绪和状态已完成
          document.getElementById(ElementId).innerHTML = xmlhttp.responseText;       //将获取到的内容<send(200,"text/plain", String());>写到id为ElementId的元素中
        }
    },
    xmlhttp.open("GET", path, true);                                                 //发送GET请求
    xmlhttp.send();
}


/******************************
创建一个新元素
ParentElement ,         父元素
Element ,                要创建的元素
Attribute ,             新元素的属性
AttributeValue ,         新元素的属性值
text                    新元素的文本节点
******************************/
function show_wifi_state(ParentElement , Element ,Attribute , AttributeValue , text){
    var att = document.createAttribute(Attribute);                //创建一个新的属性
    att.value = AttributeValue;                                    //属性值
    var para=document.createElement(Element);                    //创建了一个新的元素
    para.setAttributeNode(att);                                    //把属性加入元素
    var node=document.createTextNode(text);                        //创建文本节点
    para.appendChild(node);                                        //向元素追加文本节点
    var element=document.getElementById(ParentElement);            //查找到一个已有的父元素
    element.appendChild(para);                                    //向这个已存在的父元素追加新元素
}
</script>
</html>

data\mystyle.css


div{
    background-color:red;
}

data\WIFIConfig.conf

该文件需要改动以符合你的WIFI设备,或以AP用手机登陆来生成该文件(生成需要重启设备),详细的操作方法可以找之前的文章:https://blog.csdn.net/m0_50114967/category_12014624.html


{"wifiname":"你的WIFI名称","wifipassword":"你的WIFI密码"}

测试

以上代码上传到ESP32后,在WIFI配置正确的情况下,会自动连接到MQTT服务器(broker.emqx.io),上传前注意打开串口监视器,查看是否正确连接MQTT服务器。

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

连接成功后,这里我们需要别一个客户端来接收或给ESP32发送指令。

用如果电脑端,我们可以用MQTTX来把电脑做为一个客户端,下载地址:免费下载、试用 EMQ 产品

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

下载和安装这个工具,运行后,我们新建一个连接

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享
esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

如上图设置好,点击右上角的连接。

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

连接成功后,我们给该连接添加一个订阅

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

设置好后点确定来订阅,这个时候,我们应该会每五秒收到一条从ESP32发布的消息

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

我们在ESP32的2号引脚上连接一个LED,来测试在电脑上发送一个指令来控制该LED的亮灭,同时如果控制成功,在串口监视器上也可以看到提示。

我们用MQTTX发送一个数字"1"或"0",在代码设计中,当ESP32收到时会分别把2号引脚设置为高电平或低电平

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

发布的内容可以为"1"或"0",发布其它内容ESP32也可以接收到,但不一定能控制引脚的状态

点击发送按钮后,我们观察2号脚上的LED或观察串口监视器,

esp32 mqtt,ESP32打造物联网,物联网,c语言,服务器,网络协议,经验分享

如上图的状态,就说明我们已经可以通过电脑远端控制ESP32了。

如果有空,大家也可以在手机上下载可以使用MQTT的APP来测试控制ESP32。

以上就是ESP32利用MQTT协议与其它客户端互动的简单实现,结合之前的文章基于ESP32搭建物联网服务器十一(用WEB页面控制引脚(GPIO)功能)_esp32webserver 控制io_你的幻境的博客-CSDN博客内容,我们现在可以用WEB页面和MQTT协议来与ESP32进行互动。

在之后的文章里,将会在安全性、可控范围上继续完备这两种互动方式。文章来源地址https://www.toymoban.com/news/detail-781408.html

到了这里,关于基于ESP32搭建物联网服务器十二(使用MQTT协议与ESP32互动)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32--ESP8266物联网WIFI模块(贝壳物联)--温湿度数据上传服务器显示

    本文适用于STM32F103C8T6等MCU,其他MCU可以移植,完整资源见文末链接 一、简介 随着移动物联网的发展,各场景下对于物联控制、数据上传、远程控制的诉求也越来越多,基于此乐鑫科技推出了便宜好用性价比极高的wifi物联模块——ESP8266,话不多少我们先来看看这个神奇的模

    2024年02月08日
    浏览(42)
  • ESP32系列四:搭建http的webserver的服务器

    最近在使用ESP32搭建web服务器测试,发现esp32搭建这类开发环境还是比较方便的。具体的http协议这里就不再赘述,我们主要说一下如何使用ESP32提供的API来搭建我们的http web。 一、web服务器搭建过程 1、配置web服务器 在ESP-IDF中,Web服务器使用httpd组件实现。我们需要先创建ht

    2024年03月21日
    浏览(42)
  • 基于ESP32的简易web服务器

    本文介绍一下如何使用ESP32快速方便的搭建一个简易的web服务器。 使用ESP32或ESP8266搭建web服务器的方式有很多,但是大多数都的web页面代码都是内嵌在程序中的,这样如果要修改web页面就十分的不方便。今天介绍一种方法将web页面的代码以文件的形式上传到存储器中,然后在

    2024年02月09日
    浏览(43)
  • 基于 ESP32 通过 SMTP 服务器 来发送电子邮件信息

    电子邮件在全球范围内被用作数字通信的重要组成部分。电子邮件主要用于官方通信目的,因为它最方便、成本效益高、保存记录、覆盖全球且环保。电子邮件是一种非常快捷的通信方式,只是您需要稳定的互联网连接。 在这个项目中,我们将使用ESP32开发板发送电子邮件(

    2024年02月07日
    浏览(65)
  • 基于esp32-cam模块的监控,并自动拍照保存置服务器

    推荐esp教程网站:esp教程网站 ,纯英文,需魔法上网。         Arduino官网:Software | Arduino         建议下载1.8.x版本,不推荐下载2.x版本,1.8.x版本可以使用插件,但是2.x版本有代码补充。         点击后,会弹出两个网页,都选择 JUST DOWNLOAD 。 ​         安装

    2024年04月16日
    浏览(39)
  • 基于 ESP32 创建 HTTP Server 服务器,支持载入文件到服务器,并对载入文件进行删除管理

    软件编程指南参见:HTTP 服务器 任意一款 ESP32 系列开发板 2.4GHz 路由器热点 准备各种格式的文件 可基于 esp-idf/examples/protocols/http_server /file_serving 例程进行测试 只需要设置 ESP32 连接的 2.4GHz 的 WiFi 热点 即可 menuconfig — Example Connection Configuration — WiFi SSID — WiFi Password 下载固件

    2024年02月16日
    浏览(45)
  • 使用esp32+micropython+microdot搭建web(http+websocket)服务器(超详细)第一部分

    microdot文档速查 什么是Microdot?Microdot是一个可以在micropython中搭建物联网web服务器的框架 micropyton文档api速查 Quick reference for the ESP32 演示视频链接 视频中我们简单的实现了 使用esp32搭建web服务器 实现get请求 上传网页到服务器 手机打开网址访问该网页 服务器处理请求,实现开

    2024年02月08日
    浏览(44)
  • 从零开始用Nodejs搭建一个MQTT服务器,并且用stm32通过esp8266进行消息订阅和发布

    最近在做一个物联网项目,需要用到服务器进行数据的存储和数据的请求和发送,之前我用过onenet平台上的http服务,虽然能通过get和post请求进行数据的提交和发送,但是平台上的数据发生改变却不能主动推送给esp8266,与我此次的项目不符合,所以pass。然后我了解了下mqtt协

    2024年02月04日
    浏览(43)
  • 关于LWIP用法之HTTPD:基于STM32F4搭建web服务器

    一,STM32CUBEMX配置(使用的是6.4.0版本) 前提是在配置好LWIP的情况下(能ping通你的开发板),使能HTTPD功能。 然后是使能LWIP_HTTPD_CGI, 使能:LWIP_HTTPD_SUPPORT_POST(), 使能 :HTTPD_USE_CUSTOM_FSDATA。 会发现fs.c这个文件的#include HTTPD_FSDATA_FILE,这一句编译报错,解决办法:1) 在KEIL中lwipop

    2023年04月08日
    浏览(27)
  • ESP32连接云服务器【WebSocket】

    🔮🔮🔮🔮🔮相关文章🔮🔮🔮🔮🔮 ESP32连接MQ Sensor实现气味反应 🔗 https://blog.csdn.net/ws15168689087/article/details/131365573 ESP32+MQTT+MySQL实现发布订阅【气味数据收集】 🔗 https://blog.csdn.net/ws15168689087/article/details/131627595 个人云服务器搭建MQTT服务器 🔗 https://blog.csdn.net/ws15168689

    2024年02月16日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包