上週範例程式:
// 保護標頭檔,避免同一個 .h 標頭檔被重複引用
#ifndef WIFI_SETUP_H
#define WIFI_SETUP_H
//-------------------------------------------------------------------------------
#include <WiFi.h> // 引用WiFi函式庫
const char *ssid="SSID"; // 熱點名稱
const char *password="密碼"; // 熱點密碼
//-------------------------------------------------------------------------------
inline void startAP() // 啟用熱點
{
/*
IPAddress local_ip(192,168,31,31); // 設定熱點的IP位址
IPAddress gateway(192,168,31,2); // 設定熱點的閘道位址
IPAddress subnet(255,255,255,0); // 設定熱點的子網路遮罩位址
WiFi.softAPConfig(local_ip, gateway, subnet); // 設定熱點參數(未設定時,熱點IP位址預設為192.168.4.1)
*/
while (!WiFi.softAP(ssid, password)) // 若熱點尚未啟動,則每0.5秒印出一個點
{
delay(500);
Serial.print(".");
}
Serial.println("\n----------------------------------");
Serial.println("熱點啟動成功");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP()); // 印出熱點的IP位址
}
//-------------------------------------------------------------------------------
#include <esp_wifi.h> // 引用esp_wifi函式庫
inline void listConnectedStations() // 印出已連線裝置清單
{
wifi_sta_list_t station_list; // 儲存連線裝置列表的結構
esp_wifi_ap_get_sta_list(&station_list); // 獲取當前連接到AP的所有裝置列表
Serial.println("--------- 當前連線裝置清單 ---------");
Serial.print("連線裝置數量: ");
Serial.println(station_list.num);
Serial.println("----------------------------------");
if (station_list.num > 0) {
Serial.println("編號 MAC 位址 IP 位址");
Serial.println("----------------------------------");
for (int i = 0; i < station_list.num; i++) {
wifi_sta_info_t station = station_list.sta[i];
// 取得 MAC 位址
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", station.mac[0], station.mac[1], station.mac[2], station.mac[3], station.mac[4], station.mac[5]);
// 取得 IP 位址 (需要透過 DHCP 租約列表)
IPAddress ip = WiFi.softAPIP();
ip[3] = i + 2; // 預設分配從 .2 開始
// 顯示裝置資訊
Serial.printf("%2d %s %s\n", i + 1, macStr, ip.toString().c_str());
}
} else {
Serial.println("目前沒有裝置連線");
}
Serial.println("----------------------------------");
}
//-------------------------------------------------------------------------------------------------------------------------
inline void WiFiEvent(WiFiEvent_t event, arduino_event_info_t info) // WiFi事件處理函式
{
uint8_t *mac = nullptr; // 宣告MAC位址指標
char macStr[18] = { 0 }; // 儲存格式化MAC位址的字串緩衝區
switch (event) {
case ARDUINO_EVENT_WIFI_AP_STACONNECTED: // 裝置已連線
// 從事件資訊中提取MAC位址,並將MAC位址格式化為字串(XX:XX:XX:XX:XX:XX)
mac = info.wifi_ap_staconnected.mac;
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
Serial.println("\n[事件] 裝置已連線");
Serial.print("MAC: ");
Serial.println(macStr);
listConnectedStations(); // 更新並顯示當前連線裝置清單
break;
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: // 裝置斷開連線
// 從事件資訊中提取MAC位址,並將MAC位址格式化為字串(XX:XX:XX:XX:XX:XX)
mac = info.wifi_ap_stadisconnected.mac;
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
Serial.println("\n[事件] 裝置已斷開連線");
Serial.print("MAC: ");
Serial.println(macStr);
listConnectedStations(); // 更新並顯示當前連線裝置清單
break;
}
}
//-------------------------------------------------------------------------------
#endif
//-------------------------------------------------------------------------------------------------------------------------
const int ledPin = 2; // LED接腳
bool ledState = LOW; // LED狀態,初始值為LOW(OFF)
bool blinkEnable = LOW; // 閃爍控制,初始值為LOW (停止)
unsigned long previousMillis = 0; // 前一次的millis()時間
int interval = 500; // 預設計時的時間(ms)
//-------------------------------------------------------------------------------------------------------------------------
#include "wifi_setup.h" // 引用自行建義的 "wifi_setup.h"
//-------------------------------------------------------------------------------------------------------------------------
#include <WebServer.h> // 引用WebServer函式庫
WebServer server(80); // 建立WebServer物件, port為80
void handleRoot() // 根目錄
{
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p>歡迎光臨!請點選下列連結:</p> <ul> <li><a href=\"/on\">打開 LED 燈</a></li> <li><a href=\"/off\">關閉 LED 燈</a></li> <li><a href=\"/blink\">啟用或停止閃爍 LED 燈</a></li> </ul>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
server.send(200, "text/html; charset=UTF-8", html);
}
void handleOn() // LED ON
{
blinkEnable = false;
digitalWrite(ledPin, HIGH);
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p><strong><span style=\"color:#e74c3c\">LED燈已打開!</span></strong></p> <ul> <li><a href=\"/off\">關閉 LED 燈</a></li> <li><a href=\"/blink\">啟用或停止閃爍 LED 燈</a></li> </ul> <p><a href=\"/\">返回首頁</a></p>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
server.send(200, "text/html; charset=UTF-8", html);
}
void handleOff() // LED OFF
{
blinkEnable = false;
digitalWrite(ledPin, LOW);
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p><span style=\"color:#27ae60\"><strong>LED燈已關閉!</strong></span></p> <ul> <li><a href=\"/on\">開啟 LED 燈</a></li> <li><a href=\"/blink\">啟用或停止閃爍 LED 燈</a></li> </ul> <p><a href=\"/\">返回首頁</a></p>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
server.send(200, "text/html; charset=UTF-8", html);
}
void handleBlink() // LED Blink
{
blinkEnable = !blinkEnable;
String html;
if (blinkEnable == true)
{
html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p><span style=\"color:#e74c3c\"><strong>LED燈正在閃爍!</strong></span></p> <ul> <li><a href=\"/on\">開啟 LED 燈</a></li> <li><a href=\"/off\">關閉 LED 燈</a></li> <li><a href=\"/blink\">關閉 LED 燈閃爍</a></li> </ul><p><a href=\"/\">返回首頁</a></p>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
}
else
{
html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p><span style=\"color:#27ae60\"><strong>LED燈已停止閃爍!</strong></span></p> <ul> <li><a href=\"/on\">開啟 LED 燈</a></li> <li><a href=\"/off\">關閉 LED 燈</a></li> <li><a href=\"/blink\">開啟 LED 燈閃爍</a></li> </ul><p><a href=\"/\">返回首頁</a></p>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
}
server.send(200, "text/html; charset=UTF-8", html);
}
void handleSetInterval()
{
if (server.hasArg("time")) // 檢查是否有收到參數
{
int newInterval = server.arg("time").toInt();
if (newInterval > 0)
{
interval = newInterval;
}
}
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p><strong>閃爍間隔已設定為 " + String(interval) + " 毫秒</strong></p>"
"<p><a href=\"/\">返回首頁</a></p>"
"<p>設定LED閃爍時間(ms):</p> <form action=\"/setInterval\" method=\"get\" name=\"setInterval\"><input name=\"time\" type=\"number\" value=\"" + String(interval) + "\" /><input type=\"submit\" value=\"設定\" /> </form> <p> </p>";
server.send(200, "text/html; charset=UTF-8", html);
}
void handleNotFound() // 找不到網頁
{
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"找不到網頁";
server.send(404, "text/html; charset=UTF-8", html);
}
//-------------------------------------------------------------------------------------------------------------------------
void setup() {
//-------------------------------------------------------------------
pinMode(ledPin, OUTPUT); // 設定LED為輸出腳
Serial.begin(9600); // 啟用串列埠監看視窗
//--------------------------- softAP設定 ----------------------------
startAP(); // 啟用熱點
WiFi.onEvent(WiFiEvent); // 啟用WiFi事件處理
listConnectedStations(); // 印出已連線裝置清單
//---------------------------WebServer設定---------------------------
server.on("/", handleRoot); // 在/時,前往handleRoot()
server.on("/on", handleOn); // 在/on時,前往handleOn()
server.on("/off", handleOff); // 在/off時,前往handleOff()
server.on("/blink", handleBlink); // 在/blink時,前往handleBlink()
server.on("/setInterval", handleSetInterval); // 在/setInterval時,前往handleSetInterval()
server.onNotFound(handleNotFound); // 找不到網頁時,前往handleNotFound()
server.begin(); // 啟動webServer
Serial.println("Server已啟動"); // 印出提示文字
//-------------------------------------------------------------------
}
void loop() {
server.handleClient(); // 檢查是否有客戶端向ESP32 WebServer發送請求
//--------------------------------------------------------------------------
if (blinkEnable == true)
{
unsigned long currentMillis = millis(); // 當前的millis()時間
if (currentMillis - previousMillis >= interval) // 若達到預設計時的時間
{
previousMillis = currentMillis; // 更新前一次的millis()時間
//-----------------------------
// 每個interval所做的事
//-----------------------------
ledState = !ledState; // 改變LED狀態
digitalWrite(ledPin, ledState); // 輸出狀態
//-----------------------------
}
}
//--------------------------------------------------------------------------
}
本次練習我們希望將不同動作的各個頁面進行整合,讓所有的訊息彙整呈現在首頁上,並可定期自動更新內容。
原網頁內容






整合網頁內容


設計思路
- 新增一個目錄
/status,負責傳遞下面的 JSON 格式參數。
{
"led": "ON", // LED狀態
"blink": "正在閃爍", // 是否閃爍
"interval": 500 // 閃爍時間間隔
}
- 前端網頁用 JavaScript (AJAX / fetch)
- 每隔一段時間自動向
/status請求資料。 - 解析 JSON 內容,更新頁面上的狀態顯示。
- 每隔一段時間自動向
- 所有控制連結
/on,/off,/blink,/setInterval執行後自動導向首頁,統一在首頁顯示狀態訊息。 - AJAX 只負責讀取狀態,並不會影響原控制功能。
實作步驟
1、在WebServer設定中新增一個處理頁面 /status,前往handleStatus()副程式。
server.on("/status", handleStatus); // 在/status時,前往handleStatus()
2、handleStatus() 副程式責負傳遞需要更新的led、blink、interval等參數:
void handleStatus() // Status
{
String json = "{";
json += "\"led\":\"" + String(ledState ? "ON" : "OFF") + "\","; // ledState為1時,顯示“ON”,否則顯示“OFF”
json += "\"blink\":\"" + String(blinkEnable ? "正在閃爍" : "停止閃爍") + "\","; // blinkEnable為1時,顯示“正在閃爍”,否則顯示“停止閃爍”
json += "\"interval\":" + String(interval);
json += "}";
server.send(200, "application/json", json);
}
3、在首頁的 html 程式碼中加入AJAX自動更新的JavaScript:
void handleRoot() // 根目錄
{
String html = "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<p>歡迎光臨!請點選下列連結:</p>"
"<ul>"
"<li><a href=\"/on\">打開 LED 燈</a></li>"
"<li><a href=\"/off\">關閉 LED 燈</a></li>"
"<li><a href=\"/blink\">啟用或停止閃爍 LED 燈</a></li>"
"</ul>"
// 設定閃爍時間
"<p>設定LED閃爍時間(ms):</p>"
"<form action=\"/setInterval\" method=\"get\" name=\"setInterval\">"
"<input name=\"interval\" type=\"number\" value=\"" + String(interval) + "\" />"
"<input type=\"submit\" value=\"設定\" />"
"</form>"
// AJAX 狀態自動更新
"<div id=\"status\">讀取狀態...</div>"
"<script>"
"function updateStatus(){"
" fetch('/status')" // 向 /status 發送請求
" .then(response => response.json())" // 解析 JSON
" .then(data => {"
" let html = 'LED狀態:' + data.led + '<BR>';"
" html += '閃爍狀態:' + data.blink + '<BR>';"
" html += '閃爍間隔:' + data.interval + ' ms';"
" document.getElementById('status').innerHTML = html;"
" });"
"}"
"setInterval(updateStatus," + String(interval) + ");" // 每個interval更新一次
"updateStatus();" // 載入更新頁面
"</script>";
server.send(200, "text/html; charset=UTF-8", html);
}
4、利用下列程式碼,可使原控制連結/on, /off, /blink, /setInterval執行後,自動導向首頁。
server.sendHeader("Location", "/");
server.send(303);
//-------------------------------------------------------------------------------
void handleOn() // LED ON
{
blinkEnable = false;
digitalWrite(ledPin, HIGH);
ledState = HIGH;
server.sendHeader("Location", "/");
server.send(303);
}
//-------------------------------------------------------------------------------
void handleOff() // LED OFF
{
blinkEnable = false;
digitalWrite(ledPin, LOW);
ledState = LOW;
server.sendHeader("Location", "/");
server.send(303);
}
//-------------------------------------------------------------------------------
void handleBlink() // LED Blink
{
blinkEnable = !blinkEnable;
server.sendHeader("Location", "/");
server.send(303);
}
//-------------------------------------------------------------------------------
void handleSetInterval() // Setup Blink Interval
{
if (server.hasArg("interval")) // 檢查是否有收到參數
{
int newInterval = server.arg("interval").toInt();
if (newInterval > 0)
{
interval = newInterval;
blinkEnable = true; // 設定後啟用閃爍
}
}
server.sendHeader("Location", "/");
server.send(303);
}
//-------------------------------------------------------------------------------
作業練習:請將上週作業,控制LED燈亮滅程度、閃爍時間、顯示狀態等功能整合在首頁上,並可定期自動更新內容。