第12章 ESP32 Web Server 完整教學:手機網頁控制 LED、Relay 與 GPIO

發布日期: |更新日期:
文章主圖:第12章 ESP32 Web Server 完整教學:手機網頁控制 LED、Relay 與 GPIO

上一章我們已經讓 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 Web Server 實作硬體清單
元件 數量
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 接線表
ESP32 LED
GPIO 2 220Ω 電阻 → LED 正極
GND LED 負極

程式設定:

#define LED_PIN 2

七、ESP32 Relay 接線

Relay 控制腳使用:

GPIO 26

假設使用一般 1 Channel Relay Module:

ESP32 與 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。

ESP32 Web Server 控制 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 常用指令整理

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。

下一步學習

不要停在這一篇,接著把內容做成自己的實作

目前主題:Arduino。從完整學習路線、免費資源、會員下載,再接到實作所需材料。

同系列:ESP32 |  已是會員?直接前往 Getfile
相關資源與會員文章下載
文章可免費閱讀;下載完整 DOCX 或 PDF 需登入免費基本會員。
查看相關資源 會員免費下載 DOCX 會員免費 PDF/列印

← 回文章列表