ESP32 Web 服务入门:WebServer 网页控制、mDNS 域名与 REST API
L2 收官:Wi-Fi 第一课让设备「能上网」,本篇让它「被访问」——手机浏览器打开一个页面,点按钮控制 ESP32 上的 LED。这就是 L2 验收项目「手机浏览器控制 LED」的完整答案,也是智能家居面板、桌面潮玩 Web 配置界面的原型。
一、WebServer 三分钟跑通
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80); // 80 端口
void handleRoot() {
server.send(200, "text/html", "<h1>ESPDIY</h1><a href='/on'>开灯</a> <a href='/off'>关灯</a>");
}
void handleOn() { digitalWrite(2, HIGH); server.sendHeader("Location", "/"); server.send(303); }
void handleOff() { digitalWrite(2, LOW); server.sendHeader("Location", "/"); server.send(303); }
void setup() {
pinMode(2, OUTPUT);
WiFi.begin("你的SSID", "你的密码");
while (WiFi.status() != WL_CONNECTED) delay(300);
server.on("/", handleRoot); // 注册路由
server.on("/on", handleOn);
server.on("/off", handleOff);
server.begin();
Serial.printf("访问: http://%s\n", WiFi.localIP().toString().c_str());
}
void loop() {
server.handleClient(); // 必须持续调用(非阻塞)
}
浏览器访问串口打印的 IP → 点「开灯」→ LED 亮。核心机制:路由注册 + loop 轮询 handleClient(延续非阻塞范式,一次请求处理几毫秒,不碍事)。
二、mDNS:告别记 IP
IP 地址记不住也容易变。mDNS 让局域网设备通过 espdiy.local 这样的域名互访(手机/电脑原生支持):
#include <ESPmDNS.h>
void setup() {
// ... Wi-Fi 连接成功后:
if (MDNS.begin("espdiy")) { // 初始化,域名 = espdiy.local
Serial.println("mDNS 已启动: http://espdiy.local");
}
MDNS.addService("http", "tcp", 80); // 广播 HTTP 服务
}
手机浏览器输入
http://espdiy.local直达。一个名字只对应一台设备——量产产品用序列号后缀(espdiy-a3f2)防撞名。 局限:仅同一局域网内有效;个别安卓浏览器对 .local 支持不完整,保留 IP 访问作为后备。
三、进阶:REST API + 网页 AJAX(现代做法)
上面的整页跳转是「网页 1.0」。物联网标准做法:网页静态托管 + REST JSON 接口 + AJAX 局部刷新:
// REST 接口:GET /api/status 返回状态 JSON
void handleApiStatus() {
JsonDocument doc;
doc["led"] = digitalRead(2) == HIGH;
doc["uptime"] = millis() / 1000;
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// REST 接口:POST /api/led 切换状态
void handleApiLed() {
digitalWrite(2, !digitalRead(2));
handleApiStatus(); // 返回新状态
}
void setup() {
// ...
server.on("/api/status", HTTP_GET, handleApiStatus);
server.on("/api/led", HTTP_POST, handleApiLed);
}
网页端(存为字符串常量或 SPIFFS 文件)用 fetch 调接口:
<button onclick="fetch('/api/led',{method:'POST'}).then(refresh)">切换 LED</button>
<span id="status"></span>
<script>
async function refresh() {
const s = await (await fetch('/api/status')).json();
document.getElementById('status').textContent =
s.led ? 'ON' : 'OFF' + ' · 运行 ' + s.uptime + 's';
}
setInterval(refresh, 2000); // 每 2 秒刷新
</script>
这个架构 = Home Assistant 面板、WLED 网页的原型——L3 的 ESPAsyncWebServer 是它的性能加强版。
四、L2 验收项目:完整动线
现在可以串起 L2 全部知识自检:
[Wi-Fi] 事件驱动连接 + 自动重连
↓
[mDNS] espdiy.local 访问入口
↓
[WebServer] 网页 + REST API
↓
[PWM/定时器/中断/ADC/I2C] 业务逻辑(呼吸灯、传感器轮询、按键)
↓
手机浏览器 → 控制/查看
全链路跑通 = L2 毕业 🎓
五、避坑速查表
| 症状 | 根因与对策 |
|---|---|
| 网页打不开 | server.handleClient() 没在 loop 调用 |
| mDNS 解析失败 | 手机连了 5GHz 而 ESP32 在 2.4GHz(部分路由器 AP 隔离);换 IP 直连验证 |
| 传参丢失 | POST body 默认不解析,用 server.arg("xx") 须配 server.on(..., HTTP_POST, ...) |
| 页面越长越卡 | HTML 字符串拼在闪存( PROGMEM / LittleFS );或换 AsyncWebServer |
| 响应时亮时灭 | loop 里有长 delay 阻塞了 handleClient(回看定时器篇) |
| espdiy.local 撞名 | 量产用序列号后缀 |
| 想要 HTTPS | 静态站可用 Cloudflare 反代;本地证书较重,L4 话题 |
六、延伸阅读
- 🧭 前作:Wi-Fi 第一课——客户端侧联网
- ⏭️ L3 方向:ESPAsyncWebServer(异步高性能)、MQTT(物联协议)、ESP-NOW(无路由器通信)
- 🧭 工具串联:WLED 固件源码就是本篇架构的工业级实现,可作参考阅读
版权声明:本文采用 CC BY-SA 4.0 协议授权。转载与二创请注明出处:ESPDIY 创客工坊 - espdiy.com
ESP32 Web Services Guide: WebServer, mDNS and REST APIs
L2 finale: Wi-Fi First Steps made the device reach the network; this guide makes it reachable — open a page in a phone browser, tap a button, control the LED. It’s the complete answer to the L2 acceptance project and the prototype for smart-home dashboards and web config UIs.
1. WebServer in three minutes
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80); // port 80
void handleRoot() {
server.send(200, "text/html", "<h1>ESPDIY</h1><a href='/on'>LED ON</a> <a href='/off'>LED OFF</a>");
}
void handleOn() { digitalWrite(2, HIGH); server.sendHeader("Location", "/"); server.send(303); }
void handleOff() { digitalWrite(2, LOW); server.sendHeader("Location", "/"); server.send(303); }
void setup() {
pinMode(2, OUTPUT);
WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
while (WiFi.status() != WL_CONNECTED) delay(300);
server.on("/", handleRoot); // register routes
server.on("/on", handleOn);
server.on("/off", handleOff);
server.begin();
Serial.printf("Visit: http://%s\n", WiFi.localIP().toString().c_str());
}
void loop() {
server.handleClient(); // must be called continuously (non-blocking)
}
Open the printed IP in a browser → tap “LED ON” → the LED lights. Core mechanism: route registration + handleClient polled in loop (continuing the non-blocking paradigm — each request takes milliseconds and disturbs nothing).
2. mDNS: forget the IP
IPs are forgettable and change. mDNS lets LAN devices address each other as espdiy.local (natively supported by phones and PCs):
#include <ESPmDNS.h>
void setup() {
// ... after Wi-Fi connects:
if (MDNS.begin("espdiy")) { // hostname = espdiy.local
Serial.println("mDNS started: http://espdiy.local");
}
MDNS.addService("http", "tcp", 80); // advertise the HTTP service
}
Type
http://espdiy.localin the phone browser. One name per device — production units use serial-number suffixes (espdiy-a3f2) to avoid collisions. Limits: same LAN only; some Android browsers handle .local poorly — keep the IP as a fallback.
3. Advanced: REST API + AJAX (the modern way)
Full-page links are “web 1.0”. The IoT-standard architecture: static page hosting + REST JSON endpoints + AJAX partial refresh:
// REST: GET /api/status returns the status JSON
void handleApiStatus() {
JsonDocument doc;
doc["led"] = digitalRead(2) == HIGH;
doc["uptime"] = millis() / 1000;
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
// REST: POST /api/led toggles the LED
void handleApiLed() {
digitalWrite(2, !digitalRead(2));
handleApiStatus(); // return the new state
}
void setup() {
// ...
server.on("/api/status", HTTP_GET, handleApiStatus);
server.on("/api/led", HTTP_POST, handleApiLed);
}
The page (as a string constant or a SPIFFS file) calls the API with fetch:
<button onclick="fetch('/api/led',{method:'POST'}).then(refresh)">Toggle LED</button>
<span id="status"></span>
<script>
async function refresh() {
const s = await (await fetch('/api/status')).json();
document.getElementById('status').textContent =
(s.led ? 'ON' : 'OFF') + ' · up ' + s.uptime + 's';
}
setInterval(refresh, 2000); // refresh every 2s
</script>
This architecture = the prototype of Home Assistant dashboards and the WLED web UI — L3’s ESPAsyncWebServer is its high-performance upgrade.
4. The L2 acceptance project: full chain
Now string together all of L2 for the self-check:
[Wi-Fi] event-driven connect + auto-reconnect
↓
[mDNS] espdiy.local entry point
↓
[WebServer] page + REST API
↓
[PWM/timers/interrupts/ADC/I2C] business logic (breathing LED, sensor polling, buttons)
↓
Phone browser → control & monitor
Full chain working = L2 graduation 🎓
5. Pitfall cheat sheet
| Symptom | Cause and fix |
|---|---|
| Page never loads | server.handleClient() missing from loop |
| mDNS doesn’t resolve | Phone on 5GHz while the ESP32 is on 2.4GHz (AP isolation on some routers); verify via IP |
| POST body “missing” | Body isn’t parsed by default — use server.arg("xx") with the right route method |
| Page gets sluggish | HTML strings eat RAM (PROGMEM / LittleFS); or move to AsyncWebServer |
| Intermittent response | A long delay in loop blocks handleClient (revisit timers) |
| espdiy.local collision | Use serial-number suffixes in production |
| Wanting HTTPS | Cloudflare reverse proxy for static sites; local certs are an L4 topic |
6. Further reading
- 🧭 Previous: Wi-Fi First Steps — the client side
- ⏭️ L3 directions: ESPAsyncWebServer (async performance), MQTT (IoT protocol), ESP-NOW (routerless comms)
- 🧭 Tool tie-in: the WLED firmware is the industrial-grade version of this architecture — excellent reference reading
License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com