当前位置: 首页 > 工具软件 > LittleBlog > 使用案例 >

基于ESP32搭建物联网服务器九(用LittleFS保存设置和从LittleFS读取设置[上])

宗政博文
2023-12-01

本文会详细地介绍实现从网页端把WIFI名称和WIFI密码数据发送到后台并保存到文件系统,以及从文件系统中读取WIFI名称和WIFI密码数据数据并连接WIFI所需要用到的函数或方法。完整的服务器搭建会在下一章正式搭建,同时因为服务器的功能越来越多,所有代码都在同一个文件,可读性也会变得越来越差,所以也会同时介绍arduino IDE的多文件功能。

前文中已经详细地介绍了关于LittleFS文件系统的各种方法:

https://blog.csdn.net/m0_50114967/article/details/126961233

现在,我们就可以把一些服务器的设置,比如,连接的WIFI名称和密码保存在文件里,而不是写死在代码里,通过电脑或手机连接ESP32就可以方便地随时改变EPS32所连接的WIFI。

要实现这个功能,首先是要做到WEB服务器与后台的互动,把页面表单的字符或数据发送给后台,再把字符或数据写入指定的文件。给后台发送表单数据主要有两种方式,发送GET请求或发送POST请求(这两种请求在WEB服务器中会经常用到,可以自行先去简单了解一下)。

get请求一般是去取获取数据,参数会放在url中,所以隐私性,安全性较差,请求的数据长度也是有限制的。

post请求一般是去提交数据,没有长度的限制,请求数据是放在放在消息主体(entity-body)中,隐私性,安全性比较好。

我们现在做的是把数据提交给后台去保存数据,所以,POST请求是优先的选择,要在网页中发送POST请求,我们要做的是用html生成一个表单,里面包含WIFI名称输入框,WIFI密码输入框和确定按钮。因为表单在发送POST请求时,链接也会随之发生变化,会跳转到一个可能不存在的页面。所以在这里也同时做一下处理。

目录

生成一个表单

form元素

label元素

input元素

iframe元素

响应网页的POST请求

格式化接收到的POST数据

保存到文件

读取文件


生成一个表单

	<div>
		<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="12345678">
			<input type='submit' value='设置WIFI'>
		</form>
		<iframe src="" width="200" height="200" frameborder="0" name="myframe" style="display:NONE" ></iframe>
	</div>

form元素

<form name="wifiset" onsubmit="return validateForm()" action="\setwifi" method="post" target="myframe">

        表单元素,在这个标签内的表单会在确定按钮按下后会提交里面的表单数据

        name:        名称

        onsubmit:        在表单提交时触发  validateForm()函数

        action:             POST请求发送到"\setwifi"

        method:          定义发送的请求为"post"请求

        target:             规定在何处打开 action URL,这里为了禁止发送POST请求后跳向另一个页面,指向的是一个frame元素,这个元素会设置为隐藏。

label元素

<label for="wifiname">WIFI SSID</label>

        标签元素,为名称为name的输入框定义一个标签

input元素

<input name="wifiname" type="text" value="ESP32">

        输入框元素,定义一个输入框

        name:        名称

        type:          数据类型,text文本

        value:        默认文本

<input type='submit' value='设置WIFI'>

         提交表单按钮元素,定义一个提交表单的按钮

        type:        定义为提交表单按钮类型

        value:        默认文本,按钮上显示的字符

iframe元素

<iframe src="" width="200" height="200" frameborder="0" name="myframe" style="display:NONE" ></iframe>

        框架元素,定义这个框架的目的是为了发送POST请求后跳向本框架而不会跳向其它页面,因为设置为隐藏,所以等于跳向了一个隐藏的页面。

        主要属性

        name:        POST请求发送后,页面会跳向myframe,对应上面from元素的target属性

        style:         display:NONE是设置设元素为隐藏

这个表单的作用是,当按下提交按钮后,把表单WIFI SSID和WIFI PASSWORD输入框里的文本提交到"\setwifi",POST请求的格式如果用JSON格式来表示是这样的(发送的不一定是这样的格式,只是为了方便说明):

{"wifiname":"esp32","wifipassword":"12345678"}

JSON是一种以“‘名称/值’对”格式的无序集合,一个对象以{左括号开始,}右括号结束。每个“名称”后跟一个:冒号;“‘名称/值’ 对”之间使用,逗号分隔。

 wifiname:        名称,来自网页代码input输入框元素中的name属性

esp32:              值,来自网页代码input输入框元素中的value改属性

同理wifipassword和12345678来自另一个input输入框元属

表单提交后,就需要后台程序定义一个响应该请求的方法来接收数据。

响应网页的POST请求

ESPAsyncWebServer里响应POST请求的方法

server.on();

server.on("/setwifi"  ,HTTP_POST , get_WIFI_set_CALLback);

这行代码定义当"/setwifi"收到POST请求后,运行get_WIFI_set_CALLback回调函数。

 /**********************************************************************************
  * 函数:响应网站/setwifi目录的POST请求,收到请求后,运行get_WIFI_set_CALLback回调函数
  * 获取并格式化收到的POST数据
  *********************************************************************************/
void get_WIFI_set_CALLback(AsyncWebServerRequest *request){
  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的字符串
  }
}

回调函数定义

void get_WIFI_set_CALLback(AsyncWebServerRequest *request){

}

回调函数默认要传入一个AsyncWebServerRequest对象,该对象就是用户的请求,里面就包括发送过来的POST请求数据。我们需要在函数内读取出POST数据并格式化后保存到文件系统。

读取POST数据

if(request->hasParam("wifiname",true)){
    AsyncWebParameter* wifiname = request->getParam("wifiname",true);
    AsyncWebParameter* wifipassword = request->getParam("wifipassword",true);
    String wn  = wifiname->name().c_str();
    String wnv = wifiname->value().c_str();
    String wp  = wifipassword->name().c_str();
    String wpv = wifipassword->value().c_str();
}

现在我们要回顾一下POST发送的数据

{"wifiname":"esp32","wifipassword":"12345678"}

      对应发送过来的请求,if(request->hasParam("wifiname",true))用来确定发送的POST数据中是否包含名称为"wifiname"的集合。

AsyncWebParameter* wifiname = request->getParam("wifiname",true);
AsyncWebParameter* wifipassword = request->getParam("wifipassword",true);

          获取包含"wifiname"和"wifipassword"的集合。得到这两个集合后,我们就需要读取出这两个集合中包含的字符串,也就是它们各自的名称和值。

String wn  = wifiname->name().c_str();
String wnv = wifiname->value().c_str();
String wp  = wifipassword->name().c_str();
String wpv = wifipassword->value().c_str();

        name()结构体是获得集合的名称("wifiname"和"wifipassword"),c_str()方法是把获取的名称转换为字符串

        value()结构体是获得集合的值("esp32"和"12345678"),c_str()方法是把获取的值转换为字符串

格式化接收到的POST数据

        现在我们已经接收到了所有需要的数据,并都转换成了字符串了,但是要把WIFI名称和WIFI密码保存到文件,还需要把所有字符格式化,因为如果简单地把字符"esp32"和"12345678"保存到文件,当读取时,并不能方便地分清哪段为名称,哪段为密码。如果中间加入分隔符"esp32,12345678"但不能排除有时候会用标点符号来做为名称或密码。比较安全的方式是,把所有字符串格式化为JSON格式的字符串:

    String wifi_json_str = "{\"";
    wifi_json_str += wn;				//名称
    wifi_json_str += "\":\"";
    wifi_json_str += wnv;				//值
    wifi_json_str += "\",\"" ;
    wifi_json_str += wp;				//名称
    wifi_json_str += "\":\"" ;
    wifi_json_str += wpv;		        //值
    wifi_json_str += "\"}";

        用拼接字符的方式来生成一个JSON的字符串非常麻烦,程序的可读性也非常差。这里我们可以用ArduinoJson.h的库(可以在arduino IDE的库管理器搜索并安装(注意,本文所用的是项目作者为:Benoit BlanchonArduinoJson库))来创建一个JSON对象并转换为字符串。方法为

#include <ArduinoJson.h>

  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的字符串

        以上两种方法得到的wifi_json_str变量结果都为:

{"wifiname":"esp32","wifipassword":"12345678"}

保存到文件

      下面就需要把该字符串写到文件里了,写入操作以后可能会常常用到,这里写一个字符串写入文件的函数。

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

把JSON字符串wifi_json_str写入根目录的WIFIConfig.conf文件:

str_write("/WIFIConfig.conf",wifi_json_str);

读取文件

 读取文件字符串以后也可能会常常用到,同样写一个函数

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

读取字符串后,我们需要解析该JSON字符串,同样是用ArduinoJson库来解析字符串

/***************************************************************************************
 * 函数:解析JSON字符串,从JSON字符串名称得到该值
 * str:   JSON字符串
 * Name:  JSON集合的名称
 * return: 返回值的字符串
 ***************************************************************************************/
String analysis_json(String str, String Name){
  DynamicJsonDocument doc(str.length()*2);    //定义一个JSON对象
  deserializeJson(doc, str);                  //反序列数据
  String value = doc[Name].as<String>();      //从Name中读取对应的值
  return value;
}

得到WIFI名称和WIFI密码的字符串后,就要用这两个字符来连接WIFI:

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

至此,需要用到的所有函数都详细地介绍完成,在下一章会用以上的函数加上之前的代码来完成用LittleFS保存设置和从LittleFS读取设置。

 类似资料: