WIFI模块(ESP8266)学习4


一、HttpClient—–ESP8266HTTPClient库的使用

HTTP知识:

1、概述:

HTTP协议(Hyper Text Transfer Protocol)超文本传输协议,用于从WWW服务器传输文本到本地浏览器的传送协议。基于TCP/IP通信协议:浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。WEB服务器根据接收到的请求后,向客户端发送响应信息;

HTTP协议作为TCP/IP模型中应用层的协议,承载与TCP协议上,有时也承载与TLS或者SSL协议层之上,这时就是HTTPS

HTTP由请求响应构成,是一个标准的客户端服务器模型,默认端口号80,HTTPS是443;

浏览网页时HTTP主要应用(不只是这个功能,只是一种协议,只要通信双方都支持这个协议,HTTP就能用)

特点:HTTP0.9与1.0使用非持续连接;1.1使用持续连接。

无状态,无记忆能力

2、工作流程:(一次HTTP操作称位一个事务,可分4步)

  1. client与server建立连接;

  2. 连接后,client发送一个请求给server,请求方法格式:统一资源标示符(URL)、HTTP协议版本号、请求头、请求内容等;

  3. server接收到请求后,给予响应,格式:状态行(包括协议版本、成功或者失败代码、服务器信息、实体信息等)

  4. client接收到server返回信息,通过浏览器显示在用户显示屏上,然后client与server断开连接

3、HTTP请求:

Get请求:

  • 请求行(request line):说明请求类型,要访问的资源及HTTP版本
  • 请求头部(header):说明服务器要使用的附加信息(第二行起位请求头部,HOST指出目的地;User-Agent是client与server脚本都能访问,是浏览器检测逻辑的重要基础)(该信息由浏览器定义,并在每个请求中自动发送)
  • 空行(empty line):请求头部后空行是必须的(即使第四部分请求数据为空,也必须有空行)
  • 请求数据(request body):主体,可添加任意其他数据(下面例子中请求数据为空)

请求例子(Gharles抓取的request)

GET /562f25980001b1b106000338.jpg HTTP/1.1
Host    img.mukewang.com
User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept    image/webp,image/*,*/*;q=0.8
Referer    http://www.imooc.com/
Accept-Encoding    gzip, deflate, sdch
Accept-Language    zh-CN,zh;q=0.8

POST请求:

请求例子(Gharles抓取的request)

POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
 
name=Professional%20Ajax&publisher=Wiley

4、HTTP Response响应信息

  • 状态行
  • 消息报头
  • 空行
  • 响应正文

huf9qH.jpg

HTTP状态码

f3MpdO.png

示例:

  1. 获取天气请求(心知天气)

setup中配置好url,串口参数和httpclient并设置client请求头。loop中每1秒请求一次get服务,把获取回来的天气信息通过json库转成具体对应的数值

/**
 * Demo:
 *    演示Http请求天气接口信息
 *    https://api.seniverse.com/v3/weather/now.json?key=Sv6J6HVHp8V2Cc34a&location=beijing&language=zh-Hans&unit=c
 */
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>  //不属于ESP8266WiFi库的一部分
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "XU-ChinaNet";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "15358228063";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const char* HOST = "http://api.seniverse.com";
const char* APIKEY = "";        //API KEY
const char* CITY = "yancheng";
const char* LANGUAGE = "zh-Hans";//zh-Hans 简体中文  会显示乱码
  
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const unsigned long HTTP_TIMEOUT = 5000;               // max respone time from server
 
// 我们要从此网页中提取的数据的类型
struct WeatherData {
  char city[16];//城市名称
  char weather[32];//天气介绍(多云...)
  char temp[16];//温度
  char udate[32];//更新时间
};
WiFiClient client;
HTTPClient http;
String GetUrl;
String response;
WeatherData weatherData;
 
void setup() {
  // put your setup code here, to run once:
  WiFi.mode(WIFI_STA);     //设置esp8266 工作模式
  DebugBegin(BAUD_RATE);
  DebugPrint("Connecting to ");//写几句提示,哈哈
  DebugPrintln(AP_SSID);
  WiFi.begin(AP_SSID, AP_PSK);   //连接wifi
  WiFi.setAutoConnect(true);
  while (WiFi.status() != WL_CONNECTED) {
    //这个函数是wifi连接状态,返回wifi链接状态
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrintln("WiFi connected");
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
 
  //拼接get请求url  博哥后面考虑看看是否可以封装一个方法来用用 不需要自己一个个拼装这个url
  GetUrl = String(HOST) + "/v3/weather/now.json?key=";
  GetUrl += APIKEY;
  GetUrl += "&location=";
  GetUrl += CITY;
  GetUrl += "&language=";
  GetUrl += LANGUAGE;
  //设置超时
  http.setTimeout(HTTP_TIMEOUT);
  //设置请求url
  //http.begin(GetUrl);
  http.begin(client,GetUrl);
  //以下为设置一些头  其实没什么用 最重要是后端服务器支持
  http.setUserAgent("esp8266");//用户代理版本
  http.setAuthorization("esp8266","boge");//用户校验信息
}
 
void loop() {
  //心知天气  发送http  get请求
  int httpCode = http.GET();
  if (httpCode > 0) {
      Serial.printf("[HTTP] GET... code: %d\n", httpCode);
      //判断请求是否成功
      if (httpCode == HTTP_CODE_OK) {
        //读取响应内容
        response = http.getString();
        DebugPrintln("Get the data from Internet!");
        DebugPrintln(response);
        //解析响应内容
        if (parseUserData(response, &weatherData)) {
          //打印响应内容
          printUserData(&weatherData);
        }
      }
  } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
  delay(1000);//每1s调用一次 
}
  
/**
 * @Desc 解析数据 Json解析
 * 数据格式如下:
 * {
 *    "results": [
 *        {
 *            "location": {
 *                "id": "WX4FBXXFKE4F",
 *                "name": "北京",
 *                "country": "CN",
 *                "path": "北京,北京,中国",
 *                "timezone": "Asia/Shanghai",
 *                "timezone_offset": "+08:00"
 *            },
 *            "now": {
 *                "text": "多云",
 *                "code": "4",
 *                "temperature": "23"
 *            },
 *            "last_update": "2017-09-13T09:51:00+08:00"
 *        }
 *    ]
 *}
 */
bool parseUserData(String content, struct WeatherData* weatherData) {
//    -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
//   如果你使用StaticJsonBuffer时才需要
//    const size_t BUFFER_SIZE = 1024;
//   在堆栈上分配一个临时内存池
//    StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
//    -- 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
  DynamicJsonBuffer jsonBuffer;
   
  JsonObject& root = jsonBuffer.parseObject(content);
   
  if (!root.success()) {
    DebugPrintln("JSON parsing failed!");
    return false;
  }
    
  //复制我们感兴趣的字符串
  strcpy(weatherData->city, root["results"][0]["location"]["name"]);
  strcpy(weatherData->weather, root["results"][0]["now"]["text"]);
  strcpy(weatherData->temp, root["results"][0]["now"]["temperature"]);
  strcpy(weatherData->udate, root["results"][0]["last_update"]);
  //  -- 这不是强制复制,你可以使用指针,因为他们是指向“内容”缓冲区内,所以你需要确保
  //   当你读取字符串时它仍在内存中
  return true;
}
   
// 打印从JSON中提取的数据
void printUserData(const struct WeatherData* weatherData) {
  DebugPrintln("Print parsed data :");
  DebugPrint("City : ");
  DebugPrint(weatherData->city);
  DebugPrint(", \t");
  DebugPrint("Weather : ");
  DebugPrint(weatherData->weather);
  DebugPrint(",\t");
  DebugPrint("Temp : ");
  DebugPrint(weatherData->temp);
  DebugPrint(" C");
  DebugPrint(",\t");
  DebugPrint("Last Updata : ");
  DebugPrint(weatherData->udate);
  DebugPrintln("\r\n");
}
  1. 演视响应头获取信息

    设置了获取四个请求头的信息,然后通过header方法获取它们的数值(接口限制,无法发挥作用)

/**
 * Demo:
 *    演示Http请求天气接口信息,演示响应头操作
 */
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

const char* AP_SSID     = "XU-ChinaNet";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "15358228063";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const char* HOST = "http://api.seniverse.com";
const char* APIKEY = "";        //API KEY
const char* CITY = "yancheng";
const char* LANGUAGE = "zh-Hans";//zh-Hans 简体中文  会显示乱码
const char *keys[] = {"Content-Length","Content-Type","Connection","Date"};//需要收集的响应头的信息
  
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const unsigned long HTTP_TIMEOUT = 5000;               // max respone time from server;

WiFiClient client;
HTTPClient http;
String GetUrl;
String response;

void setup() {
  // put your setup code here, to run once:
  WiFi.mode(WIFI_STA);     //设置esp8266 工作模式
  DebugBegin(BAUD_RATE);
  DebugPrint("Connecting to ");//写几句提示,哈哈
  DebugPrintln(AP_SSID);
  WiFi.begin(AP_SSID, AP_PSK);   //连接wifi
  WiFi.setAutoConnect(true);
  while (WiFi.status() != WL_CONNECTED) {
    //这个函数是wifi连接状态,返回wifi链接状态
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrintln("WiFi connected");
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
  //拼接get请求url  博哥后面考虑看看是否可以封装一个方法来用用 不需要自己一个个拼装这个url
  GetUrl = String(HOST) + "/v3/weather/now.json?key=";
  GetUrl += APIKEY;
  GetUrl += "&location=";
  GetUrl += CITY;
  GetUrl += "&language=";
  GetUrl += LANGUAGE;
  //设置超时
  http.setTimeout(HTTP_TIMEOUT);
  //设置请求url
  http.begin(client,GetUrl);
  //以下为设置一些头  其实没什么用 最重要是后端服务器支持
  http.setUserAgent("esp8266");//用户代理版本
  http.setAuthorization("esp8266","boge");//用户校验信息
  http.addHeader("myname","cainiaobo");

  //设置获取响应头的信息
  http.collectHeaders(keys,4);
}

void loop() {
  //心知天气  发送http  get请求
  int httpCode = http.GET();
  if (httpCode > 0) {
      Serial.printf("[HTTP] GET... code: %d\n", httpCode);
      //判断请求是否成功
      if (httpCode == HTTP_CODE_OK) {
        //读取响应内容
        response = http.getString();
        DebugPrintln("Get the data from Internet!");
        DebugPrintln(response);
        DebugPrintln(String("Content-Length:")+ http.header("Content-Length"));
        DebugPrintln(String("Content-Type:")+ http.header("Content-Type"));
        DebugPrintln(String("Connection:")+ http.header("Connection"));
        DebugPrintln(String("Date:")+ http.header("Date"));
      }
  } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
  delay(1000);//每1s调用一次 
}

二、UDP服务

UDP知识:

UDP(User Datagram Protocol)一种无连接、不可靠的协议(可能会丢包),

相对于TCP而言:

  • UDP面向无连接的,不需建立连接(TCP是面向连接的)

  • UDP尽力做到可靠,但不绝对可靠(TCP无差错、不丢失、不重复且按序到达)

  • UDP较好实时性,效率比TCP高

  • UDP支持一对一,一对多,多对一和多对多的交互通信(TCP点到点)

  • UDP对系统资源要求较少(TCP多)

f31s6s.png

  1. 通过UDP收发数据

ESP8266作为UDP服务端,把电脑UDP客户端发过来的数据打印到串口调试器,并回复应答消息(使用Packet Sender软件发送UDP数据)

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

const char* ssid = "XU-ChinaNet";//wifi账号
const char* password = "15358228063";//wifi密码

WiFiUDP Udp;
unsigned int localUdpPort = 4210;  // 本地监听端口
char incomingPacket[255];  // 存储Udp客户端发过来的数据
char  replyPacket[] = "Hi there! Got the message :-)";  // 应答信息


void setup()
{
  Serial.begin(115200);
  Serial.println();

  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");

  //启动Udp监听服务
  Udp.begin(localUdpPort);
  //打印本地ip地址,udp client端会使用到
  Serial.printf("Now listening at IP %s, UDP port %d\n", WiFi.localIP().toString().c_str(), localUdpPort);
}


void loop()
{
  //解析Udp数据包
  int packetSize = Udp.parsePacket();
  if (packetSize)
  {
    // 收到Udp数据包
    Serial.printf("Received %d bytes from %s, port %d\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort());
    // 读取Udp数据包
    int len = Udp.read(incomingPacket, 255);
    if (len > 0)
    {
      incomingPacket[len] = 0;
    }
    //向D串口调试器打印信息
    Serial.printf("UP packet contents: %s\n", incomingPacket);

    //往udp 远端发送应答信息
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(replyPacket);
    Udp.endPacket();
  }
}
  1. 通过UDP控制LED
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>

const char *ssid = "XU-ChinaNet";     //wifi名称
const char *password = "15358228063"; //wifi密码

WiFiUDP Udp;
unsigned int localUdpPort = 4210; // 本地端口号
char incomingPacket[255];         // 接收缓冲区

void setup()
{
  //以下为基本功能初始化,初始化串口和网络和LED
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  Serial.println();
  Serial.printf("Connecting to %s ", ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" connected");

  //以下开启UDP监听并打印输出信息
  Udp.begin(localUdpPort);
  Serial.printf("Now listening at IP %s, UDP port %d\n", WiFi.localIP().toString().c_str(), localUdpPort);
}

void loop()
{
  int packetSize = Udp.parsePacket(); //获取当前队首数据包长度
  if (packetSize)  // 有数据可用
  {
    Serial.printf("Received %d bytes from %s, port %d\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort());
    int len = Udp.read(incomingPacket, 255); // 读取数据到incomingPacket
    if (len > 0)                             // 如果正确读取
    {
      incomingPacket[len] = 0; //末尾补0结束字符串
      Serial.printf("UDP packet contents: %s\n", incomingPacket);

      if (strcmp(incomingPacket, "LED_OFF") == 0) // 命令LED_OFF
      {
        digitalWrite(LED_BUILTIN, HIGH); // 熄灭LED
        sendCallBack("LED has been turn off");
      }
      else if (strcmp(incomingPacket, "LED_ON") == 0) // 如果收到LED_ON
      {
        digitalWrite(LED_BUILTIN, LOW); // 点亮LED
        sendCallBack("LED has been turn on");
      }
      else // 如果非指定消息
      {
        sendCallBack("Command Error!");
      }
    }
  }
}

/**
 * 发送响应信息
 */
void sendCallBack(const char *buffer){
   Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
   Udp.write(buffer); //回复内容
   Udp.endPacket(); 
}

三、WebServer—ESP8266WebServer库的使用

f8kYYq.png

示例:

  1. 演示webserver基础功能,wifi模块连接上热点后,在PC浏览器上输入serverip+url访问
/**
 * Demo:
 *    演示webserver基础功能
 *    (当wifi模块连接上ap之后,在pc浏览器中输入ip+uri来访问)
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "TP-LINK_5344";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "6206908you11011010";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
 
//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
 
ESP8266WebServer server(80);//创建一个webserver
 
/**
 * 处理根目录uri请求
 * uri:http://server_ip/
 */
void handleRoot() {
  server.send(200, "text/plain", "hello from esp8266!");
}
 
/**
 * 处理无效uri
 * uri:http://server_ip/xxxx
 */
void handleNotFound() {
  //打印无效uri的信息 包括请求方式 请求参数
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}
 
void setup(void) {
  initBasic();
  initWifi();
  initWebServer();
}
 
/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}
 
/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}
 
/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  server.on("/", handleRoot);
 
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });
 
  server.onNotFound(handleNotFound);
  //启动webserver
  server.begin();
  DebugPrintln("HTTP server started");
}
 
void loop(void) {
  server.handleClient();
}
  1. 演示webserver返回html功能
/**
 * Demo:
 *    演示webserver html功能
 *    (当wifi模块连接上ap之后,在pc浏览器中输入ip+uri来访问)
 */
 
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "XU-ChinaNet";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "15358228063";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
 
//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
 
ESP8266WebServer server(80);
 
/**
 * 处理根目录uri请求
 * uri:http://server_ip/
 */
void handleRoot() {
  char temp[400];
  int sec = millis() / 1000;
  int min = sec / 60;
  int hr = min / 60;
 
  snprintf(temp, 400,
 
           "<html>\
  <head>\
    <meta http-equiv='refresh' content='5'/>\
    <title>ESP8266 Demo</title>\
    <style>\
      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
    </style>\
  </head>\
  <body>\
    <h1>Hello from ESP8266!</h1>\
    <p>Uptime: %02d:%02d:%02d</p>\
    <img src=\"/test.svg\" />\
  </body>\
</html>",
 
           hr, min % 60, sec % 60
          );
  server.send(200, "text/html", temp);
}
 
/**
 * 处理无效uri
 * uri:http://server_ip/xxxx
 */
void handleNotFound() {
  //打印无效uri的信息 包括请求方式 请求参数
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}
 
void setup(void) {
  initBasic();
  initWifi();
  initWebServer();
}
 
void loop(void) {
  server.handleClient();
}
 
/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}
 
/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}
 
/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  server.on("/", handleRoot);
 
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works as well");
  });
  server.on("/test.svg", drawGraph);
  server.onNotFound(handleNotFound);
  //启动webserver
  server.begin();
  DebugPrintln("HTTP server started");
}
 
/**
 * 画图
 */
void drawGraph() {
  String out = "";
  char temp[100];
  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
  out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
  out += "<g stroke=\"black\">\n";
  int y = rand() % 130;
  for (int x = 10; x < 390; x += 10) {
    int y2 = rand() % 130;
    sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
    out += temp;
    y = y2;
  }
  out += "</g>\n</svg>\n";
 
  server.send(200, "image/svg+xml", out);
}
  1. 演示webserver校验账号密码功能,Autherticate请求头
/**
 * Demo:
 *    演示webserver auth校验功能
 *    (当wifi模块连接上ap之后,在pc浏览器中输入ip+uri来访问,不过需要带校验请求头)
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "XU-ChinaNet";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "15358228063";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
const char* www_username = "admin";
const char* www_password = "esp8266";
 
//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
 
ESP8266WebServer server(80);//创建webserver
 
void setup() {
  initBasic();
  initWifi();
  initWebServer();
}
 
void loop() {
  server.handleClient();
}
 
/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}
 
/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}
 
/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  server.on("/", []() {
    //校验帐号和密码
    if (!server.authenticate(www_username, www_password)) {
      return server.requestAuthentication();
    }
    server.send(200, "text/plain", "Login OK");
  });
  server.begin();
 
  DebugPrint("Open http://");
  DebugPrint(WiFi.localIP());
  DebugPrintln("/ in your browser to see it working");
}
  1. 演示webserver登录功能,html登录页面
/**
 * Demo:
 *    演示webserver auth校验功能
 *    (当wifi模块连接上ap之后,在pc浏览器中输入ip+uri来访问,不过需要带校验请求头)
 */
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "TP-LINK_5344";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "6206908you11011010";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;                   // serial connection speed
 
//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
 
ESP8266WebServer server(80);
 
/**
 * 校验是否存在cookie头并且cookie头的值是正确的
 */
bool is_authentified() {
  DebugPrintln("Enter is_authentified");
  //是否存在cookie头
  if (server.hasHeader("Cookie")) {
    DebugPrint("Found cookie: ");
    //获取cookie头的信息
    String cookie = server.header("Cookie");
    DebugPrintln(cookie);
    if (cookie.indexOf("ESPSESSIONID=1") != -1) {
      DebugPrintln("Authentification Successful");
      return true;
    }
  }
  DebugPrintln("Authentification Failed");
  return false;
}
 
/**
 * 处理登陆uri
 */
void handleLogin() {
  String msg;
  //判断是否存在cookie头
  if (server.hasHeader("Cookie")) {
    DebugPrint("Found cookie: ");
    String cookie = server.header("Cookie");
    DebugPrint(cookie);
  }
  //判断是否存在DISCONNECT参数
  if (server.hasArg("DISCONNECT")) {
    DebugPrintln("Disconnection");
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
    server.send(301);
    return;
  }
  //判断是否存在USERNAME和PASSWORD参数
  if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) {
    if (server.arg("USERNAME") == "admin" &&  server.arg("PASSWORD") == "admin") {
      server.sendHeader("Location", "/");
      server.sendHeader("Cache-Control", "no-cache");
      server.sendHeader("Set-Cookie", "ESPSESSIONID=1");
      server.send(301);
      DebugPrintln("Log in Successful");
      return;
    }
    msg = "Wrong username/password! try again.";
    DebugPrintln("Log in Failed");
  }
  //返回html 填写账号密码页面
  String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
  content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
  content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
  content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
  content += "You also can go <a href='/inline'>here</a></body></html>";
  server.send(200, "text/html", content);
}
 
/**
 * 根目录处理器
 */
//root page can be accessed only if authentification is ok
void handleRoot() {
  DebugPrintln("Enter handleRoot");
  String header;
  if (!is_authentified()) {
    //校验不通过
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(301);
    return;
  }
  String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>";
  if (server.hasHeader("User-Agent")) {
    content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
  }
  content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
  server.send(200, "text/html", content);
}
 
/**
 * 无效uri处理器
 */
void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}
 
void setup(void) {
  initBasic();
  initWifi();
  initWebServer();
}
 
void loop(void) {
  server.handleClient();
}
 
/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}
 
/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}
 
/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  
  server.on("/", handleRoot);
  server.on("/login", handleLogin);
  server.on("/inline", []() {
    server.send(200, "text/plain", "this works without need of authentification");
  });
 
  server.onNotFound(handleNotFound);
  //设置需要收集的请求头
  const char * headerkeys[] = {"User-Agent", "Cookie"} ;
  size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*);
  //收集头信息
  server.collectHeaders(headerkeys, headerkeyssize);
  server.begin();
  DebugPrintln("HTTP server started");
}

四、域名服务—ESP8266mDNS库

概述:

DNS(Domain Name System)域名系统(因特网上作为域名和IP地址相互映射的一个分布式数据库)

DNS协议运行在UDP协议上,端口号53

mDNS(Multicast DNS)组播dns,主要实现了在没有传统DNS服务器的情况下使用局域网内的主机实现本地发现和域名访问,端口号5353,遵从dns协议(基于UDP协议,即运用了UDP广播)

f88DBR.png

  1. 演示ESP8266 mDNS responder功能

电脑端输入http://esp8266.local/以域名方式访问webserver

/**
 * Demo:
 *    演示ESP8266 mDNS responder功能
 */
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* AP_SSID     = "TP-LINK_5344"; // XXXXXX -- 使用时请修改为当前你的 wifi ssid
const char* AP_PSK = "6206908you11011010";  // XXXXXX -- 使用时请修改为当前你的 wifi 密码
const unsigned long BAUD_RATE = 115200;// serial connection speed
 
//声明一下函数
void initBasic(void);
void initWifi(void);
void initWebServer(void);
void initmDNS(void);
 
ESP8266WebServer server(80);
 
/**
 * 处理根目录uri请求
 * uri:http://server_ip/
 */
void handleRoot() {
  DebugPrintln("handleRoot");
  server.send(200, "text/html", "Hello From ESP8266 mDNS demo");
}
 
/**
 * 处理无效uri
 * uri:http://server_ip/xxxx
 */
void handleNotFound() {
  DebugPrintln("handleNotFound");
  //打印无效uri的信息 包括请求方式 请求参数
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}
 
void setup(void) {
  initBasic();
  initWifi();
  initWebServer();
  initmDNS();
}
 
void loop(void) {
  MDNS.update();    //少这行
  server.handleClient();
}
 
/**
 * 初始化基础功能:波特率
 */
void initBasic(){
  DebugBegin(BAUD_RATE);
}
 
/**
 * 初始化wifi模块:工作模式 连接网络
 */
void initWifi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(AP_SSID, AP_PSK);
  DebugPrintln("");
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    DebugPrint(".");
  }
  DebugPrintln("");
  DebugPrint("Connected to ");
  DebugPrintln(AP_SSID);
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}
 
/**
 * 初始化webserver
 */
void initWebServer(){
  //以下配置uri对应的handler
  server.on("/", handleRoot);
  server.on("/inline", []() {
    DebugPrintln("handleInline");
    server.send(200, "text/plain", "this works as well");
  });
  server.onNotFound(handleNotFound);
  //启动webserver
  server.begin();
  DebugPrintln("HTTP server started");
}
 
/**
 * 初始化mDNS
 */
void initmDNS(){
  if (!MDNS.begin("esp8266")) {
    DebugPrintln("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  DebugPrintln("mDNS responder started,please input http://esp8266.local/ in your browser after install Bonjour");
}
/*
  演示ESP8266 mDNS 发现服务功能

  注意:
  - 输入你的 WiFi SSID 和 password.
  - 烧写到两块 ESP8266  板子 
*/

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>

const char* ssid     = "...";
const char* password = "...";
char hostString[16] = {0};

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\r\nsetup()");

  sprintf(hostString, "ESP_%06X", ESP.getChipId());
  Serial.print("Hostname: ");
  Serial.println(hostString);
  WiFi.hostname(hostString);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (!MDNS.begin(hostString)) {
    Serial.println("Error setting up MDNS responder!");
  }
  Serial.println("mDNS responder started");
  //往mDNS里面注册服务
  MDNS.addService("esp", "tcp", 8080); 

  Serial.println("Sending mDNS query");
  //查找服务
  int n = MDNS.queryService("esp", "tcp"); // Send out query for esp tcp services
  Serial.println("mDNS query done");
  if (n == 0) {
    Serial.println("no services found");
  } else {
    Serial.print(n);
    Serial.println(" service(s) found");
    for (int i = 0; i < n; ++i) {
      // 打印查找到的服务具体信息
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(MDNS.hostname(i));
      Serial.print(" (");
      Serial.print(MDNS.IP(i));
      Serial.print(":");
      Serial.print(MDNS.port(i));
      Serial.println(")");
    }
  }
  Serial.println();
  Serial.println("loop() next");
}

void loop() {
  // put your main code here, to run repeatedly:

五、SPIFFS—-ESP8266 SPIFFS文件系统

概述:

Arduino环境下的esp8266的flash存储分配:

  1. 代码区:程序存储区(包含当前代码区current Sketch、更新代码区OTA update)
  2. 文件系统:SPIFFS闪存文件系统(SPI Flash File System)可通过IDE配置,一般NodeMcu配置成3M```#include<FS.h>``

不支持目录 文件名32字符限制 建议保持短文件名

  1. EEPROM
  2. WIFI Config:设置WiFi模块配置时存储的数据

fGpEUP.png

  1. 文件操作
/**
 * 功能描述:spiffs文件操作常见方法使用,包括文件查找、创建、打开、关闭、删除
 */
#include <FS.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

#define myFileName  "mydemo.txt"

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
     return;
  }
  DebugPrintln("Start SPIFFS Done.");
  //判断文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
  
  File myFile;
  //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open(myFileName,"w+");
  //关闭文件
  myFile.close();
  //再次判断文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
  //删除文件
  DebugPrintln("mydemo.txt removing...");
  SPIFFS.remove(myFileName);
  //再次判断文件是否存在
  if(SPIFFS.exists(myFileName)){
    DebugPrintln("mydemo.txt exists.");
  }else{
    DebugPrintln("mydemo.txt not exists.");
  }
}

void loop(){
}
  1. 查看spiffs文件系统列表
/**
 * 功能描述:查看spiffs文件系统列表
 */
#include <FS.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open("/myDemo.txt","w+");
  //关闭文件
  myFile.close();
  
  //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open("/myDemo.jpg","w+");
  //关闭文件
  myFile.close();
  
    //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open("/myDemo.html","w+");
  //关闭文件
  myFile.close();
  
  Dir dir = SPIFFS.openDir("/");
  while(dir.next()){
    String fileName = dir.fileName();
  size_t fileSize = dir.fileSize();
  Serial.printf("FS File:%s,size:%d\n",fileName.c_str(),fileSize);
  }
  DebugPrintln("Setup Done!");
}

void loop(){
}
  1. 往文件myDemo.txt中写入一句话并读取出来显示
/**
 * 功能描述:演示文件读写功能
 */
#include <FS.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open("myDemo.txt","w+");
  if(myFile){
    DebugPrintln("Writing something to myDemo.txt...");
  myFile.println("单片机菜鸟博哥666");
  myFile.close();
  DebugPrintln("Writing Done.");
  }else{
    DebugPrintln("Open File Failed.");
  }
  
  //打开文件 可读
  myFile = SPIFFS.open("myDemo.txt","r");
  if(myFile){
    DebugPrintln("Reading myDemo.txt...");
  while(myFile.available()){
    //读取文件输出
    Serial.write(myFile.read());
  }
  myFile.close();
  }else{
    DebugPrintln("Open File Failed.");
  }
  
  DebugPrintln("Setup Done!");
}

void loop(){
}
  1. 烧写文件,将要存入SOIFFS区域的文件,提前放在代码目录的”data”目录中,使用ESP8266FS工具烧写(集成在ArduinoIDE中,但要安装这个工具)(提示没有找到这个工具的话,一般都是版本问题,换个版本就好了)
/**
 * 功能描述:演示上传文件并读取文件内容
 * 前提:需要先往SPIFFS里面上传config.txt文件 
 */
#include <FS.h>

//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)

void setup(){
  DebugBegin(9600);
  DebugPrintln("Check Start SPIFFS...");
  //启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
  if(!SPIFFS.begin()){
     DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
   return;
  }
  DebugPrintln("Start SPIFFS Done.");
  
  File myFile;
  //打开文件 不存在就创建一个 可读可写
  myFile = SPIFFS.open("/config.txt","r");
  if(myFile){
    //打印文件大小
    int size = myFile.size();
  Serial.printf("Size=%d\r\n", size);
  //读取文件内容
  DebugPrintln(myFile.readString());
  myFile.close();
  DebugPrintln("Reading Done.");
  }else{
    DebugPrintln("Open File Failed.");
  }
}

void loop(){
}

六、web配网

自定义AP配网

/**
 * 功能:AP配网(web配网)
 * 作者:单片机菜鸟哥
 * 时间:2020-02-15
 * 描述:
 *    1.设置固定IP 192.168.4.1
 *    2.设置webserver监听web请求
 *    3.处理web请求,获取ssid和pwd
 *    4.连接网络
 */

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

#define DEBUG

#ifdef DEBUG
  //以下三个定义为调试定义
  #define DebugBegin(baud_rate)    Serial.begin(baud_rate)
  #define DebugPrintln(message)    Serial.println(message)
  #define DebugPrint(message)    Serial.print(message)
#else
  //以下三个定义为调试定义
  #define DebugBegin(baud_rate)
  #define DebugPrintln(message)
  #define DebugPrint(message)
#endif  

const char* ap_ssid = "esp_webconfig";
const char* ap_password = "";//开放式网络

char sta_ssid[32] = {0};
char sta_password[64] = {0};

const char* webpage_html = "\
<!DOCTYPE html>\r\n\
<html lang='en'>\r\n\
<head>\r\n\
  <meta charset='UTF-8'>\r\n\
  <title>Document</title>\r\n\
</head>\r\n\
<body>\r\n\
  <form name='input' action='/' method='POST'>\r\n\
        wifi名称: <br>\r\n\
        <input type='text' name='ssid'><br>\r\n\
        wifi密码:<br>\r\n\
        <input type='text' name='password'><br>\r\n\
        <input type='submit' value='保存'>\r\n\
    </form>\r\n\
</body>\r\n\
</html>\r\n\
";

IPAddress local_IP(192,168,4,1);
IPAddress gateway(192,168,4,1);
IPAddress subnet(255,255,255,0);

void initApConfig();
void initWebServer();
void connectToWifi();
void handleRootPost();
void handleRoot();
void handleNotFound();

ESP8266WebServer server(80);

void setup(void) {
  DebugBegin(115200);
  DebugPrintln("");
  DebugPrint("connect ap: ");
  DebugPrintln(ap_ssid);

  initApConfig();

  DebugPrint("IP address: ");
  DebugPrintln(WiFi.softAPIP());

  initWebServer();

  DebugPrintln("HTTP server started");

  Serial.printf("Ready! Open http://%s in your browser\n",
  WiFi.softAPIP().toString().c_str());
}

void loop(void) {
  server.handleClient();
}

/**
 * 初始化AP配置
 */
void initApConfig(){
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(local_IP, gateway, subnet);
  WiFi.softAP(ap_ssid, ap_password);
}

/**
 * 初始化webserver配置
 */
void initWebServer(){
  server.on("/", HTTP_GET, handleRoot);
  server.on("/", HTTP_POST, handleRootPost);
  server.onNotFound(handleNotFound);

  server.begin();   
}

/**
 * 连接到WiFi
 */
void connectToWifi(){
  DebugPrintln("connectToWifi");
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(sta_ssid, sta_password);

  int cnt = 0;
  while (WiFi.status() != WL_CONNECTED) {
          delay(500);
          cnt++;
          Serial.print(".");
          if(cnt>=40){
            cnt = 0;
            //重启系统
            DebugPrintln("\r\nRestart now!");
            ESP.restart();
          }
  }
  DebugPrintln("connectToWifi Success!");
}

/**
 * 处理web post请求
 */
void handleRootPost() {
  DebugPrintln("handleRootPost");
  if (server.hasArg("ssid")) {
    DebugPrint("got ssid:");
    strcpy(sta_ssid, server.arg("ssid").c_str());
    DebugPrintln(sta_ssid);
  } else {
    DebugPrintln("error, not found ssid");
    server.send(200, "text/html", "<meta charset='UTF-8'>error, not found ssid");
    return;
  }

  if (server.hasArg("password")) {
    DebugPrint("got password:");
    strcpy(sta_password, server.arg("password").c_str());
    DebugPrintln(sta_password);
  } else {
    DebugPrintln("error, not found password");
    server.send(200, "text/html", "<meta charset='UTF-8'>error, not found password");
    return;
  }

  server.send(200, "text/html", "<meta charset='UTF-8'>保存成功");
  delay(2000);
  //连接wifi
  connectToWifi();
}

/**
 * 处理web get请求
 */
void handleRoot() {
  DebugPrintln("handleRoot");
  server.send(200, "text/html", webpage_html);
}

void handleNotFound() {
  String message = "File Not Found\n\n";
  server.send(404, "text/plain", message);
}

七、NTP—时间服务

NTP(Network Time Protocol)网络时间协议,基于UDP,用于网络时间同步的协议,使网络中的计算机时钟同步到UTC,再配合各个时区的偏移调整就能实现精准同步对时功能。

NTP报文协议

实现:

  1. 拼接协议
  2. 使用现成NTP第三方库(NTPClient)
#include <NTPClient.h>
// change next line to use with another board/shield
#include <ESP8266WiFi.h>
//#include <WiFi.h> // for WiFi shield
//#include <WiFi101.h> // for WiFi 101 shield or MKR1000
#include <WiFiUdp.h>

const char *ssid     = "XU-ChinaNet";
const char *password = "15358228063";

WiFiUDP ntpUDP;

// You can specify the time server pool and the offset (in seconds, can be
// changed later with setTimeOffset() ). Additionaly you can specify the
// update interval (in milliseconds, can be changed using setUpdateInterval() ).
NTPClient timeClient(ntpUDP, "ntp1.aliyun.com", 60*60*8, 30*60*1000);

void setup(){
  Serial.begin(115200);

  WiFi.begin(ssid, password);

  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }

  timeClient.begin();
}

void loop() {
  timeClient.update();

  Serial.println(timeClient.getFormattedTime());
  int hours = timeClient.getHours();
  int minu =  timeClient.getMinutes();
  int sece =  timeClient.getSeconds();
  Serial.printf("hour:%d minu:%d sece:%d\n", hours,minu,sece);
  delay(1000);
}

八、DNSServer—-真正的域名服务

建立DNS服务,使用时模块必须处于AP模式下;

真正意义上的精简版DNS服务器;

DNSServer运行于UDP服务;

这里DNS服务器唯一的作用是把域名转成对应映射地址(只支持一个)

fGI9sS.png

/**
 * 功能描述:在手机浏览器访问 "www.danpianji.com"会显示“Hello World” 
 */

#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);

void setup() {

  DebugBegin(115200);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("DNSServer example");

  // modify TTL associated  with the domain name (in seconds)
  // default is 60 seconds
  dnsServer.setTTL(300);
  // set which return code will be used for all other domains (e.g. sending
  // ServerFailure instead of NonExistentDomain will reduce number of queries
  // sent by clients)
  // default is DNSReplyCode::NonExistentDomain
  dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);

  // 启动DNS server,映射主机名为 www.danpianji.com
  bool status = dnsServer.start(DNS_PORT, "www.danpianji.com", apIP);

  if(status){
      DebugPrintln("start dnsserver success.");
  }else{
     DebugPrintln("start dnsserver failed.");
  }

  // simple HTTP server to see that DNS server is working
  webServer.onNotFound([]() {
    String message = "Hello World!\n\n";
    message += "URI: ";
    message += webServer.uri();

    webServer.send(200, "text/plain", message);
  });
  webServer.begin();
}

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

Portal认证

/**
 * 功能描述:portal认证
 */

#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;
ESP8266WebServer webServer(80);

String responseHTML = ""
                      "<!DOCTYPE html><html><head><title>CaptivePortal</title></head><body>"
                      "<h1>Hello World!</h1><p>This is a captive portal example. All requests will "
                      "be redirected here.</p></body></html>";

void setup() {
  DebugBegin(115200);
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  WiFi.softAP("DNSServer CaptivePortal example");

 
  // 所有请求都映射到一个具体地址
  dnsServer.start(DNS_PORT, "*", apIP);

  // replay to all requests with same HTML
  webServer.onNotFound([]() {
    DebugPrintln("webServer handle.");
    webServer.send(200, "text/html", responseHTML);
  });
  webServer.begin();
}

void loop() {
  dnsServer.processNextRequest();
  webServer.handleClient();
}

九、无线更新—–OTA固件更新

1、ArduinoOTA:

  • 连接WIFI;
  • 配置ArduinoOTA对象的事件函数;
  • 启动ArduinoOTA服务ArduinoOTA.begin();
  • 在loop()函数将处理权交由ArduinoOTA.handle()。

为了区分正常工作模式以及更新模式,可以设置标志位来区分(标志位可通过其他手段修改,如按键、软件控制)

void loop() {
  if (flag ==0 ) {
    // 正常工作状态的代码
  } else {
    ArduinoOTA.handle();
 }
}

fGXaO1.png

python2.7环境

旧代码:

/**
 * 功能描述:OTA之Arduino IDE更新 V1.0版本代码
 *
 */
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

#define CodeVersion "CodeVersion V1.0"

const char* ssid = "xxxx";//填上wifi账号
const char* password = "xxxxx";//填上wifi密码

void setup() {
  DebugBegin(115200);
  DebugPrintln("Booting Sketch....");
  DebugPrintln(CodeVersion);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    DebugPrintln("Connection Failed! Rebooting...");
    delay(5000);
  //重启ESP8266模块
    ESP.restart();
  }

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA.onStart([]() {
    String type;
  //判断一下OTA内容
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_SPIFFS
      type = "filesystem";
    }

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    DebugPrintln("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    DebugPrintln("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    DebugPrintF("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    DebugPrintF("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      DebugPrintln("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      DebugPrintln("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      DebugPrintln("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      DebugPrintln("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      DebugPrintln("End Failed");
    }
  });
  ArduinoOTA.begin();
  DebugPrintln("Ready");
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

烧写成功后,重启IDE(IDE与8266建立无线连接)端口项出现网络端口

新代码:

/**
 * 功能描述:OTA之Arduino IDE更新 V1.1版本代码
 *
 */
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

#define CodeVersion "CodeVersion V1.1"

const char* ssid = "xxxx";//填上wifi账号
const char* password = "xxxx";//填上wifi密码

void setup() {
  DebugBegin(115200);
  DebugPrintln("Booting Sketch....");
  DebugPrintln(CodeVersion);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    DebugPrintln("Connection Failed! Rebooting...");
    delay(5000);
  //重启ESP8266模块
    ESP.restart();
  }

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA.onStart([]() {
    String type;
  //判断一下OTA内容
    if (ArduinoOTA.getCommand() == U_FLASH) {
      type = "sketch";
    } else { // U_SPIFFS
      type = "filesystem";
    }

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    DebugPrintln("Start updating " + type);
  });
  ArduinoOTA.onEnd([]() {
    DebugPrintln("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    DebugPrintF("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    DebugPrintF("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) {
      DebugPrintln("Auth Failed");
    } else if (error == OTA_BEGIN_ERROR) {
      DebugPrintln("Begin Failed");
    } else if (error == OTA_CONNECT_ERROR) {
      DebugPrintln("Connect Failed");
    } else if (error == OTA_RECEIVE_ERROR) {
      DebugPrintln("Receive Failed");
    } else if (error == OTA_END_ERROR) {
      DebugPrintln("End Failed");
    }
  });
  ArduinoOTA.begin();
  DebugPrintln("Ready");
  DebugPrint("IP address: ");
  DebugPrintln(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

2、WebUpdateOTA:

fJ9d3T.png

  1. 系统自带OTA之WEB更新(通过建立 webserver来上传新固件以达到更新目的)

V1.1代码:

/*
 * 功能描述:OTA之web更新 V1.0版本代码
 */

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

#define CodeVersion "CodeVersion V1.0"

const char* host = "esp8266-webupdate";
const char* ssid = "xxx";//填上wifi账号
const char* password = "xxx";//填上wifi密码

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

void setup(void) {
  DebugBegin(115200);
  DebugPrintln("Booting Sketch...");
  DebugPrintln(CodeVersion);
  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);

  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
    DebugPrintln("WiFi failed, retrying.");
  }
  //启动mdns服务
  MDNS.begin(host);
  //配置webserver为更新server
  httpUpdater.setup(&httpServer);
  httpServer.begin();

  MDNS.addService("http", "tcp", 80);
  DebugPrintF("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", host);
}

void loop(void) {
  httpServer.handleClient();
  MDNS.update();
}

同一WIFI下,打开调试器中提示的网站

编译V1.1代码:

/*
 * 功能描述:OTA之web更新 V1.1版本代码
 */

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>

//调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
#define DebugPrintF(...) Serial.printf( __VA_ARGS__ )

#define CodeVersion "CodeVersion V1.1"

const char* host = "esp8266-webupdate";
const char* ssid = "xxx";//填上wifi账号
const char* password = "xxx";//填上wifi密码

ESP8266WebServer httpServer(80);
ESP8266HTTPUpdateServer httpUpdater;

void setup(void) {
  DebugBegin(115200);
  DebugPrintln("Booting Sketch...");
  DebugPrintln(CodeVersion);
  WiFi.mode(WIFI_AP_STA);
  WiFi.begin(ssid, password);

  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
    DebugPrintln("WiFi failed, retrying.");
  }
  //启动mdns服务
  MDNS.begin(host);
  //配置webserver为更新server
  httpUpdater.setup(&httpServer);
  httpServer.begin();

  MDNS.addService("http", "tcp", 80);
  DebugPrintF("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", host);
}

void loop(void) {
  httpServer.handleClient();
  MDNS.update();
}

选择bin文件,通过WEB网页上传

  1. 自定义OTA之WEB更新(个性化页面)

3.SerialUpdateOTA—OTA之服务器更新

ftmccj.png

放在服务器上,无感知更新

十、WebSocket Client—-全双工通信

问题:HTTP:一个请求-响应应用层协议

客户端没有主动发请求,服务器不能主动给客户端发数据

方法:

  1. 轮询(浪费带宽资源)
  2. 应用层协议解决MQTT协议
  3. WebSocket协议:让客户端与服务器之间建立无限制的全双工通信,任何一方都可主动发消息给对方(HTML5定义的协议,更好的节省服务器资源和带宽,并实时进行通讯)

两阶段:

  1. 通过HTTP请求确认WebSocket握手协议阶段;

  2. 通过WebSocket交互数据阶段。


文章作者: 旧时南风
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 旧时南风 !
评论
  目录