恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
ESP32智能插座DIY:从硬件电路到Web上位机完整实战指南
首页
资讯中心
/
ESP32智能插座DIY:从硬件电路到Web上位机完整实战指南
ESP32智能插座DIY:从硬件电路到Web上位机完整实战指南
发布时间:2026/9/8 15:02:02
最近刚把家里一个普通的五孔插座改造成了带 Web 上位机的智能插座核心用的是 ESP32。说白了就是给插座加了一个“大脑”再把控制界面做成了网页手机、平板、电脑打开浏览器就能看到实时电压、电流、功率远程一键开关不用装任何 App。前前后后踩了很多坑从硬件选型、继电器驱动到固件里的 Web 服务器、网页端实时刷新整个过程值得好好记录一篇。如果你正准备做智能家居相关项目或者想把手里的 ESP32 玩出点实用价值这篇文章应该能帮你省不少弯路。整个项目一句话概括ESP32 负责采集电能数据和执行继电器通断同时内置一个轻量级 Web 服务Web 上位机通过 HTTP 和 WebSocket 与设备通信在浏览器里完成数据监控和远程控制。下面我按实际做项目的顺序把方案选型、硬件电路、固件设计、上位机界面和联调问题整个讲透。1. 整体架构与方案选型1.1 为什么是 ESP32做智能插座控制端的方案其实有好几条路可以走。第一类是最传统的组合普通 MCU比如 STM32 或 51加独立的 WiFi 模块比如 ESP8266。这种方案的好处是控制逻辑和通信分离万一 WiFi 模块挂了单片机还能继续执行定时开关之类的本地逻辑。但代价是硬件复杂度高两块芯片之间要定义通信协议占 PCB 面积成本也没有优势。第二类是蓝牙方案比如 Nordic 的 nRF52832 或者 ESP32 的 BLE 模式。蓝牙的好处是功耗低、配对简单但问题也很明显你要用手机 App 控制而且蓝牙的覆盖范围只有十米左右穿墙能力弱用起来局限很大。做插座这种固定安装的设备长期通电功耗本来就不是首要矛盾蓝牙的省电优势发挥不出来。第三类就是我这次选的 ESP32 单芯片方案。ESP32 本身集成 WiFi 和 BLE还有两个 Xtensa 处理器核心主频 240MHz片上有 ADC、UART、I2C、SPI、PWM 等外设SRAM 有 520KBFlash 正常配置 4MB 起步。做智能插座这种活它的资源绰绰有余一个核跑网络协议栈和 Web 服务另一个核处理计量芯片的数据读取和继电器逻辑互不干扰。选 ESP32 还有一个很实际的原因社区资料太丰富了。你遇到的每一个问题基本都有前人趟过坑找个参考代码很容易。而且 ESP32 系列现在价格也不贵WROOM-32 模组批发价十几块钱自己打样做几个完全能接受。1.2 Web 上位机为什么比 App 更合适智能插座的控制端常见的是三种形态手机 App、第三方平台比如天猫精灵、米家、自建 Web 页面。手机 App 的问题是开发门槛高。且不说 Android 和 iOS 两套代码就算用 Flutter 这类跨端框架你也要处理打包、签名、权限这些杂事。个人做的小项目为了一两个设备专门让用户装 App实在不划算。即使只给自己用每台手机都要手动安装调试一次装一次非常痛苦。第三方平台接入则有一个“原罪”——你的数据要过别人的服务器设备控制依赖云端路由器断网或者平台服务出问题插座就变回普通插座了。而且对接过程通常会有一套封闭的协议和认证流程你想在网页里自定义界面、加上电量报表、做点个性化的逻辑往往会被平台的接口能力卡住。Web 上位机天然避开了这些问题。浏览器是所有平台通用的“运行时”手机、电脑、平板都能访问不需要安装任何东西。ESP32 本身就能当 Web 服务器你不买云服务器、不备案域名也能在局域网内用得很好。更进一步用 PWA渐进式 Web 应用还能把网页“添加”到手机桌面点开就是一个全屏应用体验和原生 App 几乎一样这谁还去折腾签名打包从开发和维护成本来看Web 界面的迭代成本低到可以忽略。你改了前端代码重新上传文件或者直接 OTA 推给设备浏览器一刷新就是新版本。这一点在当前项目里帮助很大我前前后后改了三四版界面如果做原生 App光编译安装就够烦人。1.3 系统整体链路整个系统的数据流向是这样的交流电源进来先分两路。一路经过 AC-DC 降压模块变成 5V再稳压到 3.3V 给 ESP32 和继电器供电另一路经过电能计量芯片的采样电路ESP32 通过 UART 读取电压、电流、功率等数据。控制方向则是反过来的浏览器里的 Web 上位机发出开关指令通过 HTTP 请求到达 ESP32 上的 Web 服务固件解析指令后控制 GPIO 电平驱动继电器吸合或释放最终接通或切断插座输出。为了让页面上的数据“活”起来不能靠浏览器不断刷新页面而是用 WebSocket 建立一条长连接。ESP32 每隔 500 毫秒从计量芯片拿到一组新数据通过 WebSocket 推送到前端前端 JavaScript 收到后更新数字、绘制曲线这就是整个链路里最关键的一环。这个架构拆开看就三块硬件电路负责“感知和执行”ESP32 固件负责“处理和服务”Web 上位机负责“展示和控制”。后面每一章我都按这个边界来讲。2. 硬件电路设计与安全细节2.1 器件清单与选购注意我先列一个完整的物料清单这是打样之后精简出来的版本每个器件都有它的用途。器件型号/规格数量主要用途主控模组ESP32-WROOM-321核心主控电能计量芯片HLW8032 或 BL09421采集电压电流功率继电器5V 线圈10A 250VAC 触点1通断插座输出继电器驱动管SS8050 NPN 三极管或 AO3400 MOS1驱动继电器线圈续流二极管1N4148 或 1N40071吸收线圈反电动势光耦隔离PC817 或 EL357N1隔离强电侧干扰AC-DC 电源模块HLK-PM01输出 5V1220V 转 5VLDO 稳压AMS1117-3.315V 转 3.3V采样电阻锰铜电阻 1mΩ2W1电流采样分压电阻高精度 1% 电阻若干-电压采样保险丝250V 1A 或 3A 慢断型1过流保护压敏电阻14D471K1浪涌保护选购的时候有几个坑要提醒。第一ESP32 模组千万不要买老版本的 WROOM-32因为后续改版比较杂尽量选带 PCB 天线的 WROOM-32E 或者直接上 PICO兼容性更好。第二继电器一定要选带品牌的正规货宏发、欧姆龙都可以杂牌继电器触点阻抗大大电流下会发热甚至烧熔。第三HLW8032 是高精度计量芯片但市场上散新和拆机件很多最好在立创这类正规渠道买。2.2 继电器驱动与强电侧设计继电器控制是智能插座最容易出问题的地方。ESP32 的 GPIO 输出能力有限高电平也只能提供几毫安电流直接驱动继电器线圈根本拉不动必须加一级放大。我用的驱动电路很简单GPIO 接一个 1kΩ 电阻到 SS8050 三极管的基极发射极接地集电极接继电器线圈一端线圈另一端接 5V。继电器的线圈两端反向并联一个 1N4148 二极管正极接 5V负极接集电极。这个二极管很关键继电器断电瞬间线圈会产生一个反向电动势峰值可能到几十伏没有续流二极管的话这一下就能把三极管击穿严重的情况下还会顺着 GPIO 把 ESP32 干掉。我最初画板子的时候贪省事觉得三极管和二极管的位置无所谓结果实测的时候继电器经常出现“吸合不完全”的现象就是线圈电压不够触点接触电阻不稳定。后来用万用表量了才知道是因为二极管方向接反线圈一直处于半导通状态。这个细节新手特别容易踩。强电侧的布线更要谨慎。继电器触点一侧接的是 220V 交流和弱电侧之间必须保证足够的爬电距离。PCB 上的强电区走线宽度至少 1.5mm 以上强弱电之间开槽处理避免潮湿环境下爬电。火线进线一定要串一个保险丝推荐慢断型防止插座后级短路的时候烧坏电路板和电源模块。火线和零线之间并联一个压敏电阻雷击或者浪涌的时候能吸收一部分能量提高设备抗冲击能力。2.3 电量计量与校准电能计量方案上一开始想用纯 ADC 采样再自己算 RMS 值后来放弃了。因为交流电是 50Hz 正弦波要算准功率因数至少要每周期采几十个点ESP32 的 ADC 精度在底部有非线性区软件校准很麻烦最后出来的数据自己都没信心。换用专用计量芯片事情就简单了。HLW8032 内部集成了 ADC、参考电压和功率计算引擎外部只需要一个电流采样电阻和一个电压分压网络芯片通过 UART 把电压有效值、电流有效值、有功功率、功率因数这些数据直接输出固件只要做解析就行。电流采样用锰铜电阻阻值 1mΩ功率 2W。电流 10A 的时候采样电阻上的压降只有 10mV信号很小所以要尽量靠近计量芯片的采样引脚走线最好用地线包住避免干扰。电压采样用两个电阻串联分压。220V 经过一个 1MΩ 和一个 1kΩ 的电阻分压得到大约 220mV 的采样电压接入芯片的 VP 引脚。分压电阻的精度直接影响电压读数所以要用 1% 精度或者更好的电阻。校准是这个环节的重点。HLW8032 的寄存器里有一个能量寄存器是一个累计值但它和真实功率之间有一个比例系数这个系数由内部参考电压和外部采样电阻决定不同批次芯片之间会有偏差。我买了一个公牛排插功率计作为参考基准接一个 200W 的白炽灯现在不太好找了用电吹风冷风档也行对比 ESP32 读到的功率值和功率计显示的值算出校准系数写死在固件里。校准公式是这样的校准系数 标准功率 / 芯片测量功率然后把每次读到的功率乘以这个系数。注意这个系数一次校准之后只要采样电阻不换基本是稳定的。但如果换了继电器或者改了 PCB采样电路参数变了必须重新校准否则数据偏差会让你怀疑人生。2.4 电源设计与抗干扰电源是整个系统最容易被人忽略、但最影响稳定性的部分。ESP32 的 WiFi 工作时电流峰值可以到 300mA 以上瞬时从 5V 模块吸取大电流如果电源模块余量不足电压会被拉低轻则重启重则烧闪存。我用的是 HLK-PM01 这款 AC-DC 模块输出 5V最大电流 600mA纹波控制在可接受范围。5V 出来之后一路直接给继电器线圈供电另一路经过 AMS1117-3.3 降压成 3.3V 给 ESP32 供电。AMS1117 虽然是老掉牙的线性稳压器但胜在便宜、稳定压差 1.7V 的时候输出 3.3V5V 输入完全够用。AMS1117 前面加一个 10uF 电解电容后面加一个 100nF 陶瓷电容滤波效果基本够用。继电器动作的时候线圈吸合瞬间会产生一个几十毫秒的电流浪涌会在电源线上叠加很大的噪声。如果这个噪声串进 ESP32 的供电轻则 WiFi 掉线重则死机。我的解决办法是继电器线圈的正极不直接从 ESP32 的 3.3V 节点取而是从 5V 节点取利用 LDO 的压差做了一次自然隔离。光耦隔离这个点我最后还是加上了。GPIO 先过光耦再驱动三极管虽然电路上多了一点元件但强电侧的瞬态干扰没办法通过公共地窜到弱电侧系统稳定性提升非常明显。之前没加光耦的时候每次继电器吸合串口日志必出现一次乱码加了之后就干净了。3. 固件开发Web 服务器的搭建与业务逻辑3.1 开发环境与工程骨架开发环境我选的是 PlatformIO Arduino 框架。为什么不直接上 ESP-IDF两个原因一是 Arduino 生态的网络库和 WebSocket 库非常成熟开发效率高很多二是这个项目逻辑不复杂没有到非用 ESP-IDF 不可的程度。当然如果你是做量产产品我建议还是用 ESP-IDF内存管理更可控启动速度也更快。工程结构很简单esp32_plug/ ├── platformio.ini ├── src/ │ ├── main.cpp │ ├── power_meter.h │ ├── power_meter.cpp │ ├── web_server.h │ ├── web_server.cpp │ └── relay.h │ └── relay.cpp └── data/ ├── index.html ├── app.js └── style.cssdata 目录下的文件是网页资源需要单独烧录到 SPIFFS或者 LittleFS文件系统里。platformio.ini 里要注意设置好 Flash 大小和文件系统[env:esp32dev] platform espressif32 board esp32dev framework arduino board_build.partitions default_8MB.csv board_build.filesystem littlefs monitor_speed 115200分区表这里有个坑。默认的分区表给文件系统分配的空间很小我把分区改成 8MB Flash 对应的版本文件系统分了 3MB 左右这样网页资源可以做得比较从容不用死命压缩代码。3.2 REST API 接口定义Web 上位机和设备之间的控制通道我做了一组简单的 REST API。这些接口不需要很复杂但设计要清晰。我最终定了这么几个接口方法说明/api/stateGET获取插座当前状态开关状态、电压、电流、功率、电量/api/state/setPOST设置插座开关状态Body 为 {state: 1} 或 {state: 0}/api/togglePOST反转开关状态/api/rebootPOST重启设备为什么控制指令用 POST 而不用 GET这是基本的 HTTP 语义问题。GET 请求会被浏览器缓存还会被预加载放在一个按钮的 href 里用户点了一次之后重复刷新页面可能就会触发意外操作。POST 是“非幂等”的更安全也符合语义化风格。ESP32 侧用 ESPAsyncWebServer 实现异步处理的好处是不阻塞主循环。代码片段如下server.on(/api/state, HTTP_GET, [](AsyncWebServerRequest *request){ String json {; json \on\: String(relay.isOn() ? 1 : 0) ,; json \v\: String(powerMeter.getVoltage()) ,; json \a\: String(powerMeter.getCurrent()) ,; json \w\: String(powerMeter.getPower()) ,; json \kwh\: String(powerMeter.getEnergy()) ; json }; request-send(200, application/json, json); });实际返回的长这样{ on: 1, v: 228.6, a: 1.15, w: 263.4, kwh: 3.41 }写 API 的时候注意一点返回的 JSON 不要带多余的空格和换行ESP32 的内存和带宽都有限链路越小越稳。3.3 WebSocket 实时推送我的页面里有一个功率实时曲线如果靠前端每隔一秒轮询一次 /api/stateESP32 的并发能力有限多个客户端同时轮询的时候会响应不过来CPU 占用率飙升。更推荐的方式是 WebSocket。WebSocket 建立之后ESP32 主动把数据推给浏览器前端不需要发请求只需要监听消息。我一直保持 500ms 推一次的频率在浏览器里看曲线基本是平滑的感觉不到延迟。服务端代码核心就两块第一块是处理连接建立和断开webSocket.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len){ if (type WS_EVT_CONNECT) { Serial.printf(Client #%u connected from %s\n, client-id(), client-remoteIP().toString().c_str()); } else if (type WS_EVT_DISCONNECT) { Serial.printf(Client #%u disconnected\n, client-id()); } });第二块是定时广播数据。在主循环或者定时器里void broadcastData() { String json buildDataJson(); webSocket.textAll(json); }WebSocket 的消息是 UTF-8 文本JSON 格式和 REST API 保持一致前端解析同一套逻辑省了不少事。这里有个细节ESP32 的 WebSocket 库默认会 ping/pong 保活但如果客户端断开的时候没有正常发关闭帧服务端这边不会立刻知道。所以我在前端做了断线检测网络一断就重新连接页面上的状态卡变成“离线”恢复之后自动同步数据。3.4 配网、鉴权与 OTA智能插座没有屏幕没有键盘第一次怎么连接家里 WiFi 是个问题。我的方案是首次上电之后 ESP32 进入 AP 配网模式自己开一个热点叫 ESP32-Plug-XXXX手机连上这个热点浏览器打开 192.168.4.1在配置页里输入家里 WiFi 的 SSID 和密码设备保存后自动连接。Arduino 生态里有现成的 WiFiManager 库封装好了这个流程直接用就行。鉴权这一点很多 DIY 项目会忽略。我自己家里用一开始也觉得没必要但后来想想局域网里万一哪个设备被植入脚本直接扫描网段找到插座POST 一下就能给你断电这不太好。所以我在 API 上加了一个简单的 Token 机制。设备生成一个随机 Token配网的时候绑定到浏览器Web 上位机每次请求都在 Header 里带这个 TokenESP32 校验通过才执行操作。OTA空中升级也是这个项目里很值得做的功能。固件做出来之后不可能每次改代码都插 USB 线。用 ArduinoOTA 库设备在局域网里会被 IDE 自动发现直接烧录不用接线非常适合这种装进墙里的设备。我的这个插座已经 OTA 升级过五六次固件了稳定性和便利性都比插线好太多。4. Web 上位机界面实现4.1 前端技术选型与资源规划上位机这个“上位机”在 ESP32 里其实就是一个网页应用。前端的选型有点讲究因为资源被 Flash 文件系统限制着。ESP32 的 Flash 空间虽然已经分了 3MB但也不能任意挥霍。我一开始用了 Vue 3 完整版再加上 Chart.js打完包之后一个 JS 文件 800 多KB传上去 ESP32 文件系统倒是能放得下但加载速度实在感人首次打开页面要四五秒体验很差。后来我把前端重构成了原生 HTML CSS JavaScript图表用的是轻量级的 uPlot体积不到 Chart.js 的五分之一整包压缩之后不到 200KB页面秒开。在资源规划这件事上我的建议是嵌入式前端尽量走“极简路线”。能用原生实现的组件就不用框架能用 CSS 画的就不放图片视频、动效这些能省则省。毕竟 ESP32 的 Web 服务器吞吐能力有限一个几十KB 的资源请求还没什么感觉一旦上兆多个客户端并发访问的时候网络栈很容易被拖垮。页面文件我是放在 data 目录下用 LittleFS 存储。ESP32 开机之后挂载文件系统当有浏览器请求根路径的时候直接返回 index.html。静态资源JS、CSS也一并从文件系统返回。4.2 页面结构与核心组件我的页面是三段式布局顶部是状态栏显示设备在线状态、当前时间中间是仪表区电压、电流、功率、今日电量四个数字卡片下面是一张实时功率曲线图底部是控制区一个大大的开关按钮加上本地开关的同步状态展示。仪表区用了四个数字卡片每个卡片上是一个单位标签加一个动态数值。功率数值的更新频率是 500ms 一次所以 JS 里处理数据更新的时候要注意避免引起整个页面的重排只更新对应元素的 textContent。功率曲线的实现我是用 uPlot 画了一个滚动时域图窗口显示最近 10 分钟的数据。前端维护一个数组WebSocket 每收到一条数据就 push 一个点超过窗口长度就 shift 掉旧点然后调用 uPlot 的 setData 更新曲线。uPlot 的性能很顶几百个点更新毫无压力。开关按钮是页面上最核心的控件我特意做了一个大的圆形按钮按下去会有明显的状态反馈开的时候是绿色关的时候是灰色。开关状态变化会立刻发 HTTP 请求到 /api/state/set但页面上的按钮状态要等服务器响应成功之后再变化这样可以避免因为网络延迟导致按钮状态和真实状态不一致。4.3 通信协议的设计前端和 ESP32 之间的通信协议我总结成了一套简单的 JSON 消息。上电之后前端会立刻建立 WebSocket 连接连接成功后发一条 get 请求服务端马上回一条完整数据然后进入 500ms 一次的推送模式。推送的数据结构定成这样{ type: data, on: 1, v: 228.6, a: 1.15, w: 263.4, pf: 0.99, kwh: 3.41, ts: 1713022345 }type 字段区分消息类型目前只有 data 一种但以后如果要加告警、配网状态就可以扩充。ts 是时间戳用于前端画图的时候确定时间轴。控制指令是前端发到服务端的{ type: set, target: relay, value: 1 }服务端处理完之后会广播一条状态变更消息给所有连接的客户端保证多设备同时打开页面的时候状态是同步的。这个设计在实际使用中特别有用我手机和电脑同时开着页面在手机上关掉插座电脑上立刻变成灰色按钮不用手动刷新。4.4 交互体验打磨整个界面做下来有几个交互细节我觉得特别值得分享。第一是开关按钮的“乐观更新”和“状态回弹”机制。我一开始是按钮点击之后立刻变色然后发送请求如果失败了再变回来。后来发现这样在弱网环境下会出现闪烁。改成点击按钮后进入“等待状态”按钮半透明等服务器确认后再改变状态如果超时或者失败按钮保持原样同时弹一个 toast 提示错误。体验好很多。第二是断线重连的提示。WebSocket 断开后界面不会立即变化因为 ESP32 那边可能只是暂时超时。我给前端加了重连逻辑断线之后每 2 秒尝试重连同时顶部状态栏变黄显示“连接中...”连续失败超过 10 次就变红显示“设备离线”并且在旁边放一个“手动刷新”按钮。这个设计的好处是用户自己心里有数是设备真没了还是网络抖了一下。第三是移动端适配。很多人会在手机上打开这个页面所以我用 CSS 做了响应式布局宽度小于 768px 的时候仪表卡片变成两列开关按钮放大方便手指点击。还加了一个 viewport meta 标签避免手机浏览器自动缩放页面。5. 联调、故障排查与经验总结5.1 常见问题速查表整个项目做下来我整理了一份问题速查表都是实打实遇到过的问题不是从网上抄的。问题现象可能原因解决办法ESP32 反复重启电源模块电流不够换 600mA 以上 AC-DC 模块或给继电器单独供电继电器吸合后不释放三极管驱动电路异常检查基极电阻和续流二极管方向必要时加下拉电阻Web 页面打开巨慢前端文件太大精简前端资源压缩 JS/CSS检查文件系统剩余空间WebSocket 频繁断开路由器 NAT 超时ESP32 侧增加心跳帧前端周期检测连接状态读到的功率偏大/偏小校准系数不对用标准功率计重新校准检查采样电阻阻值是否准确继电器动作时 WiFi 掉线电源被拉低继电器改用 5V 供电增加滤波电容加光耦隔离Flash 文件系统写入失败分区表错误烧录前确认分区表大小之后重刷文件系统镜像计量数据全是 0计量芯片 RX/TX 接反检查串口接线确认波特率匹配HLW8032 默认 48005.2 调试手段嵌入式项目的调试我的经验是“能打印就打印”。内部状态全部打日志只是日志要控制好级别别在正常运行时刷屏。串口日志是基本盘。我设置了几个日志级别INFO 打运行状态DEBUG 打数据细节ERROR 打异常。开发阶段全开部署之后只留 INFO 和 ERROR。ESP32 的 Serial 输出波特率设 115200日志带时间戳方便排查问题。网页端的调试主要靠浏览器开发者工具。Network 面板能看到每一次 HTTP 请求的状态码和耗时Console 面板能看到前端的报错信息Elements 面板可以直接改样式调 UI。WebSocket 的帧内容可以在 Network 面板的 WS 标签页里看到每次推送的数据都能检查。还有一个移动端真机调试的小技巧安卓手机用 Chrome 的远程调试功能USB 连接电脑后canary 版 Chrome 可以像调试桌面页面一样调试手机浏览器里的地址很方便。5.3 安全合规与扩展方向最后说点安全方面的事虽然 DIY 项目很难做到和量产产品一样的认证标准但至少要把自己能控制的做到位。强电部分必须有保险丝外壳不能用全敞开的 3D 打印件最好用有阻燃等级的 ABS 或 PC 外壳。我买的插座外壳是支持阻燃 V0 等级的打孔位置也尽量不影响绝缘间距。如果这个插座要长期跑 10A 以上的负载建议降额使用最多跑 8A。发热是个不可忽视的问题继电器触点、接线端子都会发热设计的时候要留余量。功能扩展方面这个项目其实只是开了个头。硬件上把 WebSocket 的数据再通过 MQTT 转发出去就能把插座接到 HomeAssistant 或者 Node-RED 里做成自动化联动比如湿度传感器检测到屋内湿度高自动打开排插上挂着的风扇。软件上可以加上定时任务和场景模式在 Web 界面里配置ESP32 本地执行断网也能用。再往后如果有多台同类型的智能设备可以做一个小网关统一收集数据集中展示那就是另一个更完整的“上位机系统”了。做这个项目最大的体会是一个简单的插座背后其实是一个从硬件到固件再到前端的完整链路任何一环出问题整个系统都跑不顺。但反过来看正因为它链路完整你才能在一次项目里同时学到电路设计、嵌入式开发、网络通信和前端展示的知识性价比很高。如果你也想动手做一个别犹豫先从搭一个最小系统开始把灯点亮把网页跑通再一步步往里加功能这个过程本身就会教给你很多文档里写不出来的东西。