上次使用藍牙無線控制電子裝置,但藍牙仍然只能作有限距離的無線控制
如果可以使用網絡,接通網絡後,即使用在地球另一邊都能夠遙距控制
如果可以使用網絡,接通網絡後,即使用在地球另一邊都能夠遙距控制
連接 WiFi
ESP8266 NodeMCU 本身已經內置 WiFi 模組,因此具備 WiFi 功能
而且安裝 ESP8266 NodeMCU 開發模組時,亦包含很多 ESP8266函式庫
而且安裝 ESP8266 NodeMCU 開發模組時,亦包含很多 ESP8266函式庫
編寫 WiFi 連接程式
#include <ESP8266WebServer.h> void setup() { Serial.begin(9600); Serial.println("Connecting WiFi..."); WiFi.begin("SSID", "password"); // replace a valid SSID and password while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.println("WiFi connected"); Serial.print("IP: "); Serial.println(WiFi.localIP()); } void loop() { delay(1); }
ESP8266 NodeMCU 連接 WiFi ,會獲派 IP地址
建立 HTTP伺服器
連接 WiFi 的測試成功後,可以編寫 HTTP伺服器 程式
#include <ESP8266WebServer.h> const byte LED_WIFI = 2; const byte LED_BOARD = 16; ESP8266WebServer server = ESP8266WebServer(80); const char* html PROGMEM = R"EOF(<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>ESP8266 LED Control</title> </head> <body> <div><a href="/led-wifi">LED WiFi</a></div> <div><a href="/led-board">LED Board</a></div> </body> </html>)EOF"; void setup() { Serial.begin(9600); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, HIGH); pinMode(LED_BOARD, OUTPUT); digitalWrite(LED_BOARD, HIGH); Serial.println("Connecting WiFi..."); WiFi.begin("SSID", "password"); // replace a valid SSID and password while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.println("WiFi connected"); Serial.print("Visit: http://"); Serial.println(WiFi.localIP()); server.on("/", [] { server.send(200, "text/html", html); }); server.on("/led-wifi", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_WIFI, HIGH); } else if (value == "1") { digitalWrite(LED_WIFI, LOW); } else { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); } server.sendHeader("Location", "/"); server.send(302); }); server.on("/led-board", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_BOARD, HIGH); } else if (value == "1") { digitalWrite(LED_BOARD, LOW); } else { digitalWrite(LED_BOARD, !digitalRead(LED_BOARD)); } server.sendHeader("Location", "/"); server.send(302); }); server.begin(); } void loop() { server.handleClient(); }
連接 WiFi 後,便會建立 HTTP伺服器 ,可以在網頁瀏覽器載入該 IP地址
網頁內容
在下只是使用很基本的 HTML元素 設計
按下連結,可以控制對應電子裝置
按下連結,可以控制對應電子裝置
既然可以使用網頁控制,即是可以使用 Curl 等指令,發送 HTTP Request 控制
可以使用指令,亦表示可以自行製作自動化操作
可以使用指令,亦表示可以自行製作自動化操作
使用 WiFiManager 連接 WiFi
將 SSID 及 密碼 編寫在程式,顯然有安全隱患
可以使用 WiFiManager函式庫 讓 ESP8266 NodeMCU 成為 存取點(Access Point)
先加入到 ESP8266 NodeMCU 的 存取點,並以互動方式控制 ESP8266 NodeMCU 選擇 SSID 及 輸入密碼 來連接 WiFi
可以使用 WiFiManager函式庫 讓 ESP8266 NodeMCU 成為 存取點(Access Point)
先加入到 ESP8266 NodeMCU 的 存取點,並以互動方式控制 ESP8266 NodeMCU 選擇 SSID 及 輸入密碼 來連接 WiFi
到 Sketch > Include Library > Manager Libraries... 搜找 WiFiManager
由於有很多相似名稱及功能的函式庫,但文中所使用的是由 Tzapu Tablatronix 所製作的 WiFiManager
由於有很多相似名稱及功能的函式庫,但文中所使用的是由 Tzapu Tablatronix 所製作的 WiFiManager
#include <WiFiManager.h> #include <ESP8266WebServer.h> const byte LED_WIFI = 2; const byte LED_BOARD = 16; ESP8266WebServer server = ESP8266WebServer(80); const char* html PROGMEM = R"EOF(<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>ESP8266 LED Control</title> </head> <body> <div><a href="/led-wifi">LED WiFi</a></div> <div><a href="/led-board">LED Board</a></div> </body> </html>)EOF"; void setup() { Serial.begin(9600); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, HIGH); pinMode(LED_BOARD, OUTPUT); digitalWrite(LED_BOARD, HIGH); WiFiManager wfm = WiFiManager(); wfm.resetSettings(); wfm.autoConnect("SSID", "password"); // replace your SSID and password for ESP8826 Web Server Portal server.on("/", [] { server.send(200, "text/html", html); }); server.on("/led-wifi", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_WIFI, HIGH); } else if (value == "1") { digitalWrite(LED_WIFI, LOW); } else { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); } server.sendHeader("Location", "/"); server.send(302); }); server.on("/led-board", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_BOARD, HIGH); } else if (value == "1") { digitalWrite(LED_BOARD, LOW); } else { digitalWrite(LED_BOARD, !digitalRead(LED_BOARD)); } server.sendHeader("Location", "/"); server.send(302); }); server.begin(); } void loop() { server.handleClient(); }
在 WiFi 中可以尋找到 ESP8266 Web Server Portal 的 WiFi 訊號
輸入設定的密碼加入到存取點
加入到 ESP8266 Web Server Portal 在 Arduino IDE 的 Serial Monitor 會顯示 預設閘道(Default Gateway) 的 IP地址
在下多次測試都是 192.168.4.1
注意:由於加入到 Portal 的裝置會暫時無法連接網絡,如果當時正在下載檔案便不要測試
在下多次測試都是 192.168.4.1
注意:由於加入到 Portal 的裝置會暫時無法連接網絡,如果當時正在下載檔案便不要測試
使用網頁瀏覽器便可以顯示 WiFiManager 的設定頁面
Exit 就是離開操作,在下並不在此測試
Exit 就是離開操作,在下並不在此測試
Update頁面 可以將 WiFiManager 升級,但在下暫時沒有使用此方法升級
Info頁面 顯示 ESP8266 NodeMCU 當前的資料及狀態
Configure WiFi頁面 控制 ESP8266 Web Server Portal 連接其他 WiFi訊號
選擇 SSID 及 輸入密碼後,按 Save按鈕 便設定完成
選擇 SSID 及 輸入密碼後,按 Save按鈕 便設定完成
連接後,在 Arduino IDE 的 Serial Monitor 會顯示已經連接的 SSID 及會顯示 ESP8266 Web Server Portal 連接 WiFi 後的 IP地址
結果與直接寫 SSID 及 密碼 相同
使用 MDNS 設定主機名
多重廣播DNS (Multicast DNS (MDNS)) 可以讓 ESP8266 NodeMCU HTTP伺服器 設定主機名,方便載入頁面
即使加入到不同的網絡,都可以使用相同的主機名來存取頁面,不需要記下複鎖的 IP地址
即使加入到不同的網絡,都可以使用相同的主機名來存取頁面,不需要記下複鎖的 IP地址
#include <WiFiManager.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> const byte LED_WIFI = 2; const byte LED_BOARD = 16; ESP8266WebServer server = ESP8266WebServer(80); const char* html PROGMEM = R"EOF(<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>ESP8266 LED Control</title> </head> <body> <div><a href="/led-wifi">LED WiFi</a></div> <div><a href="/led-board">LED Board</a></div> </body> </html>)EOF"; void setup() { Serial.begin(9600); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, HIGH); pinMode(LED_BOARD, OUTPUT); digitalWrite(LED_BOARD, HIGH); WiFiManager wfm = WiFiManager(); wfm.resetSettings(); wfm.autoConnect("SSID", "password"); // replace your SSID and password for ESP8826 Web Server Portal MDNS.begin("hostname"); // replace your hostname for ESP8826 Web Server Portal server.on("/", [] { server.send(200, "text/html", html); }); server.on("/led-wifi", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_WIFI, HIGH); } else if (value == "1") { digitalWrite(LED_WIFI, LOW); } else { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); } server.sendHeader("Location", "/"); server.send(302); }); server.on("/led-board", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_BOARD, HIGH); } else if (value == "1") { digitalWrite(LED_BOARD, LOW); } else { digitalWrite(LED_BOARD, !digitalRead(LED_BOARD)); } server.sendHeader("Location", "/"); server.send(302); }); server.begin(); } void loop() { server.handleClient(); MDNS.update(); }
MDNS運作後,可以使用
確認主機名能否連接
ping hostname.local
確認主機名能否連接
可以使用主機名連接頁面
使用 SSL
如果只是使用 HTTP ,資料並沒有加密保障,資料很容易被竊取
ESP8266 提供 ESP8266WebServerSecure 以建立具備 SSL 的 HTTP伺服器
ESP8266 提供 ESP8266WebServerSecure 以建立具備 SSL 的 HTTP伺服器
#include <WiFiManager.h> #include <ESP8266WebServerSecure.h> #include <ESP8266mDNS.h> const byte LED_WIFI = 2; const byte LED_BOARD = 16; ESP8266WebServerSecure server = ESP8266WebServerSecure(443); const char* html PROGMEM = R"EOF(<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>ESP8266 LED Control</title> </head> <body> <div><a href="/led-wifi">LED WiFi</a></div> <div><a href="/led-board">LED Board</a></div> </body> </html>)EOF"; const char* publicCert PROGMEM = R"EOF(-----BEGIN CERTIFICATE----- paste your public cert here -----END CERTIFICATE-----)EOF"; const char* privateKey PROGMEM = R"EOF(-----BEGIN PRIVATE KEY----- paste your private key here -----END PRIVATE KEY-----)EOF"; void setup() { Serial.begin(9600); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, HIGH); pinMode(LED_BOARD, OUTPUT); digitalWrite(LED_BOARD, HIGH); WiFiManager wfm = WiFiManager(); wfm.resetSettings(); wfm.autoConnect("SSID", "password"); // replace your SSID and password for ESP8826 Web Server Portal MDNS.begin("hostname"); // replace your hostname for ESP8826 Web Server Portal server.getServer().setRSACert(new X509List(publicCert), new PrivateKey(privateKey)); server.on("/", [] { server.send(200, "text/html", html); }); server.on("/led-wifi", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_WIFI, HIGH); } else if (value == "1") { digitalWrite(LED_WIFI, LOW); } else { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); } server.sendHeader("Location", "/"); server.send(302); }); server.on("/led-board", [] { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_BOARD, HIGH); } else if (value == "1") { digitalWrite(LED_BOARD, LOW); } else { digitalWrite(LED_BOARD, !digitalRead(LED_BOARD)); } server.sendHeader("Location", "/"); server.send(302); }); server.begin(); } void loop() { server.handleClient(); MDNS.update(); }
要以 SSL 建立 HTTP侵服器 需要建立 憑證及私鑰 ,在 Terminal 輸入
執行時,要填寫的資料全部可以留空白
執行後,會建立 public.crt 憑證 及 private.key 私鑰
ESP8266WebServerSecure 最多支援 RSA 2048 ,如果使用 RSA 4096 則無法使用
openssl req -x509 -nodes -newkey rsa:2048 -keyout "private.key" -out "public.crt"
執行時,要填寫的資料全部可以留空白
執行後,會建立 public.crt 憑證 及 private.key 私鑰
ESP8266WebServerSecure 最多支援 RSA 2048 ,如果使用 RSA 4096 則無法使用
使用 cat 指令 (或其他純文字軟件) 開啟及將內容複製
如果不想太複雜,可以開啟範本 File > Examples > ESP8266WebServer > HelloServerBearSSL
將 serverCert 及 serverKey 複製到閣下的 ESP8266 HTTP伺服器 使用亦可以
但效慮到安全性問題,最好還是閣下自行建立 憑證及私鑰
將 serverCert 及 serverKey 複製到閣下的 ESP8266 HTTP伺服器 使用亦可以
但效慮到安全性問題,最好還是閣下自行建立 憑證及私鑰
由於憑證及私鑰並非由認證的網絡安全組織發佈,因此網頁瀏覽器認為是不安全憑證
按 接受風險並繼續 即可 (不同網頁瀏覽器操作會略有不同)
按 接受風險並繼續 即可 (不同網頁瀏覽器操作會略有不同)
進入頁面,與先前的頁面其實相同
但由於受 SSL 保護,內容可以降低被中途竊取的風險
但由於受 SSL 保護,內容可以降低被中途竊取的風險
同樣由於使用自行建立 憑證及私鑰, Curl 無法通過安全認證
使用 Curl 時,需要附加 --insecure 才能執行
(!! (兩個感嘆號) 是重覆上次指令的指令)
使用 Curl 時,需要附加 --insecure 才能執行
(!! (兩個感嘆號) 是重覆上次指令的指令)
使用登入認證
雖然使用 SSL 可以降低資料被竊取的風險,但操作不需要安全認證便可以執行
如果操作連結洩漏,即使使用 SSL 都無法得到保護
如果操作連結洩漏,即使使用 SSL 都無法得到保護
#include <WiFiManager.h> #include <ESP8266WebServerSecure.h> #include <ESP8266mDNS.h> const byte LED_WIFI = 2; const byte LED_BOARD = 16; ESP8266WebServerSecure server = ESP8266WebServerSecure(443); const char* html PROGMEM = R"EOF(<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>ESP8266 LED Control</title> </head> <body> <div><a href="/led-wifi">LED WiFi</a></div> <div><a href="/led-board">LED Board</a></div> </body> </html>)EOF"; const char* publicCert PROGMEM = R"EOF(-----BEGIN CERTIFICATE----- paste your public cert here -----END CERTIFICATE-----)EOF"; const char* privateKey PROGMEM = R"EOF(-----BEGIN PRIVATE KEY----- paste your private key here -----END PRIVATE KEY-----)EOF"; const char* username = "username"; const char* password = "password"; const char* failMessage = "401 Unauthorized"; // replace your unauthorzied message void setup() { Serial.begin(9600); pinMode(LED_WIFI, OUTPUT); digitalWrite(LED_WIFI, HIGH); pinMode(LED_BOARD, OUTPUT); digitalWrite(LED_BOARD, HIGH); WiFiManager wfm = WiFiManager(); wfm.resetSettings(); wfm.autoConnect("SSID", "password"); // replace your SSID and password for ESP8826 Web Server Portal MDNS.begin("hostname"); // replace your hostname for ESP8826 Web Server Portal server.getServer().setRSACert(new X509List(publicCert), new PrivateKey(privateKey)); server.on("/", [] { server.send(200, "text/html", html); }); server.on("/led-wifi", [] { if (server.authenticate(username, password)) { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_WIFI, HIGH); } else if (value == "1") { digitalWrite(LED_WIFI, LOW); } else { digitalWrite(LED_WIFI, !digitalRead(LED_WIFI)); } server.sendHeader("Location", "/"); server.send(302); } else { return server.requestAuthentication(BASIC_AUTH, NULL, failMessage); } }); server.on("/led-board", [] { if (server.authenticate(username, password)) { String value = server.arg("value"); if (value == "0") { digitalWrite(LED_BOARD, HIGH); } else if (value == "1") { digitalWrite(LED_BOARD, LOW); } else { digitalWrite(LED_BOARD, !digitalRead(LED_BOARD)); } server.sendHeader("Location", "/"); server.send(302); } else { return server.requestAuthentication(BASIC_AUTH, NULL, failMessage); } }); server.begin(); } void loop() { server.handleClient(); MDNS.update(); }
載入操作頁面時,會顯示 基本認證 (Basic Authorization) 程序
如果取消或登入錯誤,會顯示登入錯誤的訊息
如果登入正確,便會執行操作
如果使用 Curl 操作,由於需要登入認證才能執行,未經登入認證會出錯
可以使用 -u username:password 的參數登入認證,例如:
如果使用傳統 HTTP請求,則要使用 基本認證標頭 ,即是 Authorization: Basic base64("username:password")
當然還可以使用 URL 語法
雖然這種語法最方便,但亦是最不安全,部分網頁瀏覽器可能會保存登入資料,因此要小心使用
測試時最好在受信任的網絡下使用 無痕瀏覽 (Incognito Mode 或 Private Browsing 或 InPrivate)
避免明文密碼保存在網頁瀏覽器的歷史紀錄中
可以使用 -u username:password 的參數登入認證,例如:
curl -u "username:password" "http://hostname.local/led-wifi" --insecure
如果使用傳統 HTTP請求,則要使用 基本認證標頭 ,即是 Authorization: Basic base64("username:password")
curl --header "Authorization: Basic "`printf "username:password" | base64` "http://hostname.local/led-wifi" --insecure
當然還可以使用 URL 語法
curl "http://username:password@hostname.local/led-wifi" --insecure
雖然這種語法最方便,但亦是最不安全,部分網頁瀏覽器可能會保存登入資料,因此要小心使用
測試時最好在受信任的網絡下使用 無痕瀏覽 (Incognito Mode 或 Private Browsing 或 InPrivate)
避免明文密碼保存在網頁瀏覽器的歷史紀錄中
補充資料
server.on() 第二個參數是指派 callback function
在下編寫的
可以改寫成
如果 server.on() 需要多次使用相同的 callback function,可以將參數獨立成一個 void function
可以省卻程式碼,亦方便修改內容
在下編寫的
server.on("/", [] { server.send(200, "text/html", html); });
可以改寫成
void htmlIndex() { server.send(200, "text/html", html); } server.on("/", htmlIndex);
如果 server.on() 需要多次使用相同的 callback function,可以將參數獨立成一個 void function
可以省卻程式碼,亦方便修改內容
總結
最初只是使用 ESP8266 NodeMCU 的內置 WiFi 功能,製作一個可以透過網頁遙距控制電子裝置
因此查看製作及建立 ESP8266 的 HTTP伺服器 後便完成測試,但發現越多越多需要的功能可以添加,因此增加了製作的時間
因此查看製作及建立 ESP8266 的 HTTP伺服器 後便完成測試,但發現越多越多需要的功能可以添加,因此增加了製作的時間
要將 SSID 及 密碼 直接寫在程式碼,覺得有點危險,尤其實要製作教學時,擔心不慎將重要的資料洩泄
因此尋找到使用 WiFiManager 可以建立 存取點 ,便不需要將 SSID 及 密碼 編寫在代碼中
每次都要查看 Arduino IDE 的 Serial Monitor 確認 ESP8266 的 IP地址,才能進入 HTTP伺服器
而且進入其他 WiFi 時的 IP地址 很大機會不相同,又要查看 Serial Monitor ,非常麻煩
因此尋找到使用 WiFiManager 可以建立 存取點 ,便不需要將 SSID 及 密碼 編寫在代碼中
每次都要查看 Arduino IDE 的 Serial Monitor 確認 ESP8266 的 IP地址,才能進入 HTTP伺服器
而且進入其他 WiFi 時的 IP地址 很大機會不相同,又要查看 Serial Monitor ,非常麻煩
因此嘗試設定 ESP8266 的 主機名,最初在網上查看需要使用 WiFi.hostname("hostname") 但都無法達到效果
使用 MDNS 都無法使用,原來需要在 void loop 中加入 MDNS.update() 才能保持 MDNS 運作
使用 MDNS 都無法使用,原來需要在 void loop 中加入 MDNS.update() 才能保持 MDNS 運作
最後使用具備 SSL 的 HTTP伺服器 及 設定登入認證 提升安全性
ESP8266 NodeMCU 還提供 FLASH按鈕,可以讓 ESP8266 Web Server 以 on demand 的方式起動,而不需要額外配件
如果閣下預計會以 Rest Server 或 API 方式執行操作,其實可以不需要自動返回主頁,簡化設計
測試登入認證時最好使用 無痕模式 (Incognito Mode)
避免登入後要關閉瀏覽器才能登出,影響閣下瀏覽網頁的體驗
避免登入後要關閉瀏覽器才能登出,影響閣下瀏覽網頁的體驗
使用 SSL 建立的 HTTP伺服器,執行操作的回應時間比較慢
而且回應速度並不固定,3秒至30秒不等
而且回應速度並不固定,3秒至30秒不等
沒有留言 :
張貼留言