ESP8266+STA+AP+OTA:无需外网,用ESP8266建立网页控制GPIO,并用ArduinoOTA无线更新实现可持续升级

开关

先看看最终效果,局域网内查询并控制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随时间变化。

ip访问


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位,不然虽然不会报错(对,小坑),实际上是不会成功建立热点的,注意!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容