先看看最终效果,局域网内查询并控制ESP8266端口状态(以板载LED为例),无需远程服务器,路由器掉线,或外部网络垮掉都可以用,是不是很爽?
1. 原理:
在ESP8266上建立网页服务可以以两种方式实现,作为STA或者AP。
其中STA和AP的区别在于:STA是自己接入别人的网络,AP是自己建立网络让别人接入,所以在ESP8266上建立本地网页,不论是STA还是AP模式,除了开始接入方式不同,后面的建立server及对client应答的方式都是一样的。刚好ESP8266支持STA+AP,使用WiFi.mode(WIFI_AP_STA)调用就可以了。
2. 背景
为什么需要STA+AP+OTA?
首先STA+OTA是固定搭配,ESP8266作为客户端与刷写固件的电脑连入同一个路由器,通过mDNS被发现,是无线刷固件必须的组合。而AP模式的加入可以使网页不依赖路由器通道,即使外部网络出问题的时候,手机等设备仍然能够通过连接ESP8266自己发出的接入点打开网页,与GPIO口进行交互,当然,在路由器可以接上的情况下,直接IP访问还是很方便的。
一般来说是这样的,路由器DHCP会记忆这个IP第一次分配的对应设备,除非因为IP池不够用而进行了二次分配导致该IP被占用,否则会一直分配同一个IP给同一个设备,也就是说,不用特地设置固定ip也不麻烦的。
3. 功能A(查询演示)
采用OTA方式无线更新,间隔一秒翻转点亮一次wemos板载LED,通过
1.在板子由wifi自动接入路由器时,同路由器下的设备直接访问OTA端口所在的ip地址,
2.现有wifi连接失败时,或者就是喜欢通过热点直连时,连接esp8266创建的热点"espAccessPoint"并访问192.168.4.1(默认热点IP),
从网页读取LED的亮灭状态,其中网页每5秒自动刷新一次,可以看到“status:”后的数字0,1随时间变化。
4.代码A
/*********
by Ningh
adapted from Arduino IDE example: Examples > Arduino OTA > BasicOTA.ino
https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/server-examples.html
*********/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
// Replace with your network credentials
const char* ssid = "your-ssid";//wifi接入
const char* password = "your-password";
const char* assid = "espAccessPoint";//创建AP热点
const char* asecret = "12345678";
WiFiServer server(80);//创建tcp server
const int ESP_BUILTIN_LED = 2;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;
int connCount = 0;//连接失败计数
void setup() {
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(assid, asecret);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
delay(500);
if(connCount > 30) {//如果连续30次连不上,则跳出循环,执行后面的程序
break;
}
connCount += 1;
// ESP.restart();
}
// Hostname defaults to esp8266-[ChipID]
//ArduinoOTA.setHostname("WemosEXP");
ArduinoOTA.begin();
server.begin();//启动tcp连接
pinMode(ESP_BUILTIN_LED, OUTPUT);
}
String prepareHtmlPage(){
String htmlPage =
String("HTTP/1.1 200 OK\r\n") +
"Content-Type: text/html\r\n" +
"Connection: close\r\n" + // the connection will be closed after completion of the response
"Refresh: 5\r\n" + // refresh the page automatically every 5 sec
"\r\n" +
"<!DOCTYPE HTML>" +
"<html>" +
"Digital PIN2 status: " + String(digitalRead(ESP_BUILTIN_LED)) +
"</html>" +
"\r\n";
return htmlPage;
}
void loop() {
ArduinoOTA.handle();
WiFiClient client = server.available();
// wait for a client (web browser) to connect
if (client){
while (client.connected()){
// 不断读取请求内容
if (client.available()){
String line = client.readStringUntil('\r');
Serial.print(line);
// wait for end of client's request, that is marked with an empty line
if (line.length() == 1 && line[0] == '\n'){
//返回响应内容
client.println(prepareHtmlPage());
break;
}
}
}
delay(1); // give the web browser time to receive the data
// close the connection:
client.flush();//注意这里必须用client.flush(),如果是client.close就会报错的
}
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH; // Note that this switches the LED *off*
} else {
ledState = LOW; // Note that this switches the LED *on*
}
digitalWrite(ESP_BUILTIN_LED, ledState);
}
}
5. 功能B(状态查询+控制演示)
代码B
/*********
Rui Santos
Complete project details at http://randomnerdtutorials.com
Arduino IDE example: Examples > Arduino OTA > BasicOTA.ino
*********/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
// Replace with your network credentials
const char* ssid = "your-ssid";
const char* password = "your-password";
const char* assid = "espAccessPoint";
const char* asecret = "12345678";
WiFiServer server(80);//创建tcp server
const int ESP_BUILTIN_LED = 2;
int connCount = 0;//连接失败计数
void setup() {
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(assid, asecret);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
delay(500);
if(connCount > 30) {//如果连续30次连不上,则跳出循环,执行后面的程序
break;
}
connCount += 1;
//ESP.restart();
}
// Hostname defaults to esp8266-[ChipID]
//ArduinoOTA.setHostname("WemosEXP");
ArduinoOTA.begin();
server.begin();//启动tcp连接
pinMode(ESP_BUILTIN_LED, OUTPUT);
}
String prepareHtmlPage(){
String htmlPage =
String("HTTP/1.1 200 OK\r\n") +
"Content-Type: text/html\r\n" +
"Connection: close\r\n" + // the connection will be closed after completion of the response
// "Refresh: 5\r\n" + // refresh the page automatically every 5 sec
"\r\n" +
"<!DOCTYPE html> <html>\n"+
"<h1>Digital PIN2 status: "+String(digitalRead(ESP_BUILTIN_LED))+"</h1>\n"+
"<form id='F1' action='LEDON'><input class='button button-on' type='submit' value='ON' ></form><br>"+
"<form id='F2' action='LEDOFF'><input class='button button-off' type='submit' value='OFF' ></form><br>"+
"</html>" +
"\r\n";
return htmlPage;
}
void loop() {
ArduinoOTA.handle();
WiFiClient client = server.available();
// wait for a client (web browser) to connect
if (client){
while (client.connected()){
// 不断读取请求内容
if (client.available()){
String line = client.readStringUntil('\r');
if ( line.indexOf("LEDON") > 0 ) { digitalWrite(ESP_BUILTIN_LED, LOW); }
else if ( line.indexOf("LEDOFF") > 0 ) { digitalWrite(ESP_BUILTIN_LED, HIGH); }
// wait for end of client's request, that is marked with an empty line
if (line.length() == 1 && line[0] == '\n'){
//返回响应内容
client.println(prepareHtmlPage());
break;
}
}
}
delay(1); // give the web browser time to receive the data
// close the connection:
client.flush();//注意这里必须用client.flush(),如果是client.close就会报错的
}
}
OK,功能搞定!但是这个好像有那里不对~~虽然为了可复用性,我一向推崇bare minimum,但是什么都不加,这页面实在是外表有些low啊,根本没法混。算了,必须承认极简不是简陋,拿不出手咋办呢?好办!只要在页面加入一些css样式表就可以了,这样即不影响代码结构,又不需要加载js等其它控件,很节约资源了。(js等如果需要断网使用则要放在本地,涉及到spiffs存储,it's another story,and getting more and more complicated,先打住,如果以后实在要用到ajax再说吧)
"<!DOCTYPE html> <html>\n"+
后面加上
"<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"+
"<title>LED Control</title>\n"+
"<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"+
"body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n"+
".button {display: block;width: 120px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n"+
".button-on {background-color: #5AD2FF;}\n"+
".button-on:active {background-color: #DE341E;}\n"+
".button-off {background-color: #34495e;}\n"+
".button-off:active {background-color: #DE341E;}\n"+
"p {font-size: 14px;color: #888;margin-bottom: 10px;}\n"+
"</style>\n"+
"</head>\n"+
"<body>\n"+
6.代码C
/*********
By Ningh
adapted from Arduino IDE example: Examples > Arduino OTA > BasicOTA.ino
with reference to https://lastminuteengineers.com/creating-esp8266-web-server-arduino-ide/
*********/
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
// Replace with your network credentials
const char* ssid = "your-ssid";
const char* password = "your-password";
const char* assid = "espAccessPoint";
const char* asecret = "12345678";
WiFiServer server(80);//创建tcp server
const int ESP_BUILTIN_LED = 2;
int connCount = 0;//连接失败计数
void setup() {
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(assid, asecret);
WiFi.begin(ssid, password);
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
delay(500);
if(connCount > 30) {//如果连续30次连不上,则跳出循环,执行后面的程序
break;
}
connCount += 1;
//ESP.restart();
}
// Hostname defaults to esp8266-[ChipID]
//ArduinoOTA.setHostname("WemosEXP");
ArduinoOTA.begin();
server.begin();//启动tcp连接
pinMode(ESP_BUILTIN_LED, OUTPUT);
}
String prepareHtmlPage(){
String htmlPage =
String("HTTP/1.1 200 OK\r\n") +
"Content-Type: text/html\r\n" +
"Connection: close\r\n" + // the connection will be closed after completion of the response
"\r\n" +
"<!DOCTYPE html> <html>\n"+
"<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"+
"<title>LED Control</title>\n"+
"<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"+
"body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n"+
".button {display: block;width: 120px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n"+
".button-on {background-color: #5AD2FF;}\n"+
".button-on:active {background-color: #DE341E;}\n"+
".button-off {background-color: #34495e;}\n"+
".button-off:active {background-color: #DE341E;}\n"+
"p {font-size: 14px;color: #888;margin-bottom: 10px;}\n"+
"</style>\n"+
"</head>\n"+
"<body>\n"+
"<h1>Digital PIN2 status: "+String(digitalRead(ESP_BUILTIN_LED))+"</h1>\n"+
"<form id='F1' action='LEDON'><input class='button button-on' type='submit' value='ON' ></form><br>"+
"<form id='F2' action='LEDOFF'><input class='button button-off' type='submit' value='OFF' ></form><br>"+
"</html>" +
"\r\n";
return htmlPage;
}
void loop() {
ArduinoOTA.handle();
WiFiClient client = server.available();
// wait for a client (web browser) to connect
if (client){
while (client.connected()){
// 不断读取请求内容
if (client.available()){
String line = client.readStringUntil('\r');
if ( line.indexOf("LEDON") > 0 ) { digitalWrite(ESP_BUILTIN_LED, LOW); }
else if ( line.indexOf("LEDOFF") > 0 ) { digitalWrite(ESP_BUILTIN_LED, HIGH); }
// Serial.print(line);
// wait for end of client's request, that is marked with an empty line
if (line.length() == 1 && line[0] == '\n'){
//返回响应内容
client.println(prepareHtmlPage());
break;
}
}
}
delay(1); // give the web browser time to receive the data
// close the connection:
client.flush();//注意这里必须用client.flush(),如果是client.close会报错的
}
}
补充要点:esp8266建立AP密码至少要8位,不然虽然不会报错(对,小坑),实际上是不会成功建立热点的,注意!