原文:零基础入门学用物联网 – 基础知识篇 – 目录 – 太极创客 (taichi-maker.com)http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/
目录
ESP8266库函数
GPIO引脚图
本教程中“GPIO编号”这一名称指代的是ESP8266芯片的引脚编号。我们会以“GPIO + 数字”这一格式来指代,如GPIO2, GPIO16…
GPIO口设置
NodeMCU开发板的引脚名指的是开发板上印刷的文字。通常这些引脚名都是一个字符接一个数字的组合,如:D0、D1、A0… 我们会用“开发板引脚”或直接使用“引脚”来表示它们。
以上语句通过digitalWrite函数将引脚4设置为高电平。那么您知道这个数字4到底是指GPIO4还是指开发板D4引脚呢?答案是GPIO4而不是D4。
如果要对D2引脚设置为高电平,您可以使用以下语句。
可用引脚
ESP8266芯片有17个GPIO引脚(GPIO0~GPIO16)。这些引脚中的GPIO6~GPIO 11被用于连接开发板的闪存(Flash Memory)。如果在实验电路中使用GPIO6~GPIO11,NodeMCU开发板将无法正常工作。因此建议您不要使用GPIO6~GPIO 11。
电压电流限制
板子电压是3.3v 模拟引脚是0-1v;
NodeMCU开发板引脚的输入输出电压限制是3.3 V。如果向引脚施加3.6V以上的电压就有可能对芯片电路造成损坏。同时请注意,这些引脚的最大输出电流是12mA。
由于NodeMCU开发板的引脚允许电压和电流都是低于Arduino开发板的引脚,所以如您想要将NodeMCU与Arduino引脚相互连接,请特别注意这两个开发板的引脚电压和电流的区别。如果操作不当可能会损坏NodeMCU开发板。
特殊引脚情况说明
GPIO2引脚 在NodeMCU开发板启动时是不能连接低电平的。
GPIO15引脚在开发板运行中一直保持低电平状态。因此请不要使用GPIO15引脚来读取开关状态或进行I²C通讯。
GPIO0引脚在开发板运行中需要一直保持高电平状态。否则ESP8266将进入程序上传工作模式也就无法正常工作了。您无需对GPIO0引脚进行额外操作,因为NodeMCU的内置电路可以确保GPIO0引脚在工作时连接高电平而在上传程序时连接低电平。
上拉电阻/下拉电阻
GPIO 0-15引脚都配有内置上拉电阻。这一点与Arduino十分类似。GPIO16 引脚配有内置下拉电阻。
模拟输入
ESP8266 只有一个模拟输入引脚(该引脚通过模拟-数字转换将引脚上的模拟电压数值转化为数字量)。此引脚可以读取的模拟电压值为 0 – 1.0V。请注意:ESP8266 芯片模拟输入引脚连接在1.0V以上电压可能损坏ESP8266芯片。
以上所描述的是针对ESP8266芯片的引脚。而对于NodeMCU开发板引脚,情况就不同了。
NodeMCU开发板配有降压电路。您可以用NodeMCU开发板的模拟输入引脚读取0-3.3V的模拟电压信号。
通讯
串行端口
ESP8266有2个硬件串行端口(UART)。
串行端口0(UART0)使用GPIO1和GPIO3引脚。其中GPIO1引脚是TX0,GPIO3是RX0。
串行端口1(UART1)使用GPIO2和GPIO8引脚。其中GPIO2引脚是TX1,GPIO8是RX1。请注意,由于GPIO8被用于连接闪存芯片,串行端口1只能使用GPIO2来向外发送串行数据。
I²C
ESP8266只有软件模拟的I²C端口,没有硬件I²C端口。也就是说我们可以使用任意的两个GPIO引脚通过软件模拟来实现I²C通讯。ESP8266的数据表(datasheet)中,GPIO2标注为SDA,GPIO14标注为SCL。
SPI
ESP8266的SPI端口情况如下:
GPIO14 — CLK
GPIO12 — MISO
GPIO13 — MOSI
GPIO 15 — CS(SS)
互联网基础知识
互联网知识基础-TCP/IP协议簇
互联网知识基础-链路层
互联网知识基础-网络层
网络层与IP协议
尽管设备可以通过链路层联网,但是光有链路层还无法实现设备之间的数据通讯。因为网络设备没有明确的标识。网络设备无从知晓要向谁传输数据,也无法确定从何处获取数据。
这很像我们在网上购物时需要把收货地址提供给商家。有了这个地址,商家才知道向哪里发货。假如收货后我们不满意,那么商家也要提供退货地址。这样才能确保货物被返还给商家。在这里,商家和我们都有一个明确的地址,以确保货物运输的正常运行。
在网络中传输数据的过程很像刚才这个例子。每一台网络设备都有独立的地址,数据在这些独立的地址间实现传输。但是网络设备的地址是什么呢?很多人都听过这个名字,它叫IP地址。
网络层主要作用是通过IP协议为联网设备提供IP地址。
有了IP地址还不够,因为要确保网络中所有设备IP地址不重复,还需要DHCP (Dynamic Host Configuration Protocol) 服务器来实现这一功能。
当网络中所有设备都有了独立的IP地址后,设备之间就可以收发数据
用255这个数字标出IP地址的哪一部分是子网地址。而用0这个数字来表示IP地址的哪一部分是设备部分。
互联网知识基础-传输层
网络设备通讯时,数据丢失和数据受损的情况经常出现。传输层的
TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)协议可以用来解决这一问题。通常我们会选择这两种协议中的一种来保证数据传输的准确性。具体选择哪一种协议要看我们使用的是何种网络应用。因为不同的网络应用对于数据的传输要求是不同的。
举例来说,对于网络游戏而言,我们对数据的传输速率要求很高。因为玩家在发出了一个游戏控制指令后,这个指令需要以最快的速度传送给游戏服务器。如果传输速度跟不上,游戏体验将会大打折扣。相反,有一些网络应用对数据传输速度要求较低,但是对数据传输的准确性要求是极高的。比如电子邮件应用。当我们发出电子邮件以后,通常我们不太介意这封邮件的传输速度。邮件可以是1分钟后送达,也可以是10分钟后送达。这没有什么影响。但是邮件的信息内容是绝对要保证准确的。
这就引出了TCP和UDP这两种协议的应用范围。
TCP协议可以更好的保证数据传输的准确性,但是传输速度比UDP协议而言要慢一些。TCP协议的特点是可以保证所有数据都能被接收端接收,数据的传输顺序也不会被打乱,而且如有数据损坏则重发受损数据。基于以上功能特点,TCP通常用于电子邮件及文件上传等。
UDP协议并不能保证所有数据都被接收端所接受。一旦出现数据受损的情况,UDP协议将会抛弃受损的数据。这些数据一旦被抛弃将会永久性的消失,发送端不会因为数据受损而重新发送。因此UDP协议远不如TCP协议可靠。但是既然是这样,为何还有人会选择UDP协议呢?这是因为UDP比TCP速度快。因此UDP协议通常用于网络游戏以及语音聊天或视频聊天应用。
互联网知识基础-应用层
HTTP协议
应用层中有很多种协议,最常见是HTTP协议。它常被用来传输网页数据。我们这篇教程也将着重介绍HTTP协议。
HTTP协议由请求和响应构成。也就是说,HTTP的工作模式很像是一问一答。
HTTP请求
举例来说,当您在浏览器输入www.taichi-maker.com这一网址并按下回车,这时候浏览器会把这一操作转换成一个HTTP请求。
这个HTTP请求主要分为两大部分。一部分是请求头(Request Header)一部分是请求体(Request Body)。对于我们学习物联网知识来说,请求头是我们重点要关注的内容。
在以上的HTTP请求中:
“GET” 是一个读取请求。也就是请求网站服务器把网页数据发送过来。
“/” 的作用就是要告诉网站服务器,我这个读取请求的内容是网站根目录下的内容。换句话说,就是请求服务器把网站首页的网页数据发过来。
“HTTP/1.1” 是指请求所采用的HTTP协议版本是1.1。
“Host: www.taichi-maker.com”表示请求的域名是 www.taichi-maker.com 也就是太极创客网站的域名。
以上是HTTP协议的 GET 请求中最关键的内容。在 HTTP 协议中,GET只是诸多请求方法中的一种。以下是HTTP协议中的其它请求方法:
HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
关于请求方法,我们这里主要介绍的只有GET。其它的请求方法已经超越了我们这个教程的范围,就不在这里继续深入讲下去了。感兴趣的话,您可以通过互联网找到这方面的教程资源。
HTTP响应
接下来我们再看一看浏览器发送以上HTTP请求后,接收到的服务器HTTP响应。HTTP响应内容也是分为两个部分,一部分是响应头(Response Header)一部分是响应体(Response Body)。其中响应体部分是可选项,也就是说有些HTTP响应只有响应头,而响应体是空的。
我们先来给大家介绍响应头部分。
由于响应头信息量比较大,我们还是选出主要内容给大家讲解。如下所示:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
“HTTP/1.1”这个信息我们刚刚在HTTP请求部分中介绍过。它的含义就是此HTTP响应所采用的协议版本是1.1。
“200”这个代码可能有些朋友会感觉陌生。但是如果我说起“404”这个数字可能大家会感到更熟悉吧?无论是200也好还是404也好,这些都是HTTP响应状态码。它的作用是以代码的形式表达服务器在接到请求后的状态。“200”代表服务器成功找到了请求的网页资源(这一点大家在后面的OK中也已经体现出来了)。 “404”代表服务器无法找到请求的网页资源。:
以下是常见的服务器状态码:
100~199:成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程。
200~299:成功接收请求并已完成整个处理过程。常用200
300~399:完成请求,客户需进一步细化请求。
400~499:客户端的请求有错误,常用404和403(403的含义是权限不够,服务器拒绝访问。)
500~599:服务器端出现错误,常用500
“Content-Type” 指示响应体的内容是什么类型。这里的响应体内容类型是“text/htm”,即网页HTML代码。通过这一行响应头信息,我们的浏览器将会知道,在这一个响应中的响应体部分都是HTML网页代码。于是浏览器将会做好准备,将网页代码翻译成我们人类容易读懂的格式并且呈现在浏览器中。
我们再举一个例子,假设某一个响应头中“Content-Type” 类型是”image/jpeg”。这就意味着该响应体中的信息是一个jpeg格式的图片,那么浏览器也就会按照jpeg的解码方式将图片呈现在我们的面前。
在以上示例中,我们使用互联网浏览器来讲解HTTP的请求和响应。当我们使用NodeMCU来开发物联网项目时,发出HTTP请求的就不再是浏览器而是NodeMCU开发板了。而读取这些响应请求的也将是NodeMCU开发板。那么,究竟如何让NodeMCU发出HTTP请求,而NodeMCU又是如何解读HTTP响应呢?这些都依赖于我们为NodeMCU开发的控制程序。这些内容会在后续的教程里给大家详细介绍。
DNS(Domain Name System/域名系统)
在之前的教程中,我曾经给大家讲过网络中的所有设备都具有独立的IP地址。这一点对于网站服务器来说也不例外。当我们使用浏览器访问某一个网站时,实际上我们是通过浏览器向网站服务器发送HTTP请求。然而网站服务器的IP地址很难记忆,比如太极创客网站的域名由两个单词组成,taichi是太极,maker是创客。这很好记,但是要想记住太极创客服务器的IP就没那么容易了。因为那是4个毫无规律的数字。
为了解决IP地址不好记这一问题,DNS被派上了用场。
我们可以把DNS看作是一个巨型电话本。电话本中的联系人一栏就是网站的域名,而电话本中的电话号码一栏则是这些网站的IP地址。有了DNS我们就可以使用简单易记的域名来访问网站了。
还是用太极创客网站来举例吧,每当我们在浏览器中输入域名www.taichi-maker.com并按下回车后,这时浏览器首先会向DNS服务器发送请求,请求的内容大致如下:“亲爱的DNS服务器,我那个明明可以靠脸吃饭却偏偏要学物联网的主人想访问一个域名是www.taichi-maker.com的网站。麻烦您把这个域名的网站服务器IP地址告诉我好吗?” DNS服务器在接收到这一请求后,会做出以下应答:“亲爱的浏览器,您要的网站服务器ip地址是 12 . 34 . 56 . 78。” 浏览器在接收到这一IP地址后,就开始向这个IP地址所对应的网站服务器正式发出HTTP GET请求了。
ESP8266开发
开发环境
NodeMCU开发板的接入点模式
#include <ESP8266WiFi.h>
const char *ssid = "taichi-maker"; // 设置SSID为"taichi-maker"
const char *password = "12345678"; // 设置密码为"12345678"(可选)
void setup() {
Serial.begin(115200);
// 配置 NodeMCU 作为 Access Point
WiFi.softAP(ssid, password);
// 获取并打印 IP 地址
IPAddress ip = WiFi.softAPIP();
Serial.print("Access Point IP Address: ");
Serial.println(ip);
}
void loop() {
// 你可以在这里添加其他代码来处理与连接设备的通信
}
NodeMCU开发板的无线终端模式
#include <ESP8266WiFi.h>
// 设置连接到的WiFi网络的SSID和密码
const char* ssid = "your_SSID"; // 替换为你的无线路由器SSID
const char* password = "your_PASSWORD"; // 替换为你的WiFi密码
void setup() {
Serial.begin(115200); // 启动串口通讯
WiFi.begin(ssid, password); // 开始连接到指定的WiFi网络
Serial.print("Connecting to ");
Serial.print(ssid);
Serial.println("...");
// 等待连接
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// 打印连接成功的信息
Serial.println("\nConnected successfully!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP()); // 打印分配给 NodeMCU 的 IP 地址
}
void loop() {
// 你可以在这里添加其他代码,例如与网络服务器的通信等
}
ESP8266-NodeMCU网络服务器
搭建一个局域网 通过访问返回的IP访问
/**********************************************************************
项目名称/Project : 零基础入门学用物联网
程序名称/Program name : 3_2_1_First_Web_Server
团队/Team : 太极创客团队 / Taichi-Maker (www.taichi-maker.com)
作者/Author : CYNO朔
日期/Date(YYYYMMDD) : 20191107
程序目的/Purpose : 使用NodeMCU建立基本服务器。用户可通过浏览器使用8266的IP地址
访问8266所建立的基本网页(Hello from ESP8266)
-----------------------------------------------------------------------
修订历史/Revision History
日期/Date 作者/Author 参考号/Ref 修订说明/Revision Description
***********************************************************************/
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库
ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'
ESP8266WebServer esp8266_server(80);// 建立ESP8266WebServer对象,对象名称为esp8266_server
// 括号中的数字是网路服务器响应http请求的端口号
// 网络服务器标准http端口号为80,因此这里使用80为端口号
void setup(void){
Serial.begin(115200); // 启动串口通讯
//通过addAp函数存储 WiFi名称 WiFi密码
wifiMulti.addAP("TEST1", "A12345678"); // 这三条语句通过调用函数addAP来记录3个不同的WiFi网络信息。
wifiMulti.addAP("taichi-maker2", "87654321"); // 这3个WiFi网络名称分别是taichi-maker, taichi-maker2, taichi-maker3。
wifiMulti.addAP("taichi-maker3", "13572468"); // 这3个网络的密码分别是123456789,87654321,13572468。
// 此处WiFi信息只是示例,请在使用时将需要连接的WiFi信息填入相应位置。
// 另外这里只存储了3个WiFi信息,您可以存储更多的WiFi信息在此处。
int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
Serial.print(i++); Serial.print(' '); // 将会连接信号最强的那一个WiFi信号。
} // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
// 此处while循环判断是否跳出循环的条件。
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n'); // WiFi连接成功后
Serial.print("Connected to "); // NodeMCU将通过串口监视器输出。
Serial.println(WiFi.SSID()); // 连接的WiFI名称
Serial.print("IP address:\t"); // 以及
Serial.println(WiFi.localIP()); // NodeMCU的IP地址
//--------"启动网络服务功能"程序部分开始-------- // 此部分为程序为本示例程序重点1
esp8266_server.begin(); // 详细讲解请参见太极创客网站《零基础入门学用物联网》
esp8266_server.on("/", handleRoot); // 第3章-第2节 ESP8266-NodeMCU网络服务器-1
esp8266_server.onNotFound(handleNotFound);
//--------"启动网络服务功能"程序部分结束--------
Serial.println("HTTP esp8266_server started");// 告知用户ESP8266网络服务功能已经启动
}
/* 以下函数语句为本示例程序重点3
详细讲解请参见太极创客网站《零基础入门学用物联网》
第3章-第2节 3_2_1_First_Web_Server 的说明讲解*/
void loop(void){
esp8266_server.handleClient(); // 处理http服务器访问
}
/* 以下两个函数为本示例程序重点2
详细讲解请参见太极创客网站《零基础入门学用物联网》
第3章-第2节 3_2_1_First_Web_Server 的说明讲解*/
void handleRoot() { //处理网站根目录“/”的访问请求
esp8266_server.send(200, "text/plain", "Hello from ESP8266"); // NodeMCU将调用此函数。
}
// 设置处理404情况的函数'handleNotFound'
void handleNotFound(){ // 当浏览器请求的网络资源无法在服务器找到时,
esp8266_server.send(404, "text/plain", "404: Not found"); // NodeMCU将调用此函数。
}
LED灯的控制
esp8266_server.on("/", HTTP_GET, handleRoot); // 设置服务器根目录即'/'的函数'handleRoot'
“/” 的作用就是要告诉网站服务器,我这个读取请求的内容是网站根目录下的内容。换句话说,就是请求服务器把网站首页的网页数据发过来。
“GET” 是一个读取请求。也就是请求网站服务器把网页数据发送过来。
HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。// 在根路径生成一个包含按钮的网页 void handleRoot() { esp8266_server.send(200, "text/html", "<form action=\"/LED\" method=\"POST\">" "<input type=\"submit\" value=\"Toggle LED\">" "</form>" ); }
“200”代表服务器成功找到了请求的网页资源(这一点大家在后面的OK中也已经体现出来了)
- 该函数用于向客户端发送 HTTP 响应。
- 第一个参数
200
是 HTTP 状态码,表示请求成功。- 第二个参数
"text/html"
指定响应的内容类型为 HTML。- 第三个参数是 HTML 内容,生成一个包含按钮的网页。
"<form action=\"/LED\" method=\"POST\">" ... "</form>"
:
- 这是一个 HTML 表单标签。
action="/LED"
: 指定表单提交的目标 URL,当用户点击提交按钮时,浏览器会向 NodeMCU 服务器的/LED
端点发送请求。method="POST"
: 指定表单提交方法为 POST。
"<input type=\"submit\" value=\"Toggle LED\">" ...
:
- 这是表单中的一个输入元素,创建一个提交按钮。
type="submit"
: 指定输入类型为提交按钮。value="Toggle LED"
: 按钮上显示的文本为 "Toggle LED"。
esp8266_server.on("/LED", HTTP_POST, handleLED); // 设置处理LED控制请求的函数'handleLED'POST发送
//处理LED控制请求的函数'handleLED'
void handleLED() {
digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); // 改变LED的点亮或者熄灭状态
esp8266_server.sendHeader("Location","/"); // 跳转回页面根目录
esp8266_server.send(303); // 发送Http相应代码303 跳转
}
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库
ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是 'wifiMulti'
ESP8266WebServer esp8266_server(80); // 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)
void setup(void){
Serial.begin(115200); // 启动串口通讯
pinMode(LED_BUILTIN, OUTPUT); // 设置内置LED引脚为输出模式以便控制LED
wifiMulti.addAP("TEST1", "A12345678"); // 将需要连接的一系列WiFi ID和密码输入这里
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); // ESP8266-NodeMCU再启动后会扫描当前网络
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); // 环境查找是否有这里列出的WiFi ID。如果有
Serial.println("Connecting ..."); // 则尝试使用此处存储的密码进行连接。
int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 通过wifiMulti.run(),NodeMCU将会在当前环境中搜索addAP函数所存储的WiFi。
delay(1000); // 如果搜到多个存储的WiFi那么NodeMCU将会连接信号最强的那一个WiFi信号。
Serial.print(i++); Serial.print(' '); // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。
}
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n');
Serial.print("Connected to ");
Serial.println(WiFi.SSID()); // 通过串口监视器输出连接的WiFi名称
Serial.print("IP address:\t");
Serial.println(WiFi.localIP()); // 通过串口监视器输出ESP8266-NodeMCU的IP
esp8266_server.begin(); // 启动网站服务
esp8266_server.on("/", HTTP_GET, handleRoot); // 设置服务器根目录即'/'的函数'handleRoot'
esp8266_server.on("/LED", HTTP_POST, handleLED); // 设置处理LED控制请求的函数'handleLED'
esp8266_server.onNotFound(handleNotFound); // 设置处理404情况的函数'handleNotFound'
Serial.println("HTTP esp8266_server started"); // 告知用户ESP8266网络服务功能已经启动
}
void loop(void){
esp8266_server.handleClient(); // 检查http服务器访问
}
/*设置服务器根目录即'/'的函数'handleRoot'
该函数的作用是每当有客户端访问NodeMCU服务器根目录时,
NodeMCU都会向访问设备发送 HTTP 状态 200 (Ok) 这是send函数的第一个参数。
同时NodeMCU还会向浏览器发送HTML代码,以下示例中send函数中第三个参数,
也就是双引号中的内容就是NodeMCU发送的HTML代码。该代码可在网页中产生LED控制按钮。
当用户按下按钮时,浏览器将会向NodeMCU的/LED页面发送HTTP请求,请求方式为POST。
NodeMCU接收到此请求后将会执行handleLED函数内容*/
void handleRoot() {
esp8266_server.send(200, "text/html", "<form action=\"/LED\" method=\"POST\"><input type=\"submit\" value=\"Toggle LED\"></form>");
}
//处理LED控制请求的函数'handleLED'
void handleLED() {
digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); // 改变LED的点亮或者熄灭状态
esp8266_server.sendHeader("Location","/"); // 跳转回页面根目录
esp8266_server.send(303); // 发送Http相应代码303 跳转
}
// 设置处理404情况的函数'handleNotFound'
void handleNotFound(){
esp8266_server.send(404, "text/plain", "404: Not found"); // 发送 HTTP 状态 404 (未找到页面) 并向浏览器发送文字 "404: Not found"
}
显示高低电平
#include <ESP8266WiFi.h> // 本程序使用 ESP8266WiFi库
#include <ESP8266WiFiMulti.h> // ESP8266WiFiMulti库
#include <ESP8266WebServer.h> // ESP8266WebServer库
#define buttonPin D3 // 按钮引脚D3
ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,对象名称是'wifiMulti'
ESP8266WebServer esp8266_server(80);// 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)
bool pinState; // 存储引脚状态用变量
void setup(){
Serial.begin(9600); // 启动串口通讯
pinMode(buttonPin, INPUT_PULLUP); // 将按键引脚设置为输入上拉模式
wifiMulti.addAP("TEST1", "A12345678"); // 将需要连接的一系列WiFi ID和密码输入这里
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2"); // ESP8266-NodeMCU再启动后会扫描当前网络
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3"); // 环境查找是否有这里列出的WiFi ID。如果有
Serial.println("Connecting ..."); // 则尝试使用此处存储的密码进行连接。
int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 此处的wifiMulti.run()是重点。通过wifiMulti.run(),NodeMCU将会在当前
delay(1000); // 环境中搜索addAP函数所存储的WiFi。如果搜到多个存储的WiFi那么NodeMCU
Serial.print(i++); Serial.print(' '); // 将会连接信号最强的那一个WiFi信号。
} // 一旦连接WiFI成功,wifiMulti.run()将会返回“WL_CONNECTED”。这也是
// 此处while循环判断是否跳出循环的条件。
// WiFi连接成功后将通过串口监视器输出连接成功信息
Serial.println('\n'); // WiFi连接成功后
Serial.print("Connected to "); // NodeMCU将通过串口监视器输出。
Serial.println(WiFi.SSID()); // 连接的WiFI名称
Serial.print("IP address:\t"); // 以及
Serial.println(WiFi.localIP()); // NodeMCU的IP地址
esp8266_server.begin(); // 启动网站服务
esp8266_server.on("/", handleRoot); // 设置服务器根目录即'/'的函数'handleRoot'
esp8266_server.onNotFound(handleNotFound);// 设置处理404情况的函数'handleNotFound'
Serial.println("HTTP esp8266_server started");// 告知用户ESP8266网络服务功能已经启动
}
void loop(){
esp8266_server.handleClient(); // 处理http服务器访问
pinState = digitalRead(buttonPin); // 获取引脚状态
}
/* 以下函数处理网站首页的访问请求。此函数为本示例程序重点1
详细讲解请参见太极创客网站《零基础入门学用物联网》
第3章-第2节“通过网络服务将开发板引脚状态显示在网页中”的说明讲解。*/
void handleRoot() {
String displayPinState; // 存储按键状态的字符串变量
if(pinState == HIGH){ // 当按键引脚D3为高电平
displayPinState = "Button State: HIGH"; // 字符串赋值高电平信息
} else { // 当按键引脚D3为低电平
displayPinState = "Button State: LOW"; // 字符串赋值低电平信息
}
esp8266_server.send(200, "text/plain", displayPinState);
// 向浏览器发送按键状态信息
}
// 设置处理404情况的函数'handleNotFound'
void handleNotFound(){ // 当浏览器请求的网络资源无法在服务器找到时,
esp8266_server.send(404, "text/plain", "404: Not found"); // NodeMCU将调用此函数。
}
闪存文件系统(SPIFFS)
闪存文件的基本操作
1. 通过程序向闪存文件系统写入信息
每一个ESP8266都配有一个闪存,这个闪存很像是一个小硬盘,我们上传的文件就被存放在这个闪存里。这个闪存的全称是Serial Peripheral Interface Flash File System(SPIFFS)。
除了可以存放上传的程序以外,我们还可以将网页文件或者系统配置文件存放在ESP8266的闪存中。在这节课里,我们将学习如何利用程序对闪存文件系统(SPIFFS)进行文件读取和修改。
#include <FS.h> // 引入文件系统库
String file_name = "/taichi-maker/notes.txt"; // 被读取的文件位置和名称
void setup() {
Serial.begin(9600); // 初始化串口通信
Serial.println("");
Serial.println("SPIFFS format start");
SPIFFS.format(); // 格式化SPIFFS
Serial.println("SPIFFS format finish");
if(SPIFFS.begin()){ // 启动SPIFFS
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
// 建立File对象用于向SPIFFS中的file对象(即/taichi-maker/notes.txt)写入信息
File dataFile = SPIFFS.open(file_name, "w");
dataFile.println("Hello IOT World."); // 向dataFile写入字符串信息
dataFile.close(); // 完成文件写入后关闭文件
Serial.println("Finished Writing data to SPIFFS");
}
void loop() {
// 空的loop函数,程序在setup函数中完成任务后不再执行其他操作
}
结果
在使用SPIFFS存储文件以前,我们必须使用#include "FS.h"
,如以上程序第18行所示。
我们建立了一个字符串变量。该变量用于存储文件位置和文件名。其中/
代表根目录。/taichi-maker/
代表根目录下的taichi-maker
目录。notes.txt
代表着文件名称。
SPIFFS.format();
是对闪存文件系统进行格式化。这很想是我们对u盘进行格式化的操作。您无需每次使用闪存文件系统都对它进行格式化操作。这里仅仅是为了演示如何使用SPIFFS.format();
。
SPIFFS.begin()
用于启动闪存文件系统。在每次使用闪存文件系统以前都需要执行这一操作。如果闪存文件系统成功启动,该函数的返回值为布尔型,如果成功启动闪存文件形同,则返回真。否则将返回假。
File dataFile = SPIFFS.open(file_name, "w");
这条语句中,open函数可用于对SPIFFS进行操作。该函数共有两个参数。第一个参数file_name
是被操作的文件名称,本示例中该文件为/taichi-maker/notes.txt
第二个参数"w"
代表此操作为向SPIFFS写入文件信息。请注意:如果文件系统没有/taichi-maker/notes.txt文件,此操作将会在文件系统中建立该文件。如果文件系统有该文件,则程序将会重新建立该文件,即原有文件信息将会被覆盖。
dataFile.println("Hello IOT World.");
用于向dataFile文件写入信息。信息内容为“Hello IOT World.”。
程序第38行dataFile.close();
用于关闭dataFile文件。结束文件操作后,应执行此操作。
2.通过程序从闪存文件系统读取信息
函数说明:
SPIFFS.open(file_name, "r");
以上SPIFFS函数有两个参数:
第一个参数是被操作的文件名称,本示例中该文件为/notes.txt
第二个参数"r" 代表读取文件信息。(如需了解如何写入信息,请参阅示例程序esp8266-flash-write)
***********************************************************************/
#include <FS.h>
String file_name = "/taichi-maker/notes.txt"; //被读取的文件位置和名称
void setup() {
Serial.begin(9600);
Serial.println("");
if(SPIFFS.begin()){ // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
//确认闪存中是否有file_name文件
if (SPIFFS.exists(file_name)){
Serial.print(file_name);
Serial.println(" FOUND.");
} else {
Serial.print(file_name);
Serial.print(" NOT FOUND.");
}
//建立File对象用于从SPIFFS中读取文件
File dataFile = SPIFFS.open(file_name, "r");
//读取文件内容并且通过串口监视器输出文件信息
for(int i=0; i<dataFile.size(); i++){
Serial.print((char)dataFile.read());
}
//完成文件读取后关闭文件
dataFile.close();
}
void loop() {
}
结果
SPIFFS.exists(file_name)
用于检查闪存文件系统中有file_name文件(注:file_name变量具体信息在本程序第23行)。该函数返回值为布尔型。如果文件存在则返回真,否则将返回假。
File dataFile = SPIFFS.open(file_name, "r");
这条语句中,open函数可用于对SPIFFS进行操作。该函数共有两个参数。第一个参数file_name
是被操作的文件名称,本示例中该文件为/taichi-maker/notes.txt
第二个参数"r"
代表此操作为读取文件信息。
for循环语句中,循环条件使用了函数dataFile.size()
。该函数将会返回dataFile的大小。循环语句体中,dataFile.read()
将会读取dataFile文件内容。每调用一次该含税都会返回dataFile文件中一个字符。再次调用,将会返回下一个字符。以此类推,直到dataFile结尾。通过for循环语句,程序将会依次读取dataFile文件内容,并且将文件内容逐字符输出于串口监视器中。
3.通过程序向闪存文件系统文件添加信息
#include <FS.h>
String file_name = "/taichi-maker/notes.txt"; //被读取的文件位置和名称
void setup() {
Serial.begin(9600);
Serial.println("");
if(SPIFFS.begin()){ // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
//确认闪存中是否有file_name文件
if (SPIFFS.exists(file_name)){
Serial.print(file_name);
Serial.println(" FOUND.");
File dataFile = SPIFFS.open(file_name, "a");// 建立File对象用于向SPIFFS中的file对象(即/notes.txt)写入信息
dataFile.println("This is Appended Info."); // 向dataFile添加字符串信息
dataFile.close(); // 完成文件操作后关闭文件
Serial.println("Finished Appending data to SPIFFS");
} else {
Serial.print(file_name);
Serial.print(" NOT FOUND.");
}
}
void loop() {
}
File dataFile = SPIFFS.open(file_name, "a");
这条语句中,open函数可用于对SPIFFS进行操作。该函数共有两个参数。第一个参数file_name
是被操作的文件名称,第二个参数"a"
代表向该文件添加信息。请留意,此处的添加信息是不会删除文件内原有信息,而是在原有信息后面添加新的信息。这与但写入操作是有所区别的。写入操作是将文件内容完全清除,重新写入新信息。dataFile.println("This is Appended Info.")
,此语句作用将会向dataFile文件尾部添加双引号中的信息内容,也就是在文件尾部添加“This is Appended Info.”。
4. 通过程序读取目录内容
#include <FS.h>
String file_name = "/taichi-maker/myFile.txt"; //被读取的文件位置和名称
String folder_name = "/taichi-maker"; //被读取的文件夹
void setup() {
Serial.begin(9600);
Serial.println("");
if(SPIFFS.begin()){ // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
File dataFile = SPIFFS.open(file_name, "w");// 建立File对象用于向SPIFFS中的file对象(即myFile.txt)写入信息
dataFile.println("Hello Taichi-Maker."); // 向dataFile写入字符串信息
dataFile.close(); // 完成文件写入后关闭文件
Serial.println(F("Finished Writing data to SPIFFS"));
// 显示目录中文件内容以及文件大小
Dir dir = SPIFFS.openDir(folder_name); // 建立“目录”对象
while (dir.next()) { // dir.next()用于检查目录中是否还有“下一个文件”
Serial.println(dir.fileName()); // 输出文件名
}
}
void loop() {
}
建立了一个字符串变量。该变量用于存储文件夹名。其中/
代表根目录。/taichi-maker/
代表根目录下的taichi-maker
目录。
SPIFFS.openDir(folder_name)
中的openDir函数函将返回一个“目录”对象并且赋值给dir。此”目录”对象正是folder_name所存储的/taichi-maker/
目录。后续程序对dir的所有操作都是针对/taichi-maker/
所执行的。
while循环语句的循环条件是dir.next()
的返回值。dir.next()
函数用于检查dir文件夹内的文件。我们可以想象dir文件夹里有一个指针,每一次调用next函数都会让指针向下挪动一格。每一次挪动一格,如果下一个位置有文件,则返回真。否则将会返回假。因此,while (dir.next())
循环语句中的内容会依次显示dir文件夹中的每一个文件的文件名。
5.从闪存文件系统中删除文件
#include <FS.h>
String file_name = "/taichi-maker/notes.txt"; //被读取的文件位置和名称
void setup() {
Serial.begin(9600);
Serial.println("");
if(SPIFFS.begin()){ // 启动闪存文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
//从闪存中删除file_name文件
if (SPIFFS.remove(file_name)){
Serial.print(file_name);
Serial.println(" remove sucess");
} else {
Serial.print(file_name);
Serial.println(" remove fail");
}
}
void loop() {
}
通过使用SPIFFS.remove(file_name)
将file_name
所指代的文件进行了删除操作。另外,SPIFFS.remove(file_name)
的返回值为布尔型。如果文件删除执行成功则返回真,否则返回假。
6. 显示闪存文件系统信息
#include <FS.h>
FSInfo fs_info;
void setup() {
Serial.begin(9600);
SPIFFS.begin(); //启动SPIFFS
Serial.println("");
Serial.println("SPIFFS Started.");
// 闪存文件系统信息
SPIFFS.info(fs_info);
// 可用空间总和(单位:字节)
Serial.print("totalBytes: ");
Serial.print(fs_info.totalBytes);
Serial.println(" Bytes");
// 已用空间(单位:字节)
Serial.print("usedBytes: ");
Serial.print(fs_info.usedBytes);
Serial.println(" Bytes");
// 最大文件名字符限制(含路径和'\0')
Serial.print("maxPathLength: ");
Serial.println(fs_info.maxPathLength);
// 最多允许打开文件数量
Serial.print("maxOpenFiles: ");
Serial.println(fs_info.maxOpenFiles);
// 存储块大小
Serial.print("blockSize: ");
Serial.println(fs_info.blockSize);
// 存储页大小
Serial.print("pageSize: ");
Serial.println(fs_info.pageSize);
}
void loop() {
}
FSInfo fs_info;
建立了FSInfo 对象,用于存储闪存状态信息。
SPIFFS.info(fs_info);
。通过info函数将闪存状态信息赋给fs_info。后续的程序中,通过一系列语句将闪存状态信息通过串口监视器输出。具体信息内容可参考程序注释部分。
通过Arduino IDE向闪存文件系统上传文件
使用闪存文件系统建立功能更加丰富的网络服务器
使用前需要在文件目录建立一个data 文件夹并点击下面按键
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <FS.h> // 包含文件系统头文件
ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象,用于管理多个WiFi连接
ESP8266WebServer esp8266_server(80); // 建立网络服务器对象,监听80端口,用于响应HTTP请求
void setup() {
Serial.begin(9600); // 启动串口通讯,波特率9600
Serial.println("");
// 添加多个WiFi接入点供ESP8266连接
wifiMulti.addAP("TEST1", "A12345678");
wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2");
wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");
Serial.println("Connecting ...");
int i = 0;
while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行WiFi连接,直到成功
delay(1000);
Serial.print(i++); Serial.print(' ');
}
// WiFi连接成功后,通过串口监视器输出连接成功信息
Serial.println('\n');
Serial.print("Connected to ");
Serial.println(WiFi.SSID()); // 输出连接的WiFi名称
Serial.print("IP address:\t");
Serial.println(WiFi.localIP()); // 输出ESP8266-NodeMCU的IP地址
if(SPIFFS.begin()){ // 启动SPIFFS文件系统
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}
esp8266_server.onNotFound(handleUserRequet); // 设置处理404错误的回调函数
esp8266_server.begin(); // 启动HTTP服务器
Serial.println("HTTP server started");
}
void loop(void) {
esp8266_server.handleClient(); // 处理客户端请求
}
// 处理用户浏览器的HTTP访问
void handleUserRequet() {
String webAddress = esp8266_server.uri(); // 获取用户请求的网址
bool fileReadOK = handleFileRead(webAddress); // 尝试从SPIFFS读取文件
if (!fileReadOK){
esp8266_server.send(404, "text/plain", "404 Not Found"); // 如果文件未找到,则返回404错误
}
}
bool handleFileRead(String path) { // 处理浏览器HTTP访问
if (path.endsWith("/")) { // 如果访问地址以"/"为结尾
path = "/index.html"; // 将访问地址修改为/index.html便于SPIFFS访问
}
String contentType = getContentType(path); // 获取文件类型
if (SPIFFS.exists(path)) { // 如果访问的文件在SPIFFS中存在
File file = SPIFFS.open(path, "r"); // 打开该文件
esp8266_server.streamFile(file, contentType);// 将文件返回给浏览器
file.close(); // 关闭文件
return true; // 返回true表示成功
}
return false; // 返回false表示文件未找到
}
// 获取文件类型
String getContentType(String filename){
if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain"; // 默认返回"text/plain"
}
结果
ESP8266-NodeMCU网络客户端
ESP8266网络客户端基本操作
ESP8266-Arduino库中有两个库用于控制ESP8266与网络服务器进行通讯。他们是WiFiClient库和ESP8266HTTPClient库。
这两个库虽然功能相似,但是他们却是截然不同的两个库。ESP8266HTTPClient库相对简单易用。该库的库函数可以自动生成客户端请求信息并解析服务器响应信息。但正是由于该库的库函数为我们做了所有底层工作,这就导致该库不如WiFiclient库灵活。假如我们需要用ESP8266建立自定义客户端请求信息时,就要用WiFiClient库来实现了。另外WiFiClient库在解析服务器响应信息时可以使用丰富的STREAM类函数,这也为我们提供了很多便利。
因此,在我们使用ESP8266开发项目时,更多的时候是使用WiFiClient库来实现物联网通讯功能。换句话说,我个人认为WiFiClient库的重要性和实用性要高于ESP8266HTTPClient库。
以上描述有些抽象,接下来我们看两个简单示例。这两个示例的功能是相同的。都是通过ESP8266通过互联网向网络服务器发送请求并且将将网站服务器响应的信息输出在屏幕中。不同的是,示例一使用了ESP8266HTTPClient库来实现这一操作,示例二使用了WiFiClient库实现。
示例1. 使用ESP8266HTTPClient库实现网络通讯
根据错误信息,新版的 HTTPClient::begin
方法要求使用 WiFiClient
对象和 URL 作为参数。我们需要创建一个 WiFiClient
对象,并将其与 URL 一起传递给 HTTPClient::begin
方法。以下是更新后的代码:
#include <ESP8266WiFi.h> // 包含ESP8266 WiFi库
#include <ESP8266HTTPClient.h> // 包含HTTP客户端库
// 测试HTTP请求用的URL。注意网址前面必须添加"http://"
#define URL "http://www.example.com"
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "TEST1"; // WiFi名称
const char* password = "A12345678"; // WiFi密码
void setup() {
// 初始化串口设置
Serial.begin(9600); // 设置串口通讯波特率为9600
// 设置ESP8266工作模式为无线终端模式
WiFi.mode(WIFI_STA); // 将ESP8266设置为STA模式(无线终端模式)
// 开始连接WiFi
WiFi.begin(ssid, password); // 连接到指定的WiFi
// 等待WiFi连接, 连接成功打印IP
while (WiFi.status() != WL_CONNECTED) { // 等待WiFi连接成功
delay(1000);
Serial.print("."); // 每隔一秒打印一个点,表示正在连接
}
Serial.println("");
Serial.print("WiFi Connected!"); // WiFi连接成功后打印信息
Serial.print("IP address: ");
Serial.println(WiFi.localIP()); // 打印分配的IP地址
httpClientRequest(); // 发送HTTP请求并处理响应
}
void loop() {}
// 发送HTTP请求并且将服务器响应通过串口输出
void httpClientRequest(){
// 创建WiFiClient对象
WiFiClient wifiClient;
// 创建HTTPClient对象
HTTPClient httpClient;
// 初始化HTTP请求并设置目标URL
httpClient.begin(wifiClient, URL); // 配置请求地址
Serial.print("URL: ");
Serial.println(URL);
// 通过GET函数启动连接并发送HTTP请求
int httpCode = httpClient.GET(); // 发送HTTP GET请求并获取响应状态码
Serial.print("Send GET request to URL: ");
Serial.println(URL);
// 如果服务器响应HTTP_CODE_OK(200),则从服务器获取响应体信息并通过串口输出
if (httpCode > 0) { // 检查httpCode的值是否大于0,表示请求已发送成功
if (httpCode == HTTP_CODE_OK) { // 如果响应码为200(HTTP OK)
String responsePayload = httpClient.getString(); // 获取服务器响应体内容
Serial.println("Server Response Payload: ");
Serial.println(responsePayload); // 打印服务器响应体内容
} else {
Serial.println("Server Response Code: ");
Serial.println(httpCode); // 打印服务器响应状态码
}
} else {
Serial.println("Error on HTTP request");
Serial.println(httpCode); // 打印错误码
}
// 关闭ESP8266与服务器的连接
httpClient.end(); // 结束HTTP请求
}
程序重点讲解
重点1. 声明HTTPClient对象,对象名称httpClient。后续程序中,我们将使用对象httpClient控制ESP8266的网络通讯。
重点2. 通过ESP8266HTTPClient库的begin函数来设置ESP8266发送HTTP请求的目标URL。
重点3. 通过ESP8266HTTPClient库的GET函数向服务器发送HTTP请求。
重点4. 以上重点3中GET函数的返回值是网络服务器响应状态码。根据该状态码,我们可以判断服务器是否成功接收到了ESP8266客户端的请求。如果服务器成功接收到请求,我们就可以在接下来使用getString函数来获取服务器响应报文(服务器响应体)信息,并且将该信息传递给responsePayload变量以便我们在后面通过串口监视器显示服务器响应报文。(这一报文信息正是www.example.com网站的首页HTML源代码)。
重点5.执行完以上操作后,我们将关闭ESP8266与服务器连接。这里是通过ESP8266HTTPClient库的end函数来实现这一操作的。
示例2. 使用WiFiClient库实现网络通讯
程序重点讲解
重点1:实现网络服务器连接
这部分逻辑判断语句中的判断条件是client.connect(host, httpPort)
的返回值。ESP8266所实现的网络客户端是通过connect
函数来实现与网络服务器的连接的。被连接的网络服务器网址为connect函数的第一个参数,即host。这里的第二个参数httpPort
则是连接网络服务器的端口编号。关于host 和 httpPort的具体定义都在程序刚一开始的部分。
如果ESP8266所建立的网络客户端成功与网络服务器建立了连接,connect
函数将会返回“真”,否则将会返回“假”。利用connect
函数返回值,程序可以根据网络服务器的连接状况来决定具体执行哪一个操作。即:
– 连接成功则通过后续的while
循环语句来获取网络服务器的HTTP响应信息,并且将信息通过串口输出。
– 连接不成功则通过串口输出“连接失败”信息。
重点2:获取网络服务器响应信息并且通过串口输出
这里的 while (client.connected() || client.available())
循环语句判断条件由两个函数的返回值来决定。
第一个条件是 client.connected()
的返回值。connected()
这个函数用于检查当前ESP8266与网络服务器的连接情况。如果连接状态为“真”,则返回真。否则返回“假”。
第二个条件是 client.available()
的返回值。available()
函数用于检查网络客户端是否有接收到服务器发来的信息。如果有信息则返回真,否则返回“假”。
利用以上两个条件进行“或”运算所得到的结果即是这里while
循环语句的判断条件。换句话说,就是当ESP8266与服务器保持连接以及服务器有信息发送给ESP8266这两个条件满足一个,while循环语句体就会执行循环。当这两个条件都不满足了,则跳出循环。
接下来我们看一下while
循环的具体内容。这里我们使用了一个逻辑判断语句。判断条件再次出现了client.available()
。在ESP8266与网络服务器通过connect
函数建立连接并且ESP8266发送了HTTP请求以后,ESP8266并不会马上就收到服务器的响应信息。造成这个情况的原因有很多个,其中主要原意是服务器接到HTTP响应后,也许需要处理其它客户端的响应或者进行其它工作。这就导致服务器在响应ESP8266时会产生延迟。另外我们的网络环境也会产生延迟。也就是说服务器从发出响应到我们的ESP8266接收到响应这个过程是受到到网络环境等等因素影响的。
基于以上原因,我们需要让ESP8266客户端在与网络服务器取得连接以后在原地待命。一旦ESP8266客户端接收到服务器的响应信息,available函数将会返回“真”值,这时才让ESP8266客户端开始检查收到的响应信息具体是什么。这个检查工作是通过逻辑判断语句中的,client.readStringUntil('\n')
来实现的。
关于readStringUntil
函数的具体用法,由于涉及到stream的概念,我们在下一节给各位详细讲解。这里请各位了解一点就是client.readStringUntil('\n')
函数会将服务器响应信息中逐行分解为字符串。这些字符串会赋值给调用该函数的位置,也就是赋值给line
这个字符串变量中。
为了更好的理解这里的程序内容,建议您展开下一节“stream数据流基础”课程的学习。在了解了stream数据流基础的详细内容以后,就会对此处的程序内容有更好的理解了。
Stream – ESP8266物联网应用
Stream这个概念可能很多朋友会感到陌生。其实在过去的学习和开发中,我们已经使用Stream很久了。Stream对于ESP8266-Arduino语言来说指的是数据序列。请留意:在C++编程中Stream常被翻译作“流”。我们认为将Stream称为数据序列更加直观。因为数据序列这一概念有两个很关键特点。
第一个特点是“序”,即数据序列不能是杂乱无章的数据罗列。
第二个特点是“列”,即数据序列是排成一列的。
综上所述,数据序列可以理解为一列有先后顺序的数据。为了让您对数据序列也就是Stream数据有更直观的认识,这里我们将通过几个实例来向您说明Stream数据是什么。
示例1 使用串口监视器演示Stream概念
void setup() {
// 启动串口通讯
Serial.begin(9600);
Serial.println();
}
void loop() {
if (Serial.available()){ // 当串口接收到信息后
String serialData = Serial.readString(); // 将接收到的信息使用readString()存储于serialData变量
Serial.print(serialData); // 以便查看serialData变量的信息
}
}
在本示例中,我们使用了Serial.available
来判断ESP8266开发板是否接收到串口数据。这里的开发板通过串口所接收到的数据就是Stream数据。另外,程序通过Serial.println
语句将接收到的Stream数据通过串口输出并显示在串口监视器中,这里ESP8266通过串口所输出的数据也是Stream数据。换句话说,ESP8266开发板通过串口收发的数据都是Stream数据
示例2 使用HTTP请求和响应信息演示Stream概念
#include <ESP8266WiFi.h>
const char* host = "www.example.com"; // 网络服务器地址
const int httpPort = 80; // http端口80
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "taichimaker";
const char* password = "12345678";
void setup() {
//初始化串口设置
Serial.begin(9600);
Serial.println("");
//设置ESP8266工作模式为无线终端模式
WiFi.mode(WIFI_STA);
//开始连接wifi
WiFi.begin(ssid, password);
//等待WiFi连接,连接成功打印IP
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi Connected!");
wifiClientRequest();
}
void loop(){}
// 向服务器发送HTTP请求
void wifiClientRequest(){
// 建立WiFi客户端对象,对象名称client
WiFiClient client;
// 建立字符串,用于HTTP请求
String httpRequest = String("GET /") + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n" +
"\r\n";
// 通过串口输出连接服务器名称以便查阅连接服务器的网址
Serial.print("Connecting to ");
Serial.print(host);
// 连接网络服务器,以下段落中的示例程序为本程序重点1
// 请参考太极创客网站中关于本程序的讲解页面获取详细说明信息。网址:
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/esp8266-nodemcu-web-client/http-request/
if (client.connect(host, httpPort)){
Serial.println(" Success!"); // 连接成功后串口输出“Success”信息
client.print(httpRequest); // 向服务器发送合同谈判请求
Serial.println("Sending request: ");// 通过串口输出HTTP请求信息内容以便查阅
Serial.println(httpRequest);
// 通过串口输出网络服务器响应信息, 以下段落中的示例程序为本程序重点2
// 请参考太极创客网站中关于本程序的讲解页面获取详细说明信息。网址:
// http://www.taichi-maker.com/homepage/esp8266-nodemcu-iot/iot-c/esp8266-nodemcu-web-client/http-request/
Serial.println("Web Server Response:");
while (client.connected() || client.available()){
if (client.available()){
String line = client.readStringUntil('\n');
Serial.println(line);
}
}
client.stop(); // 断开与服务器的连接
Serial.print("Disconnected from "); // 并且通过串口输出断开连接信息
Serial.print(host);
} else{ // 如果连接不成功则通过串口输出“连接失败”信息
Serial.println(" connection failed!");
client.stop();
}
}