上一章我們已經讓 ESP32 成功連上 Wi-Fi,並學會取得 IP Address、RSSI、MAC Address,以及 Wi-Fi 自動重連。
從本章開始,我們要真正發揮 ESP32 網路功能的價值:
直接把 ESP32 變成一台 Web Server。
完成之後,不需要另外安裝手機 App,也不需要 Windows 軟體,只要讓:
- ESP32
- 手機
- 平板
- Notebook
- 桌上型電腦
連接到同一個區域網路,就能直接使用瀏覽器控制 ESP32 GPIO。
本章將完成一個完整的:
ESP32 手機 Web 控制器
手機瀏覽器可以直接執行:
- LED ON
- LED OFF
- Relay ON
- Relay OFF
- 顯示 LED 目前狀態
- 顯示 Relay 目前狀態
這也是 ESP32 從「單機微控制器」正式進入:
IoT、智慧家庭、遠端控制與 Web HMI
的重要一章。
一、ESP32 Web Server 是什麼?
一般我們在瀏覽器輸入:
一般情況下,在瀏覽器輸入網站的 HTTPS 網址,瀏覽器就會連線到對應的網站伺服器。
瀏覽器會連線到網站伺服器。
伺服器收到 HTTP Request 後,再把:
HTML
CSS
JavaScript
圖片
資料
傳回瀏覽器。
ESP32 雖然是一顆微控制器,但它也可以執行相同概念。
架構如下:
手機瀏覽器
│
│ HTTP
▼
Wi-Fi Router
│
│ 區域網路
▼
ESP32 Web Server
│
├── GPIO
├── LED
└── Relay
例如 ESP32 的 IP Address 是:
192.168.1.105
手機瀏覽器輸入:
http://192.168.1.105
就能直接開啟 ESP32 所產生的控制網頁。
二、什麼是 HTTP?
HTTP 全名:
HyperText Transfer Protocol
中文通常稱為:
超文字傳輸協定。
瀏覽器與 Web Server 之間,就是利用 HTTP 交換資料。
例如手機輸入:
http://192.168.1.105/
瀏覽器實際上會向 ESP32 發出 HTTP Request。
概念可以理解成:
手機
│
│ GET /
▼
ESP32
│
│ HTML
▼
手機瀏覽器
ESP32 收到:
GET /
之後,回傳 HTML 網頁。
三、什麼是 GET?
HTTP 有很多 Request Method。
最常見的包括:
- GET
- POST
- PUT
- DELETE
本章先使用最容易理解的:
GET
例如我們設計以下網址:
/
代表首頁。
LED ON:
/led/on
LED OFF:
/led/off
Relay ON:
/relay/on
Relay OFF:
/relay/off
因此手機按下:
LED ON
按鈕時,瀏覽器可以送出:
GET /led/on
ESP32 收到之後執行:
digitalWrite(LED_PIN, HIGH);
LED 就會亮。
四、ESP32 Web Server 控制流程
整個控制流程如下:
手機瀏覽器
│
│ 點擊 LED ON
▼
GET /led/on
│
▼
ESP32 Web Server
│
▼
digitalWrite()
│
▼
GPIO HIGH
│
▼
LED ON
Relay 也是相同概念:
手機
│
│ Relay ON
▼
GET /relay/on
│
▼
ESP32
│
▼
GPIO Control
│
▼
Relay Module
│
▼
繼電器吸合
所以 ESP32 Web Server 的核心觀念其實就是:
瀏覽器送出 HTTP 指令,ESP32 將 HTTP 指令轉換成 GPIO 動作。
五、本章使用的硬體
本實作可以使用一般 ESP32-WROOM-32 / ESP32 DevKit 開發板。
需要:
| 元件 | 數量 |
|---|---|
| ESP32 開發板 | 1 |
| LED | 1 |
| 220Ω~330Ω 電阻 | 1 |
| Relay Module | 1 |
| 麵包板 | 1 |
| 杜邦線 | 若干 |
| Wi-Fi Router | 1 |
| 手機 | 1 |
六、ESP32 LED 接線
本章 LED 使用:
GPIO 2
接線如下:
ESP32 GPIO 2
│
│
220Ω
│
▼
LED 正極
LED 負極
│
▼
GND
接線表:
| ESP32 | LED |
|---|---|
| GPIO 2 | 220Ω 電阻 → LED 正極 |
| GND | LED 負極 |
程式設定:
#define LED_PIN 2
七、ESP32 Relay 接線
Relay 控制腳使用:
GPIO 26
假設使用一般 1 Channel Relay Module:
| Relay Module | ESP32 |
|---|---|
| IN | GPIO 26 |
| GND | GND |
| VCC | 依 Relay Module 規格供電 |
程式:
#define RELAY_PIN 26
八、注意 Relay Module 工作電壓
這一點非常重要。
ESP32 GPIO 電壓為:
3.3V
很多市售 Relay Module 的 Relay 線圈可能是:
5V
但:
Relay 線圈電壓與 IN 控制腳位的邏輯電壓是兩件不同的事情。
有些 5V Relay Module 可以接受 ESP32 3.3V 控制訊號,有些則不一定可靠。
因此需要確認 Relay Module:
- VCC 工作電壓
- IN High Level 電壓
- IN Low Level 電壓
- 是否有光耦
- 是否支援 3.3V Logic
如果模組不能可靠接受 3.3V,應加入:
Transistor
MOSFET
Level Shifter
進行介面轉換。
九、Relay 為什麼常常是 Active LOW?
很多 Arduino / ESP32 Relay Module 採用:
Active LOW
也就是:
LOW = Relay ON
HIGH = Relay OFF
這和 LED 常見邏輯相反。
例如:
digitalWrite(RELAY_PIN, LOW);
繼電器吸合。
而:
digitalWrite(RELAY_PIN, HIGH);
繼電器釋放。
因此本章程式會特別定義:
#define RELAY_ON LOW
#define RELAY_OFF HIGH
這樣程式會更容易閱讀。
如果你的 Relay Module 是 Active HIGH,只要將它改成:
#define RELAY_ON HIGH
#define RELAY_OFF LOW
即可。
十、ESP32 Web Server 使用的 Library
本章需要:
#include <WiFi.h>
#include <WebServer.h>
其中:
WiFi.h
負責:
- Wi-Fi
- STA Mode
- SSID
- Password
- IP Address
而:
WebServer.h
負責建立 HTTP Server。
十一、建立 ESP32 Web Server
首先建立:
WebServer server(80);
其中:
80
就是 HTTP 預設 Port。
因此:
WebServer server(80);
代表:
ESP32 開啟 TCP Port 80,等待瀏覽器的 HTTP Request。
手機可以直接輸入:
http://192.168.1.105
通常不需要另外輸入:
:80
因為 HTTP 預設就是 Port 80。
十二、Web Server 的基本架構
ESP32 WebServer 最核心的程式架構如下:
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
void setup() {
WiFi.begin(ssid, password);
server.on("/", handleRoot);
server.begin();
}
void loop() {
server.handleClient();
}
最重要的一行是:
server.handleClient();
它必須持續執行。
因為 ESP32 需要不斷檢查:
是否有新的手機或瀏覽器正在送 HTTP Request 過來。
十三、什麼是 server.on()?
例如:
server.on("/", handleRoot);
意思是:
當瀏覽器要求:
/
時,執行:
handleRoot()
例如:
void handleRoot() {
server.send(
200,
"text/html",
"<h1>Hello ESP32</h1>"
);
}
瀏覽器就會看到:
Hello ESP32
十四、HTTP 200 是什麼?
程式中:
server.send(200, "text/html", html);
其中:
200
代表:
HTTP 200 OK
也就是:
Request 已成功處理。
而:
text/html
表示回傳的內容是:
HTML
十五、第一個 ESP32 Web Server 程式
先測試最簡單的版本:
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
WebServer server(80);
void handleRoot() {
String html = "<!DOCTYPE html>";
html += "<html>";
html += "<head>";
html += "<meta charset='UTF-8'>";
html += "<title>ESP32 Web Server</title>";
html += "</head>";
html += "<body>";
html += "<h1>ESP32 Web Server</h1>";
html += "<p>Hello ESP32!</p>";
html += "</body>";
html += "</html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi Connected");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.begin();
Serial.println("HTTP Server Started");
}
void loop() {
server.handleClient();
}
Serial Monitor 可能顯示:
Connecting.....
WiFi Connected
IP Address: 192.168.1.105
HTTP Server Started
接著手機輸入:
http://192.168.1.105
就會看到:
ESP32 Web Server
Hello ESP32!
這表示 ESP32 Web Server 已成功運作。
十六、手機與 ESP32 必須在同一個網路
本章使用:
區域網路控制
因此最簡單的架構是:
Wi-Fi Router
/ \
/ \
ESP32 手機
192.168.1.105 192.168.1.120
兩個裝置都需要能在同一個 LAN 中互相通訊。
這並不是 Internet Remote Control。
目前是:
Local Network Control
也就是:
區域網路控制。
十七、建立 LED ON 控制網址
我們可以定義:
server.on("/led/on", handleLedOn);
對應:
void handleLedOn() {
digitalWrite(LED_PIN, HIGH);
ledState = true;
server.send(
200,
"text/html",
generateWebPage()
);
}
手機要求:
http://192.168.1.105/led/on
ESP32 就執行:
digitalWrite(LED_PIN, HIGH);
十八、LED OFF 控制
建立:
server.on("/led/off", handleLedOff);
程式:
void handleLedOff() {
digitalWrite(LED_PIN, LOW);
ledState = false;
server.send(
200,
"text/html",
generateWebPage()
);
}
十九、Relay ON 控制
Relay Active LOW 時:
void handleRelayOn() {
digitalWrite(RELAY_PIN, RELAY_ON);
relayState = true;
server.send(
200,
"text/html",
generateWebPage()
);
}
二十、Relay OFF 控制
void handleRelayOff() {
digitalWrite(RELAY_PIN, RELAY_OFF);
relayState = false;
server.send(
200,
"text/html",
generateWebPage()
);
}
二十一、HTML Button 如何控制 ESP32?
最簡單的方法是:
<a href="/led/on">
<button>LED ON</button>
</a>
當使用者按:
LED ON
瀏覽器就會進入:
/led/on
ESP32 Web Server 收到:
GET /led/on
然後控制 GPIO。
所以流程就是:
HTML Button
│
▼
HTTP GET
│
▼
ESP32 Web Server
│
▼
GPIO Control
二十二、實作:ESP32 手機 Web 控制器
現在開始整合本章完整專題。
功能:
LED ON
LED OFF
Relay ON
Relay OFF
LED Status
Relay Status
Wi-Fi SSID
IP Address
RSSI
控制介面也會設計成適合手機使用的大按鈕。
二十三、ESP32 手機 Web 控制器完整程式
/*
* ====================================================
* 第 12 章
* ESP32 Web Server
* 手機控制 LED + Relay
* ====================================================
*
* 功能:
*
* 1. ESP32 連接 Wi-Fi
* 2. 建立 Web Server
* 3. 手機瀏覽器控制 LED
* 4. 手機瀏覽器控制 Relay
* 5. 顯示 LED 狀態
* 6. 顯示 Relay 狀態
* 7. 顯示 SSID
* 8. 顯示 ESP32 IP
* 9. 顯示 Wi-Fi RSSI
*
* LED:
* GPIO 2
*
* Relay:
* GPIO 26
*
* Relay 預設:
* Active LOW
* ====================================================
*/
#include <WiFi.h>
#include <WebServer.h>
// ====================================================
// Wi-Fi
// ====================================================
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";
// ====================================================
// GPIO
// ====================================================
#define LED_PIN 2
#define RELAY_PIN 26
// ====================================================
// Relay Logic
// ====================================================
#define RELAY_ON LOW
#define RELAY_OFF HIGH
// ====================================================
// Web Server
// ====================================================
WebServer server(80);
// ====================================================
// 狀態
// ====================================================
bool ledState = false;
bool relayState = false;
// ====================================================
// 產生 HTML
// ====================================================
String generateWebPage() {
String html;
html.reserve(7000);
html += "<!DOCTYPE html>";
html += "<html lang='zh-TW'>";
html += "<head>";
html += "<meta charset='UTF-8'>";
html += "<meta name='viewport' ";
html += "content='width=device-width, initial-scale=1.0'>";
html += "<title>";
html += "ESP32 Web Control";
html += "</title>";
// ==================================================
// CSS
// ==================================================
html += "<style>";
html += "body{";
html += "font-family:Arial,sans-serif;";
html += "background:#f4f6f8;";
html += "text-align:center;";
html += "margin:0;";
html += "padding:20px;";
html += "}";
html += ".container{";
html += "max-width:600px;";
html += "margin:auto;";
html += "}";
html += ".card{";
html += "background:white;";
html += "border-radius:15px;";
html += "padding:20px;";
html += "margin-bottom:20px;";
html += "box-shadow:0 4px 12px rgba(0,0,0,0.1);";
html += "}";
html += "h1{";
html += "font-size:28px;";
html += "}";
html += "h2{";
html += "font-size:22px;";
html += "}";
html += ".status{";
html += "font-size:20px;";
html += "font-weight:bold;";
html += "margin:15px;";
html += "}";
html += ".on{";
html += "color:green;";
html += "}";
html += ".off{";
html += "color:red;";
html += "}";
html += ".btn{";
html += "display:inline-block;";
html += "width:130px;";
html += "padding:16px;";
html += "margin:8px;";
html += "font-size:18px;";
html += "border:none;";
html += "border-radius:10px;";
html += "cursor:pointer;";
html += "color:white;";
html += "}";
html += ".btn-on{";
html += "background:#28a745;";
html += "}";
html += ".btn-off{";
html += "background:#dc3545;";
html += "}";
html += ".network{";
html += "font-size:16px;";
html += "line-height:1.8;";
html += "}";
html += "</style>";
html += "</head>";
// ==================================================
// BODY
// ==================================================
html += "<body>";
html += "<div class='container'>";
html += "<h1>";
html += "ESP32 Web Control";
html += "</h1>";
// ==================================================
// LED
// ==================================================
html += "<div class='card'>";
html += "<h2>LED Control</h2>";
html += "<div class='status ";
if (ledState) {
html += "on'>";
html += "LED Status : ON";
}
else {
html += "off'>";
html += "LED Status : OFF";
}
html += "</div>";
html += "<a href='/led/on'>";
html += "<button class='btn btn-on'>";
html += "LED ON";
html += "</button>";
html += "</a>";
html += "<a href='/led/off'>";
html += "<button class='btn btn-off'>";
html += "LED OFF";
html += "</button>";
html += "</a>";
html += "</div>";
// ==================================================
// Relay
// ==================================================
html += "<div class='card'>";
html += "<h2>Relay Control</h2>";
html += "<div class='status ";
if (relayState) {
html += "on'>";
html += "Relay Status : ON";
}
else {
html += "off'>";
html += "Relay Status : OFF";
}
html += "</div>";
html += "<a href='/relay/on'>";
html += "<button class='btn btn-on'>";
html += "Relay ON";
html += "</button>";
html += "</a>";
html += "<a href='/relay/off'>";
html += "<button class='btn btn-off'>";
html += "Relay OFF";
html += "</button>";
html += "</a>";
html += "</div>";
// ==================================================
// Network
// ==================================================
html += "<div class='card'>";
html += "<h2>Network Status</h2>";
html += "<div class='network'>";
html += "SSID : ";
html += WiFi.SSID();
html += "<br>";
html += "IP Address : ";
html += WiFi.localIP().toString();
html += "<br>";
html += "RSSI : ";
html += String(WiFi.RSSI());
html += " dBm";
html += "<br>";
html += "MAC : ";
html += WiFi.macAddress();
html += "</div>";
html += "</div>";
html += "</div>";
html += "</body>";
html += "</html>";
return html;
}
// ====================================================
// Home
// ====================================================
void handleRoot() {
server.send(
200,
"text/html; charset=utf-8",
generateWebPage()
);
}
// ====================================================
// LED ON
// ====================================================
void handleLedOn() {
digitalWrite(
LED_PIN,
HIGH
);
ledState = true;
Serial.println(
"LED ON"
);
server.send(
200,
"text/html; charset=utf-8",
generateWebPage()
);
}
// ====================================================
// LED OFF
// ====================================================
void handleLedOff() {
digitalWrite(
LED_PIN,
LOW
);
ledState = false;
Serial.println(
"LED OFF"
);
server.send(
200,
"text/html; charset=utf-8",
generateWebPage()
);
}
// ====================================================
// Relay ON
// ====================================================
void handleRelayOn() {
digitalWrite(
RELAY_PIN,
RELAY_ON
);
relayState = true;
Serial.println(
"Relay ON"
);
server.send(
200,
"text/html; charset=utf-8",
generateWebPage()
);
}
// ====================================================
// Relay OFF
// ====================================================
void handleRelayOff() {
digitalWrite(
RELAY_PIN,
RELAY_OFF
);
relayState = false;
Serial.println(
"Relay OFF"
);
server.send(
200,
"text/html; charset=utf-8",
generateWebPage()
);
}
// ====================================================
// 404
// ====================================================
void handleNotFound() {
server.send(
404,
"text/plain",
"404 Not Found"
);
}
// ====================================================
// Connect Wi-Fi
// ====================================================
void connectWiFi() {
WiFi.mode(
WIFI_STA
);
WiFi.setAutoReconnect(
true
);
WiFi.begin(
WIFI_SSID,
WIFI_PASSWORD
);
Serial.println();
Serial.print(
"Connecting to "
);
Serial.println(
WIFI_SSID
);
unsigned long startTime =
millis();
while (
WiFi.status() != WL_CONNECTED &&
millis() - startTime < 15000
) {
delay(500);
Serial.print(".");
}
Serial.println();
if (
WiFi.status() == WL_CONNECTED
) {
Serial.println(
"WiFi Connected"
);
Serial.print(
"IP Address : "
);
Serial.println(
WiFi.localIP()
);
Serial.print(
"RSSI : "
);
Serial.print(
WiFi.RSSI()
);
Serial.println(
" dBm"
);
}
else {
Serial.println(
"WiFi Connection Failed"
);
}
}
// ====================================================
// SETUP
// ====================================================
void setup() {
Serial.begin(
115200
);
// --------------------------------------------------
// GPIO
// --------------------------------------------------
pinMode(
LED_PIN,
OUTPUT
);
pinMode(
RELAY_PIN,
OUTPUT
);
// --------------------------------------------------
// Startup State
// --------------------------------------------------
digitalWrite(
LED_PIN,
LOW
);
digitalWrite(
RELAY_PIN,
RELAY_OFF
);
ledState = false;
relayState = false;
// --------------------------------------------------
// Wi-Fi
// --------------------------------------------------
connectWiFi();
// --------------------------------------------------
// Web Routing
// --------------------------------------------------
server.on(
"/",
handleRoot
);
server.on(
"/led/on",
handleLedOn
);
server.on(
"/led/off",
handleLedOff
);
server.on(
"/relay/on",
handleRelayOn
);
server.on(
"/relay/off",
handleRelayOff
);
server.onNotFound(
handleNotFound
);
// --------------------------------------------------
// Start HTTP Server
// --------------------------------------------------
server.begin();
Serial.println(
"HTTP Server Started"
);
Serial.println();
Serial.println(
"Open browser:"
);
Serial.print(
"http://"
);
Serial.println(
WiFi.localIP()
);
}
// ====================================================
// LOOP
// ====================================================
void loop() {
server.handleClient();
// --------------------------------------------------
// Wi-Fi Reconnect
// --------------------------------------------------
static unsigned long
lastReconnect = 0;
if (
WiFi.status() != WL_CONNECTED
) {
if (
millis() - lastReconnect >= 10000
) {
lastReconnect =
millis();
Serial.println(
"WiFi disconnected."
);
Serial.println(
"Trying reconnect..."
);
WiFi.reconnect();
}
}
}
二十四、修改 Wi-Fi SSID 與 Password
程式:
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";
修改成自己的網路。
例如:
const char* WIFI_SSID = "Jason_WIFI";
const char* WIFI_PASSWORD = "12345678";
正式公開程式時,不要直接公布真正的 Wi-Fi Password。
二十五、Arduino IDE 設定
如果使用一般 ESP32 DevKit / ESP32-WROOM-32:
Tools
→ Board
→ ESP32 Arduino
→ ESP32 Dev Module
再選:
Tools
→ Port
→ COMx
Serial Monitor:
115200 baud
二十六、ESP32 開機後的 Serial Monitor
正常情況可能顯示:
Connecting to Jason_WIFI
.....
WiFi Connected
IP Address : 192.168.1.105
RSSI : -46 dBm
HTTP Server Started
Open browser:
http://192.168.1.105
其中最重要的是:
192.168.1.105
因為這就是:
ESP32 Web Server IP Address。
二十七、手機如何連接 ESP32?
手機先確認連到和 ESP32 相同的 Wi-Fi。
例如:
Wi-Fi:
Jason_WIFI
然後開啟:
- Chrome
- Safari
- Edge
- Firefox
網址輸入:
http://192.168.1.105
實際 IP 請以自己的 Serial Monitor 為準。
二十八、手機 Web 控制畫面
網頁將顯示:
ESP32 Web Control
LED Control
LED Status : OFF
[ LED ON ]
[ LED OFF ]
Relay Control
Relay Status : OFF
[ Relay ON ]
[ Relay OFF ]
Network Status
SSID : Jason_WIFI
IP Address : 192.168.1.105
RSSI : -46 dBm
MAC : XX:XX:XX:XX:XX:XX
二十九、按下 LED ON 發生什麼事?
使用者按:
LED ON
瀏覽器會要求:
GET /led/on
ESP32 執行:
handleLedOn();
然後:
digitalWrite(LED_PIN, HIGH);
並設定:
ledState = true;
接著重新產生網頁。
因此畫面顯示:
LED Status : ON
三十、按下 LED OFF
HTTP Request:
GET /led/off
執行:
digitalWrite(LED_PIN, LOW);
狀態變成:
LED Status : OFF
三十一、Relay ON
按:
Relay ON
瀏覽器送出:
GET /relay/on
ESP32:
digitalWrite(RELAY_PIN, RELAY_ON);
如果是 Active LOW:
GPIO 26 = LOW
Relay 吸合。
三十二、Relay OFF
網址:
/relay/off
執行:
digitalWrite(RELAY_PIN, RELAY_OFF);
Active LOW 模組此時為:
GPIO 26 = HIGH
Relay 釋放。
三十三、ESP32 Web Server URL 整理
本專題共有五個主要 URL。
| 功能 | URL |
|---|---|
| 首頁 | / |
| LED ON | /led/on |
| LED OFF | /led/off |
| Relay ON | /relay/on |
| Relay OFF | /relay/off |
假設 ESP32 IP:
192.168.1.105
完整網址就是:
http://192.168.1.105/
http://192.168.1.105/led/on
http://192.168.1.105/led/off
http://192.168.1.105/relay/on
http://192.168.1.105/relay/off
三十四、為什麼使用 URL 控制很重要?
因為這表示控制介面和硬體已經被分離。
ESP32 只需要理解:
/led/on
/led/off
/relay/on
/relay/off
未來控制來源可以是:
- 手機 Browser
- Windows Browser
- Raspberry Pi
- Python
- Android App
- iPhone App
- Home Assistant
- Node-RED
- 其他 ESP32
只要能送 HTTP Request,就可以控制 ESP32。
因此 Web Server 不只是做漂亮網頁。
真正重要的是:
ESP32 開始具備 Network API 的概念。
三十五、GET 控制其實就是簡單的 API
例如:
GET /relay/on
我們可以把它理解為:
控制 Relay ON 的 API
未來 Python 可以直接呼叫:
http://192.168.1.105/relay/on
Home Assistant 也可以呼叫。
另一台 ESP32 也可以呼叫。
所以這一章同時正在建立:
ESP32 HTTP API 的基本觀念。
三十六、為什麼 Web Server 需要 server.handleClient()?
程式:
void loop() {
server.handleClient();
}
這一行非常重要。
它負責:
檢查 Client
↓
接收 HTTP Request
↓
判斷 URL
↓
執行 Handler
↓
回傳 HTTP Response
如果把:
server.handleClient();
刪掉,Web Server 雖然可能已經:
server.begin();
但 ESP32 不會正常處理新的 HTTP Request。
三十七、loop() 中不要使用很長的 delay()
例如不要寫:
void loop() {
delay(10000);
server.handleClient();
}
因為瀏覽器最多可能要等 10 秒 ESP32 才處理 Request。
比較好的 IoT 程式應該利用:
millis()
處理時間控制。
例如:
Web Server
Sensor
LCD
Relay
Wi-Fi Monitor
Timer
都可以在同一個 loop 中非阻塞運作。
這也是後面大型 ESP32 專案非常重要的架構。
三十八、如何確認 Relay 是 Active LOW?
先不要連 AC 市電負載。
只測試 Relay Module 本身。
程式:
digitalWrite(RELAY_PIN, LOW);
delay(2000);
digitalWrite(RELAY_PIN, HIGH);
delay(2000);
如果:
LOW
時 Relay:
喀一聲
LED 指示燈亮
Relay 吸合
代表:
Active LOW
如果 HIGH 才吸合,就是:
Active HIGH
三十九、Relay NO、NC、COM 是什麼?
Relay 輸出通常有:
COM
NO
NC
COM
Common,共用端。
NO
Normally Open。
Relay 沒有動作時:
COM 與 NO 不導通
Relay ON:
COM 與 NO 導通
NC
Normally Closed。
Relay 沒有動作:
COM 與 NC 導通
Relay ON:
COM 與 NC 斷開
因此一般「開啟設備」用途常使用:
COM + NO
四十、Relay 控制負載的概念
低壓 DC 示範可以理解為:
Power +
│
▼
Relay COM
│
│ Relay ON
▼
Relay NO
│
▼
Load
│
▼
Power -
ESP32 GPIO 只是控制:
Relay Module IN
不是直接驅動大負載。
四十一、AC 市電安全
Relay 經常被用來控制:
AC 110V
AC 220V
但這已經涉及高壓電。
ESP32 教學與開發初期建議先使用:
LED
5V Lamp
12V DC Fan
低壓負載
驗證控制邏輯。
若要操作 AC 市電,必須考慮:
- Relay 額定電流
- Relay 額定電壓
- PCB Creepage
- Clearance
- 保險絲
- 接地
- 外殼
- 絕緣
- 線徑
- 端子安全
- 負載突波
不能只看到 Relay 標示:
10A 250VAC
就認為整塊便宜 Relay Module 都可以安全承受 10A。
四十二、為什麼 ESP32 IP 有時會改變?
因為上一章介紹過:
DHCP
Router 會自動配發 IP。
今天可能是:
192.168.1.105
重新開機後可能變成:
192.168.1.108
這對 Web Server 不方便。
解決方法之一是:
DHCP Reservation
在 Router 內綁定:
ESP32 MAC Address
↓
固定配發
192.168.1.105
這是我比較建議的家庭或公司 LAN 使用方式。
四十三、手機連不到 ESP32 的常見原因
如果:
http://192.168.1.105
打不開,可以依序檢查。
1. ESP32 是否真的連上 Wi-Fi?
Serial Monitor 是否看到:
WiFi Connected
2. IP 是否正確?
看:
IP Address
不要直接照範例的:
192.168.1.105
一定要使用自己的 IP。
3. 手機與 ESP32 是否同網段?
例如:
ESP32:
192.168.1.105
手機:
192.168.1.120
通常代表同一個區域網路。
4. 是否使用 Guest Wi-Fi?
有些 Router 的 Guest Network 啟用了:
Client Isolation
AP Isolation
即使兩台設備連同一個 SSID,也不允許互相通訊。
這會造成:
ESP32 可上 Internet
手機也可上 Internet
但是手機找不到 ESP32
5. 網址是否用了 HTTPS?
本章使用的是:
HTTP
所以應輸入:
http://192.168.1.105
不是:
https://192.168.1.105
四十四、為什麼控制網頁可以直接顯示目前狀態?
本章使用:
bool ledState;
bool relayState;
記錄:
LED
Relay
目前的狀態。
例如:
ledState = true;
代表:
LED ON
HTML 產生時:
if (ledState) {
html += "LED Status : ON";
}
所以網頁不是只有控制按鈕。
它同時也是:
簡單的 HMI 狀態顯示器。
四十五、什麼是 HMI?
HMI:
Human Machine Interface
也就是:
人機介面。
以前工業設備可能使用:
LCD
按鍵
觸控螢幕
現在 ESP32 可以利用:
Web Browser
直接當作 HMI。
因此:
ESP32
+
Wi-Fi
+
Web Server
+
HTML
就可以建立一套低成本:
Web HMI。
四十六、手機 Web 控制的優點
相較於專用 App:
ESP32 Web Control 有幾個優勢。
不需要安裝 App
直接開瀏覽器。
跨平台
可以使用:
- iPhone
- Android
- Windows
- macOS
- Linux
- Raspberry Pi
修改容易
網頁直接存在 ESP32。
部署簡單
只要知道:
IP Address
即可使用。
四十七、ESP32 Web Server 的限制
雖然 ESP32 可以當 Web Server,但它並不是:
Apache Server
Nginx Server
大型 Linux Server
ESP32 的:
- RAM
- CPU
- Concurrent Connections
- Storage
都有限制。
因此比較適合:
Device Control
Sensor Dashboard
IoT Configuration
Local HMI
Simple API
而不是高流量大型網站。
四十八、本章完整系統架構
手機
│
│ Browser
│
▼
http://192.168.1.105
│
│
▼
Wi-Fi Router
│
│ LAN
▼
ESP32 Web Server
│
┌─────────┴─────────┐
│ │
▼ ▼
GPIO 2 GPIO 26
│ │
▼ ▼
LED Relay Module
│
▼
Load
四十九、Web Request 架構
首頁:
GET /
│
▼
ESP32
│
▼
HTML
LED:
GET /led/on
│
▼
GPIO 2 HIGH
GET /led/off
│
▼
GPIO 2 LOW
Relay:
GET /relay/on
│
▼
GPIO 26
│
▼
Relay ON
GET /relay/off
│
▼
GPIO 26
│
▼
Relay OFF
五十、這一章真正重要的是什麼?
很多初學者學完本章,可能只看到:
手機可以讓 LED 亮滅。
但真正重要的不是 LED。
真正重要的是我們已經建立:
Browser
↓
HTTP
↓
GET
↓
URL Routing
↓
ESP32 Web Server
↓
Application Logic
↓
GPIO
這是一個真正的:
Network Control Architecture。
後面可以把 LED 換成:
- Relay
- Servo
- Motor
- Fan
- Pump
- Door Lock
- Light
- Solenoid Valve
- Air Conditioner
- Alarm
- Robot
整個 Web Server 架構都不用重新學。
五十一、下一步可以如何擴充?
目前網頁使用的是:
按下 Button
↓
重新載入整張 HTML
後面可以進一步使用:
JavaScript
Fetch API
AJAX
JSON
變成:
按 Button
↓
HTTP Request
↓
只更新狀態
↓
不重新整理整頁
介面就會更像真正的 App。
五十二、也可以加入感測器資料
例如後面加入:
DHT11
DHT22
BMP280
BME280
DS18B20
光敏電阻
PIR
網頁就可以顯示:
Temperature : 27.5°C
Humidity : 65%
LED : ON
Relay : OFF
ESP32 就會從:
Web Remote Control
進化成:
IoT Monitoring Dashboard
五十三、ESP32 Web Server 常用指令整理
| 功能 | 程式 |
|---|---|
| Wi-Fi Library | #include <WiFi.h> |
| Web Server Library | #include <WebServer.h> |
| 建立 Server | WebServer server(80) |
| STA Mode | WiFi.mode(WIFI_STA) |
| Wi-Fi Connect | WiFi.begin() |
| IP Address | WiFi.localIP() |
| 建立 Route | server.on() |
| HTTP Response | server.send() |
| 啟動 Server | server.begin() |
| 處理 Client | server.handleClient() |
| LED Control | digitalWrite() |
| Wi-Fi RSSI | WiFi.RSSI() |
| Wi-Fi Reconnect | WiFi.reconnect() |
五十四、本章重點整理
學完本章後,應該理解以下重點。
1. ESP32 可以直接當 Web Server
WebServer server(80);
2. 手機利用 HTTP 連線 ESP32
http://ESP32_IP
3. GET 可以傳送控制命令
例如:
GET /led/on
4. server.on() 建立 URL Route
server.on("/led/on", handleLedOn);
5. server.send() 回傳 HTML
server.send(
200,
"text/html",
html
);
6. GPIO 可由網路控制
digitalWrite(
LED_PIN,
HIGH
);
7. Relay 必須注意 Active LOW / Active HIGH
例如:
#define RELAY_ON LOW
8. server.handleClient() 必須持續執行
server.handleClient();
9. 手機與 ESP32 需能在 LAN 中互通
也就是:
Local Network Control
10. ESP32 Web Server 是 IoT 的重要基礎
未來可以延伸到:
Sensor
Dashboard
API
JSON
AJAX
MQTT
Cloud
Home Assistant
五十五、結語
從本章開始,ESP32 已經不再只是:
LED
按鍵
Sensor
Motor
控制器。
我們已經讓它變成一台真正可以接受:
HTTP Request
並回傳:
HTML
的 Web Server。
整個系統架構已經變成:
手機
│
▼
Wi-Fi
│
▼
ESP32 Web Server
│
├── LED
└── Relay
使用者只要打開瀏覽器,就能完成:
LED ON
LED OFF
Relay ON
Relay OFF
同時還可以看到:
LED Status
Relay Status
SSID
IP Address
RSSI
MAC Address
這就是一個完整的:
ESP32 手機 Web 控制器
更重要的是,本章建立的 HTTP 與 Web Server 架構,可以直接成為後續 ESP32 IoT 專案的核心。
接下來就可以進一步進入:
ESP32
+
Web Server
+
Sensor
+
JavaScript
+
AJAX
+
JSON
讓網頁不需要重新整理,就能即時更新溫度、濕度、GPIO 與設備狀態,建立真正的:
ESP32 IoT Dashboard。