基于ESP32搭建物联网服务器九(用LittleFS保存设置和从LittleFS读取设置[上])
本文会详细地介绍实现从网页端把WIFI名称和WIFI密码数据发送到后台并保存到文件系统,以及从文件系统中读取WIFI名称和WIFI密码数据数据并连接WIFI所需要用到的函数或方法。完整的服务器搭建会在下一章正式搭建,同时因为服务器的功能越来越多,所有代码都在同一个文件,可读性也会变得越来越差,所以也会同时介绍arduino IDE的多文件功能
本文会详细地介绍实现从网页端把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请求时,链接也会随之发生变化,会跳转到一个可能不存在的页面。所以在这里也同时做一下处理。
目录
生成一个表单
<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 Blanchon的ArduinoJson库))来创建一个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读取设置。
更多推荐
所有评论(0)