0x00 项目指北
要实现网络Server要利用好8266的两个功能SPIFFS合webServer,实现效果如下
一.SPIFFS闪存系统
1.什么叫SPIFFS? SPIFFS可以拆开成两部分来理解,一是SPI 二是FFS(文件系统)。 SPI 是 Serial Peripheral Interface 鉴于你是个爱学英语的小可爱,我告诉你第二个单词这么读 [pəˈrɪf(ə)rəl] “福瑞福若”,是外部设备的意思。所以SPI就是串行外设接口。
百度百科:
“SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如AT91RM9200。”
挺好奇的,查了一下啊,啥叫全双工,就是像柏油马路一样,双向同行就是全双工,单行道就是单工。
Github跟进
不够深入,我在github上查了一下,它是github上一位叫pellepl的工程师在2013年编写的一个文件系统
Introduction是这么写的:
Spiffs is a file system intended for SPI NOR flash devices on embedded targets. Spiffs is designed with following characteristics in mind:
- Small (embedded) targets, sparse RAM without heap
- Only big areas of data (blocks) can be erased
- An erase will reset all bits in block to ones
- Writing pulls one to zeroes
- Zeroes can only be pulled to ones by erase
- Wear leveling
意思是:
Spiffs是一个专门为基于SPI Nor flash储存架构的嵌入式设备开发的文件系统。
SPIFFS被设计用于以下几种特点:
1.用于小型嵌入式设备,不需要堆(heap)
2.只有大范围的数据块才能被擦除
3.擦除数据将把block位全部置1
4.写的操作会把1变成0
5.0只能被擦除成
6.损耗均衡
同时他也介绍了Features特性:
FEATURES
What spiffs does:
- Specifically designed for low ram usage
- Uses statically sized ram buffers, independent of number of files
- Posix-like api: open, close, read, write, seek, stat, etc
- It can run on any NOR flash, not only SPI flash - theoretically also on embedded flash of a microprocessor
- Multiple spiffs configurations can run on same target - and even on same SPI flash device
- Implements static wear leveling
- Built in file system consistency checks
- Highly configurable
What spiffs does not:
- Presently, spiffs does not support directories. It produces a flat structure. Creating a file with path tmp/myfile.txt will create a file called tmp/myfile.txt instead of a myfile.txt under directory tmp.
- It is not a realtime stack. One write operation might last much longer than another.
- Poor scalability. Spiffs is intended for small memory devices - the normal sizes for SPI flashes. Going beyond ~128Mbyte is probably a bad idea. This is a side effect of the design goal to use as little ram as possible.
- Presently, it does not detect or handle bad blocks.
- One configuration, one binary. There's no generic spiffs binary that handles all types of configurations.
意思是:
spiffs能干什么?
1.专门为小ram(小运存)使用而设计
2.使用静态大小的ram缓冲区,与文件大小无关
3.POSIX(Portable Operating System Interface,统一UNIX-like 的OS对外的接口)提供操作系统接口,如打开,关闭,读,写,查找,统计,等。
4.可以在任何NOR闪存运行,不仅仅是SPI flash理论上也可以在微处理器上运行
5.多个SPIFFS配置可以在相同目标上运行,甚至可以在相同SPI flash闪存设备上运行
6.flash寿命维护
7.内置文件系统一致性检查
8.高度可配置
SPIFFS不能做什么:
1、目前,spiffs不支持目录。它产生一个平面结构。使用路径tmp/myfile.txt创建文件将创建一个名为tmp/myfile.txt的文件,而不是在tmp目录下创建一个名为myfile.txt的文件。
2、它不是一个实时堆栈。一个写操作的持续时间可能比另一个长得多。
3、可怜的可伸缩性。Spiffs适用于小型内存设备——SPI flash的正常大小。超过~128Mbyte就不推荐了。由于设计目标是用尽可能少的ram,所以这是设计目标的一个副作用。
4、目前,它不能检测或处理坏块。
5、一个配置,一个二进制。没有通用的spiffs二进制可以处理所有类型的配置。
关于SPI Nor flash架构:由存储单元,行列控制单元,接口转换单元组成
总结:
所以,简单来说,这是一个简易的文件系统,可以面向任何闪存。最初考虑到大部分处理器的RAM资源有限,于是使用少量RAM设计了这样一个文件系统。同时,由于用的RAM比较少,导致它不能处理大于128MB的flash。
扁平化结构,不支持目录。举例,在电脑中,假设我在C盘某路径有一个文件:C:\flexIm\hello.txt,而使用SPIFFS的话,只能创建一个名字为C:\flexIm\hello的txt格式的文件。它还有一个优点,就是考虑到了flash的寿命问题,因此做了算法均匀使用flash中的每一个block。
二.WebServer
webServer就是ESP8266WebServer库及相关功能组成的
0x01 SPIFFS API
<FS.h> 文件操作很重要,对于web来说,访问资源调用SPIFFS是必须的!
基本操作(含文件)
SPIFFS.format() | 格式化SPIFFS | |
Bool | SPIFFS.begin() | 启动SPIFFS |
File | SPIFFS.open(file_name,"w") | 打开文件并执行第二个参数(这里是写入) |
dataFile.println("") | 向被打开的dataFile写入信息 | |
dataFile.close() | 文件操作结束后必须执行 | |
SPIFFS.info((FSInfo)fs_info) | 向fs_info写入info,仍需print出具体要看哪类info,api不展示了就 |
目录操作
Bool | SPIFFS.exists(file_name) | 检查文件系统中有无此文件,有则返回True |
num | dataFile.size() | 返回当前打开的文件大小 |
Dir | SPIFFS.openDIR(folder_name) | 创建目录 |
Bool | dir.next() | 检查是否有下一个文件 dir指向下一个文件,函数返回True |
Bool | SPIFFS.remove(file_name) | 删除文件 |
0x02 通过Arduino IDE向SPIFFS系统上传文件
请确保已经完成了以下准备工作qwq:
- 将NodeMCU开发板与电脑通过USB数据线连接好
- NodeMCU开发板驱动已经安装完毕
- 设置Arduino IDE以使其支持NodeMCU开发板
一.下载Arduino-ESP8266闪存文件插件程序
通过这个链接进行下载 https://github.com/esp8266/arduino-esp8266fs-plugin/releases
二.确定项目文件夹位置并粘贴文件
通过 ArduinoIDE ---> 文件 ---> 首选项 --->项目文件夹位置 在项目位置建立新文件夹"tools"然后把下载好的文件解压,直接丢过去 然后重新启动ArduinoIDE 如何确定安装成功了呢? 在工具 ---> 若有ESP8266 Sketch Data upload 则安装完毕了 上传 然后在项目文件夹下创建data文件夹,将文件直接拖进去就好
0x03 Connection API
<ESP8266WiFiMulti.h>
AP Mode:
AP麻,就是 Access Point 由自身成为WiFi,使别的设备可以连接
WiFi.softAP(ssid,password) | 启动ap模式,ssid是wifi名字 |
WiFi.softAPIP() | 获得NodeMCU的内网地址 |
Station Mode:
即无线终端模式,纯粹的wifi收发
WiFi.status() | 获取当前wifi连接状态 |
WL_CONNECTED |
Auto Multi Connection:
机智的你会发现,一次只能链接一个wifi有点不太符合我们的日常使用逻辑,所以我们希望能够在一个库中保存许多wifi,然后自动选取信号最强的或者可以建立有效链接的进行connect,于是我们用到了ESP8266WiFiMutil.h这个库。 所以,这个库只适用于AP mode哦!
对象 | ESP8266WiFiMulti wifiMulti | 建立ESP8266WiFiMulti对象 然后可以使用对象的众多方法来实现功能 |
wifiMulti.addAP("ssid"."passwd") | 给链接库里面添加wifi | |
bool | wifiMulti.run() | 搜索当前链接库信号最强的wifi 连接成功则返回WL_CONNECTED |
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前注意哦!!!为了实现在连接前持续搜索的效果,我们需要持续循环判断wifi是否链接,比如可以这样子写
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU Serial.print('.'); // 将会连接信号最强的那一个WiFi信号。 }
others:
<ESP8266WiFi.h>
WiFi.mode(mode) | 调整ESP8266工作模式为无线终端模式(如WIFI_STA) |
WiFi.begin(ssid,password) | 开始链接wifi |
WiFi.SSID() | AP mode下查看当前连接的wifi名称 |
WiFi.localIP() | 查看NodeMCU当前的IP地址 |
0x04 Server API
<ESP8266WebServer>
对象 | ESP8266WebServer esp8266_server(port) | 建立webServer对象 参数为打开的端口 根据协议标准,一般打开80端口 |
esp8266_server.begin(); | 让ESP8266-NodeMCU来启动网络服务功能 | |
esp8266_server.on("pos",func); | 指挥NodeMCU来如何处理浏览器的http请求,pos是访问位置('/'代表首页),func是访问对应页面执行函数 | |
esp8266_server.onNotFound(func) | 若无此页面则调用func处理404访问 | |
esp8266_server.handleClient(); | 检查有没有设备通过网络向NodeMCU发送请求 (因此我们需要把它放在loop函数中,从而确保它能经常被调用。假如我们的loop函数里有类似delay一类的函数延迟程序运行,那么这时候就一定要注意了,如果handleClient函数长时间得不到调用,NodeMCU的网络服务会变得很不稳定。因此在使用NodeMCU执行网络服务功能的时候,一定要确保handleClient函数经常得以调用。我在这里反复强调这一点是因为这一点非常关键,请务必注意!) | |
esp8266_server.send(index,"type","text") | 生成并且发送http响应信息,param1:状态码,param2:说明http响应体信息类型,parma3:具体内容(最好放在具体esp8266_Server.on对应执行函数里面,来实现“响应头”的效果) | |
esp8266_server.sendHeader(headerName, headerValue, first); | 用于向响应头信息中添加自定义键值对。 parma1:自定义响应头的名称,可以使用字符串类型 parma2:自定义响应头的值,可使用字符串类型 parma3:设置该响应头是否需要放在第一行,缺省则为false(参数为bool) | |
esp8266_server.send(index) | 向浏览器发送状态码 |
content-type要注意哦!!! 1.实现持续处理Client的访问响应,需要将handleClient放在loop函数中运行 而begin和on等配置型函数,在setup中使用。 2.!!!!.send函数很重要,尤其是TYPE!!!因为不同type代表了告诉client怎么处理网络字节流
常见格式如下:
- text/html : HTML格式
- text/plain :纯文本格式
- text/xml : XML格式
- image/gif :gif图片格式
- image/jpeg :jpg图片格式
- image/png:png图片格式
application开头的媒体类型:
- application/xhtml+xml :XHTML格式
- application/xml: XML数据格式
- application/atom+xml :Atom XML聚合格式
- application/json: JSON数据格式
- application/pdf:pdf格式
- application/msword : Word文档格式
- application/octet-stream : 二进制流数据(如常见的文件下载)
- application/x-www-form-urlencoded : <form encType="">中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
更多API如下:
0x05 Client API
其实这个项目是不需要用到Client API哒!刚好学到这里了,就大概见一下吧! 如果不想了解可以直接跳过 你可能会疑惑,我8266是Server为啥要Client API呢,因为我8266不光可以作为Server,还可以作为Client,类似浏览器干的事情,主动去访问一个别的Server,并解析他的响应。
ESP8266HTTPClient库
特征:简单易用,但是不灵活,封装好了底层内容,不够自由
HTTPClient httpClient | 要声明HTTPClient类型对象才能操作哦!!! | |
httpClient.begin(URL,[port]); | begin函数来配置请求地址,param2为默认80 | |
int | httpClient.GET() | 通过get函数启动链接并发送http请求 返回状态码 |
int | httpClient.POST(payload,[size]) | 此函数用于ESP8266使用HTTP协议通过Server向服务器发送请求 parma1:通过post请求发送的数据信息,可使用字符串 parma2:通过post请求发送字节数(size_t) |
Sting | httpClient.getString() | getString函数获得服务器响应体内容 |
httpClient.end() | 关闭esp8266与服务器的链接 此函数来清除ESP8266的接收缓存以便设备再次接收服务器发来的响应信息。 |
void httpClientRequest(){}注意!!!! 发送HTTP请求并将服务器响应通过串口输出最好用一个函数来写,且函数必须写在loop里面,然后再setup函数启用时调用!!!
WiFiClient库
bool | client.connect(host, httpport) | 与Server连接,host是网址httpport是端口号 Success则True | |
bool | client.connected() | 检查当前服务器的连接情况 Connected则True | |
bool | client.available() | 检查Server是否发来信息 有则True | 注意,最好写两层abaliable函数,服务器响应有延迟 |
String | client.readStringUntil('\n') | 将服务器响应信息逐行分解为字符串 | |
client.print(request) | 向Server发送封装好的请求,request是String串 |
同上一个库发送HTTP请求并将服务器响应通过串口输出最好用一个函数来写,且函数必须写在loop里面,然后再setup函数启用时调用!!!void httpClientRequest(){}
readStringUntil函数的实现利用了Stream 数据流概念,下面我们一起探索一下Stream
Stream & Stream API
直白来讲,就是一列有先后顺序的数据
bool | Serial.avilable() | 判断开发板是否收到数据 | 一般在loop函数下写相关功能的实现!! |
Serial.println() | 将Stream串口数据输出在串口监视器中 |
还有很多api,暂不赘述啦!
0x06 最后几步
相信到这里,你已经利用插件在SPIFFS上传好了html文件,最后我们要做的就是将SPIFFS文件读取与webServer.on函数练习起来,我们都知道,每一个url都是一个资源,当我们在web标签调用图片之类的资源,也相当于发了一次request和response,所以我们需要给每一个url设置.on函数......确实有点麻烦。那么上代码吧。
#include <ESP8266WiFi.h> // 本程序使用ESP8266WiFi库
#include <ESP8266WebServer.h> // ESP8266WebServer库
#include <FS.h>
ESP8266WebServer Server(80);
const char *ssid = "LiuJiahuan Test Web Server"; // 这里定义将要建立的WiFi名称。此处以""为示例
// 您可以将自己想要建立的WiFi名称填写入此处的双引号中
const char *password = "12345678"; // 这里定义将要建立的WiFi密码。此处以12345678为示例
// 您可以将自己想要使用的WiFi密码放入引号内
// 如果建立的WiFi不要密码,则在双引号内不要填入任何信息
void setup() {
Serial.begin(9600); // 启动串口通讯
if(SPIFFS.begin()){ // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
WiFi.softAP(ssid, password); // 此语句是重点。WiFi.softAP用于启动NodeMCU的AP模式。
// 括号中有两个参数,ssid是WiFi名。password是WiFi密码。
// 这两个参数具体内容在setup函数之前的位置进行定义。
Serial.print("Access Point: "); // 通过串口监视器输出信息
Serial.println(ssid); // 告知用户NodeMCU所建立的WiFi名
Serial.print("IP address: "); // 以及NodeMCU的IP地址
Serial.println(WiFi.softAPIP()); // 通过调用WiFi.softAPIP()可以得到NodeMCU的IP地址
Server.begin();
Server.on("/",handleRoot);
Server.on("/test.jpeg", handlePhoto);
}
void loop() {
Server.handleClient();
}
void handleRoot() { //处理网站根目录“/”的访问请求
File dataFile = SPIFFS.open("/index.html", "r");
String rp;
for(int i = 0; i < dataFile.size(); i++){
rp += (char)dataFile.read();
}
Server.send(200, "text/html", rp);
dataFile.close();
}
void handlePhoto(){
File dataFile = SPIFFS.open("/test.jpeg", "r");
String pt;
for(int i = 0; i < dataFile.size(); i++){
pt += (char)dataFile.read();
}
Server.send(200, "image/jpeg", pt);
dataFile.close();
}
我给SPIFFS放了个index.html和test.jpeg 非常easy的小demo!!!给你们宿舍也做个小网站吧hhhhhh!!!做内网穿透以后更好玩了