1. 程式人生 > >解析-ESP01模塊開發Arduino物聯網wifi開關模塊

解析-ESP01模塊開發Arduino物聯網wifi開關模塊

tin 掛載 開始 oop 是否 lin download 新建 gin

本文將解析《完美圖解物聯網Iot實操 ESP8266》中 第五章 P177頁 動手做的代碼2(使用SPIFFS文件系統的代碼)

首先我們先動手使用Arduino IDE編譯並且上傳代碼,上傳後記得使用工具中ESP8266 SPIFFS上傳工具上傳SPIFFS文件夾的內容,否則應用將無法使用。你很有可能看到下面這樣的畫面。

技術分享圖片FileNotFind 默認的404頁面

至於為什麽後面會詳細說明,如果上傳SPIFFS文件夾上傳成功,訪問你的ESP8266的IP看到的應該是這樣的畫面。

技術分享圖片正常頁面

下面我們開始解析一下代碼吧,這裏首先假定大家已經有開發過Arduino 應用的基礎。

完整代碼

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

const byte LED_PIN = 2;
const byte PWM_PIN = 0;

const char ssid[] = "TP-LINK_B5672C";
const char pass[] = "19970929";
const char* host = "jarvis";

ESP8266WebServer server(80);

// 定義處理首頁請求的自定義函數
String getContentType(String filename){
  if(server.hasArg("download")) return "application/octet-stream";
  else 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";
}

bool handleFileRead(String path){
  Serial.println("handleFileRead: " + path);
  if(path.endsWith("/")) path += "index.htm";
  String contentType = getContentType(path);
  String pathWithGz = path + ".gz";
  if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
    if(SPIFFS.exists(pathWithGz))
      path += ".gz";
    File file = SPIFFS.open(path, "r");
    size_t sent = server.streamFile(file, contentType);
    file.close();
    return true;
  }
  return false;
}

void setup( ){
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);

  SPIFFS.begin();  // 啟用SPIFFS文件系統

  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  IPAddress ip = WiFi.localIP();
  
  if (!MDNS.begin(host, ip)) {
    Serial.println("Error setting up MDNS responder!");
    while(1) { 
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.print("Connected! IP address: ");
  Serial.println(WiFi.localIP());

  server.on ("/sw", []() {
     String state = server.arg("led");
     if (state == "ON") {
         digitalWrite(LED_PIN, HIGH);
     } else if (state == "OFF") {
         digitalWrite(LED_PIN, LOW);
     }
     
     Serial.print("LED_PIN: ");
     Serial.println(state);
  });
  
  server.on ("/pwm", []() {
     String pwm = server.arg("led");
     int val = pwm.toInt();
     analogWrite(PWM_PIN, val);
     Serial.print("PWM: ");
     Serial.println(val);
  });

  // 處理根路徑以及「不存在的」路徑
  server.onNotFound([](){
    if(!handleFileRead(server.uri()))
      server.send(404, "text/plain", "FileNotFound");
  });

  server.begin();
  Serial.println("HTTP server started");

  MDNS.setInstanceName("Cubie‘s ESP8266");
  MDNS.addService("http", "tcp", 80);
}
 
void loop( ){
  server.handleClient();
}

頭文件

可以看到我們這裏引用的頭文件分別是

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

下面分別說說看不同頭文件的用處

ESP8266WIFI.h

這個頭文件是提供ESP8266 WIFI功能的頭文件,只有這個頭文件在才能使用ESP8266的WIFI功能,否則無法使用WIFI(相當於把ESP8266當做沒有wifi功能的atmega328p用一樣)。

所有的Wifi對象定義在這個頭文件裏面。

ESP8266WebServer.h

這個頭文件是提供ESP8266 Web服務器的功能,類似於Windows上運行的Web服務器一樣。Web服務器是提供Web服務的基礎,像是你正在閱讀的這個博客一樣,這個博客的Web服務器就是一個CentOS系統中使用Apache功能提供的(指的是myblogmaoweicao.club這個域名的博客)。博客園也有自己的Web服務器。

有了Web服務器才能使用通過一些協議定義的Web功能。

ESP8266mDNS.h

這個頭文件是提供一個微型的DNS解析服務器的功能,就像正常的DNS服務器一樣,但是這個功能可能只有提供 域名-IP綁定 的服務(也就是正常DNS服務的a類)。域名的概念類似於酒店裏面給某個編號的房間起名字一眼,比如給化工樓313起名叫學生辦公室,那麽如果你到化工路的3樓,跟別人打聽學生辦公室是哪裏,別人就會告訴你是313,這樣你就能找到對應的房間了。

mDNS功能會將ESP8266本身設置成一個簡單的DNS服務器,單訪問特定的域名,例如:jarvis.local時,ESP8266就會將地址解析成正確的IP地址(例如自己的IP地址),在變化的環境下使用DNS服務其實更好。就類似於百度在很多地方都部署了自己的CDN(這是另外一種網絡服務,用於分攤請求),但是每個CDN都有自己的IP地址,福州的CDN的IP地址能叫1.1.1.1,而百度的服務器其實叫114.114.114.114(這個不是真實的IP地址,舉得例子都是DNS服務器),連接福州CDN的延時可能只要20ms,但是連接百度自己的服務器的延時藥400ms,這個讓人難以忍受了。但是如果有建立DNS的話,在福州這裏訪問www.baidu.com,DNS服務器就會自動將網址解析到延時最小的CDN服務器,也就是1.1.1.1上這樣就保證了延時最小。當然這樣做也會發生事故,就是如果沒有建立正確的鏈接,那麽你訪問www.baidu.com可能就得不到在正確的頁面。

FS.h

這個頭文件是提供文件系統的相關功能,在嵌入式開發板上每個板子的文件系統都不太一樣,但是通過上層抽象就有一致的接口可以訪問了。

只有這個頭文件被引用,才能使用SPIFFS對象,也就是ESP01上那唯一的一塊硬盤。

常量(const修飾的變量)

常量 的定義 是C++特有的概念之一,由const(不變的)修飾符修飾,被const修飾的變量無法被外界再次改變。

具體的大家看C++的圖書就好了,const修飾符和Java中的final修飾符(在Java中使用final(最終的)修飾一個變量來作為常量)很像。

在這個代碼中,修飾的常量有五個。

LED_PIN

連接了LED的引腳,這裏是指定的是ESP01上唯二引出的引腳GPIO 0。

PWM_PIN

這裏制定了 PWM(載波調制)的引腳,用於輸出某種模擬量(用數字信號模擬),這裏指定的ESP01上唯二引出的引腳GPIO 2。

SSID

SSID是WIFI特有的概念之一,也就是路由器的“名字”,這裏需要由程序指定鏈接哪個wifi。(除非你對其進行修改)

PASS

PASS對應的是Password(密碼),這裏需要輸入的就是你要鏈接的路由器的Wifi密碼,只有給出正確的密碼才能連入正確的網絡。

HOST

host是主機名的意思,用於指定這塊ESP01的域名,這裏我們指定的名字叫做“jarvis”

對象

ESP8266WebServer server(端口號)

這一句就是定義個ESP8266的WebServer對象叫做“server”,然後服務的端口號為80(也就是一般HTTP服務的端口號)。

這是個網站服務器,所以提供了一些網站服務的方法。

(HTTP對象).on(“服務器的連接”,<處理函數>)方法(在C++裏面叫做函數,在Java裏面叫方法,其實是一個東西)定義了一個服務器可以對哪些鏈接提供哪些獨一無二的服務。

(HTTP對象).onNotFound(<處理函數>)方法定義了一個服務器在處理沒有被on指定的服務之外的對象應該這麽做。

(HTTP對象).uri()方法是返回服務器接收到的請求的URL連接,像是訪問
“www.baidu.com/1.png”(假設有這個路徑,實際上沒有),那麽這個函數會返回“1.png”這個就是URL。(URL具體定義參考:https://baike.baidu.com/item/url/110640?fr=aladdin)

(HTTP對象).handClient()就是處理客戶端的請求,每個服務器都要處理對應的客戶端的請求。

Serial 串口對象

Arduino內建的對象,描述的單片機經常用的串口功能。具體參考:https://www.arduino.cc/reference/en/language/functions/communication/serial/

SPIFFS SPI文件系統對象

文件系統就是我們常說的硬盤上用的那種文件系統,操作系統只有跟文件系統打交道才能跟硬盤要交流。在PC機上的Windows系統上常見的是FAT32(以前挺多的,現在少見)、NTFS(多見,目前Windows默認格式化磁盤的格式就是NTFS)、EXFAT(擴展的FAT32,支持4GB以上的對象)等。

在單片機上由於性能限制,所以只能使用一些比較簡單的文件系統,像這裏用的就是SPIFFS。

SPIFFS.begin()方法,用於掛載SPIFFS的文件系統,使用前必須需要begin方法進行初始化。一般操作系統在加載的時候也都初始化了。

一般還要搭配SPIFFS.format()來進行格式化,但是這裏沒有,因為ESP8266都安排好了。所以不用format了。

SPIFFS.open(路徑, 模式)類似於C語言裏面的fopen函數用來打開一個文件,返回一個File對象。
SPIFFS.exists(路徑) 用來檢測路徑上的文件是否存在。

這裏就是用來ESP8266的這個磁盤到底有沒有這個文件用的。一般都要判斷是否存在。

當然在這裏如果你想要上傳一些東西,需要在Arduino工程所在的文件夾裏面在新建一個data文件夾用於上傳需要的東西不過在Arduino上傳你還需要安裝一個另外的工具。

IPAddress IPv4地址對象(4字節)

這是一個在ESP8266裏面內部定義的一個IP地址對象,這是IP v4的對象,所以是4個字節。每一個字節表示一個範圍。具體參考:《計算機網絡》

MDNS 組播DNS服務器對象

用於定義一個小小的DNS服務器,來完成域名到IP+端口號的轉換任務。

一般是由公網服務商提供。像是1.1.1.1就是個DNS服務器。

這裏是定義個組播DNS服務器,用於局域網內沒有DNS服務器的情況下進行DNS的服務。

MDNS.begin(主機名,IP地址)初始化MDNS服務器。

MDS.setInstanceName(“別名”)這個就是設置MDNS服務器的別名。

MDNS.addService(“http”, “tcp”, 80) 添加服務,為特定的類型添加服務器,這裏為http協議設置成TCP格式的內容並且添加了對應服務的端口號為80

特殊語法

在這個例子裏面你們可能看到跟平時都看不到的語法也就是 [](){} 這種格式其實是C++ 11裏面新增的語法叫做 lambda表達式。用於代替一些需要使用匿名函數的場合。(匿名函數,即沒有名字的函數,程序員有時候覺得有些時候為一些簡單功能的函數起個名字太麻煩了,所以就有了匿名函數的功能)

具體的格式是:

[捕獲列表](形式參數列表) 返回類型 {函數體}

與正常的函數相同他也有自己的形參和返回類型,這的返回類型是可選項。

捕獲列表 是一個特殊的函數,僅在函數體內可用。一般在全局環境下不可用。(因為編譯器不知道你要捕獲什麽玩意),捕獲列表可以寫一些變量或者對象的名字,這樣你就能在lambda表達式內部使用這些東西而不用進行參數傳遞。(也就是對象地址傳遞),具體的參考《C++ Primer》裏面有詳細介紹(可以買第五版的中文版,輪子哥校對的【逼乎上一個老司機,在微軟工作,真名叫 陳梓瀚】)

結束

下面上傳代碼,並且上傳SPIFFS文件系統裏面的東西,那麽你在對應的IP地址應該可以看到這樣的東西。

技術分享圖片

解析-ESP01模塊開發Arduino物聯網wifi開關模塊