家里十几个智能设备,手机里却装着七八个App,开关灯用一个、调空调用一个、看摄像头又得换一个。这种体验我相信折腾过智能家居的人都不陌生。ESP32这颗芯片火了好几年,原因很简单:它把WiFi和BLE两种无线协议集成了同一颗SoC上,价格却压到了十几块钱。这意味着你可以用一片ESP32同时搞定“设备上网”和“手机近场控制”两件事——网关、传感器、执行器一肩挑,不需要额外买昂贵的智能家居中枢。这篇博客我想把我实际搭建的一套基于ESP32的WiFi+BLE双模智能家居方案完整梳理一遍,从硬件选型、引脚规划、配网逻辑到固件实现,再把踩过的坑和排查技巧都摊开讲清楚,适合刚入手ESP32想做点实在东西的朋友,也适合已经被各种协议折腾到想回归简单的老手。
1. 方案架构与整体设计思路
1.1 为什么选ESP32而不是ESP8266或树莓派
既然要做智能家居,选型首先得把账算清楚。ESP8266价格更低、生态成熟,但它只有一个核、外设资源少,WiFi和BLE不能同时工作——它压根就没有BLE。树莓派性能强,跑个Home Assistant轻轻松松,但一块树莓派Zero也要小两百,还要配TF卡、电源、外壳,功耗和体积放在智能家居设备里都偏大,用它的场景更偏向做“中枢”而不是“终端节点”。
ESP32的位置正好卡在中间:双核240MHz的Xtensa处理器、520KB SRAM、4MB Flash起步的内存配置,WiFi 802.11 b/g/n和BLE 4.2双协议栈共存。最关键的是一颗ESP32模块(我用的是经典的ESP32-WROOM-32D,十几块钱)既能当终端设备,也能跑简单的TCP服务器或者MQTT客户端。实测下来,一片ESP32做一盏灯的控制板,再挂两三个温湿度传感器,负载非常轻松,Core 0跑WiFi协议栈,Core 1跑应用逻辑,互不干扰。
还有一个容易被忽略的选型理由:ESP32的ADC、I2C、SPI、UART、PWM、触摸传感器这些外设都是现成的。智能家居里最常见的传感器(DHT11/20、DS18B20、BH1750、PIR人体红外)和最常见的执行器(继电器、LED灯带、舵机、蜂鸣器),全部可以直接挂在ESP32上,不需要额外的单片机做信号预处理,BOM成本可以压缩得很薄。
1.2 双模协同:WiFi负责广域,BLE负责近场
很多人刚接触ESP32时有个误区,觉得既然有WiFi了,为什么还要加BLE?这不是多此一举,而是两套协议在智能家居场景里分工天然不同。
WiFi的优势是带宽大、能上互联网,缺点我也踩过:配网流程反人类、休眠功耗偏高。一个新设备买回家,第一步就要在手机App里找WiFi、输密码,如果路由器开了隐藏SSID或者5GHz/2.4GHz频段隔离,常会卡在配网半路上。BLE则完全不同,手机靠近设备就能广播,用户不用知道家里WiFi密码是什么就能先把设备激活。我的方案里把二者的角色定为:BLE承担“首次配网、近距离调试、低功耗唤醒通道”,WiFi承担“日常通信、云平台对接、局域网内互相控制”。开机后ESP32先启动BLE广播,用户在手机小程序里扫描到设备、填入WiFi凭据,ESP32拿到SSID和密码后连接路由器,连接成功后再把BLE广播关掉,进入纯WiFi工作模式。
实际上WiFi和BLE在ESP32内部共用2.4GHz射频前端,硬件上不可能真正同时收发,但乐鑫的共存机制(Coexistence)做得相当成熟,两者以时分方式切换,实测局域网内WiFi链路延迟掉到20ms以内,BLE的连接事件也能稳定维持。所以这个“各司其职”的设计在工程上是完全可行的,而且比用两颗芯片简单得多。
1.3 从功能模块拆解整体架构
我最终搭建的方案分为四层:
第一层是传感器和执行器层,包括DHT20温湿度模块、BH1750光照模块、PIR人体感应、一个两路继电器模块。这些外设挂在ESP32的GPIO上,属于底层的“手脚”。
第二层是ESP32主控层,负责采集传感器数据、执行控制逻辑、维护连接状态。设备固件里跑了一个轻量级任务调度器——其实就是FreeRTOS自带的任务机制,把不同的功能拆成独立任务,避免某个传感器I2C读取卡住时拖垮整个控制链路。
第三层是无线接入层,包含两个接口:BLE GATT服务用于近场配置和控制,WiFi TCP/HTTP接口用于局域网内访问,同时通过MQTT协议向本地的EMQX Broker上报状态。这样手机不在同一个局域网时,也能通过公网Broker中转拿到设备状态。
第四层是交互层,我写了一个内嵌在ESP32 Flash里的Web页面,所有静态资源用ESPAsyncWebServer提供,浏览器打开设备IP就能看到仪表盘,实时温湿度曲线和开关控制都在里面。用BLE做配网、Web页面做可视化、MQTT做远端下发,这套组合基本覆盖了一个简单智能家居节点从“激活”到“日常使用”的全链路。
2. 硬件设计与引脚规划细节
2.1 开发板选型与引脚避坑
市面上的ESP32开发板五花八门,但核心就两类:一类是带USB转串口芯片的通用开发板(比如经典的30引脚DevKitC),另一类是面向产品化的裸模块。做智能家居原型验证我建议直接选DevKitC形态的板子,带稳压电路、一键下载按键、MicroUSB口,插上电脑就能用Arduino IDE烧录,省去自己焊下载电路的时间。
但是引脚规划这里我有几条很实在的经验,都是拿时间换出来的。ESP32的GPIO不是每个都能随便用的,比如GPIO6到GPIO11连接着板载Flash,正常运行时绝对别接外设。GPIO12是MTDI引脚,上电时如果被拉高会导致芯片进不了下载模式,所以这个脚我宁可空着。GPIO16和GPIO17调试时经常用,但它们在模块内部接了PSRAM的话也不可用,具体看模块型号。我自己画引脚分配表时,会专门留一张文档记录每个引脚的用途,防止改接线时忘掉之前的定义。
还有一个经常翻车的地方是GPIO34到GPIO39是纯输入引脚,没有内部上拉,也不能输出PWM。如果某些教程让你把PIR传感器接到GPIO34,那没问题,但千万别拿它去驱动继电器。我实际项目中把PIR接在GPIO34上,读取数字信号完全正常,想输出电平控制蜂鸣器就换到了GPIO25。
2.2 供电方案与外设连接
智能家居设备通常直接使用USB 5V供电或者墙壁电源里的5V/3.3V,但接传感器时有个容易被忽略的点:ESP32开发板上的3.3V稳压器一般只有几百毫安的输出能力,如果同时给WiFi模块、DHT20、OLED屏幕、PIR供电,3.3V轨会在WiFi发包瞬间瞬间跌落,轻则传感器读数跳变,重则直接重启。
我的做法是分轨供电:USB的5V直接送给继电器模块(继电器线圈额定5V),3.3V轨只负责ESP32、传感器和逻辑电平部分。WiFi发射时板的瞬态电流能冲到300mA以上,所以我尽量选用输出电流足够的LDO或者直接依赖开发板自带的稳压器,外设电流太大时单独用AMS1117-3.3再从5V取一路。实测下来,同一路3.3V带ESP32+两个I2C传感器是稳的,如果再加上WS2812灯带这种吃电流的外设,就单独接5V并把数据线串一个330欧电阻。
接线方面还有几个细节:I2C总线上拉电阻必须有,但ESP32内部已经有了部分上拉,外部再并联4.7k电阻也可以,如果传感器模块已经有上拉则可以不加。DHT20这类I2C设备地址如果冲突,跳线帽可以改地址,但多个同一型号传感器并存时更省事的办法是直接用GPIO模拟I2C接到不同的引脚,或者换用单总线的DS18B20。这里没有绝对标准,核心是提前设计好地址分配,别等焊完线才发现冲突。
2.3 继电器、传感器与指示灯接线实战
我的执行端用了两路5V继电器模块,低电平触发。这里有一个一定要警惕的点:继电器模块的逻辑触发端不要直接接3.3V的GPIO就完事,很多模块上的光耦端其实是可以用3.3V驱动的,但如果你买的模块没有光耦隔离,继电器线圈工作时会有反向电动势,可能会干扰ESP32甚至把GPIO打坏。我都是买带光耦隔离的模块,同时在继电器线圈两端反向并联一个1N4007二极管,虽然模块上可能已经有了,但自己加一颗不亏。
传感器接线上,我把DHT20(I2C接口)挂到GPIO21(SDA)和GPIO22(SCL),BH1750同样挂在这条I2C总线上,地址一个0x38一个0x23,互不冲突。PIR人体传感器输出接GPIO34,采用脉冲模式,检测到人体时输出3.3V高电平,我配合一个100ms的软件消抖在固件里做过滤。指示灯用一个板载LED接GPIO2,逻辑和烧录时都会闪烁,方便肉眼判断状态。
给出一份我实际使用的接线表,方便直接抄作业:
| 外设 | 信号引脚 | ESP32 GPIO | 说明 |
|---|---|---|---|
| DHT20 | SDA / SCL | GPIO21 / GPIO22 | I2C接口,地址0x38 |
| BH1750 | SDA / SCL | GPIO21 / GPIO22 | 同一条I2C总线,地址0x23 |
| PIR人体感应 | OUT | GPIO34 | 仅输入,高电平触发 |
| 继电器1 | IN1 | GPIO25 | 低电平触发,控制客厅灯 |
| 继电器2 | IN2 | GPIO26 | 低电平触发,控制风扇 |
| 板载LED | LED | GPIO2 | 状态指示,全程可用 |
| 烧录模式 | EN/IO0 | 板载按键 | 需按住IO0再复位 |
3. 软件开发流程与核心实现
3.1 开发环境搭建和国内源加速
写固件时我用的Arduino IDE加espressif的ESP32开发板包。这里有个很影响体验的坑:在Arduino IDE的“开发板管理器”里搜索ESP32时,默认源在国外,下载速度和成功率都不稳定。解决方法是给Arduino IDE配置国内的高校镜像源,在“文件-首选项-附加开发板管理器网址”里填上镜像地址(比如清华的tuna镜像对应的json链接),然后重启IDE再安装。我这边实测镜像源的下载速度能快几倍,而且不容易中途断掉。
如果不用Arduino IDE,PlatformIO也是一个稳定选择。PlatformIO通过platformio.ini文件指定board为esp32dev,用framework-arduinoespressif32作为框架,依赖管理比Arduino IDE清晰得多。我的项目一开始用Arduino IDE方便快速验证,后面代码多了、要同时维护开发和生产两套配置时切成PlatformIO,版本管理更干净。
这里强烈建议把ESP32开发板包版本固定住,不要每次遇到问题就“最新版”。我遇到过几次乐鑫更新核心库后,某些依赖的第三方库API变化导致编译失败的情况。具体版本号可以在开发板管理器里看到,锁定在自己验证过的一个稳定版本上,项目能跑就尽量别动工具链。
3.2 固件整体代码结构与任务划分
项目代码基于FreeRTOS任务划分,主程序里面创建了三个任务:
void setup() { Serial.begin(115200); WiFi.mode(WIFI_AP_STA); // 启动蓝牙配网 startBLESetup(); // 启动本地Web服务器 startWebServer(); // 启动传感器轮询任务 xTaskCreatePinnedToCore(sensorTask, "sensorTask", 4096, NULL, 1, &sensorTaskHandle, 1); xTaskCreatePinnedToCore(controlTask, "controlTask", 4096, NULL, 1, &controlTaskHandle, 1); }WiFi在连接成功前工作在AP_STA模式,这样用户既可以扫描到设备热点,也能通过BLE收到配网信息。连接路由器成功后,我再把WiFi模式切换为STA,此时BLE配网服务会关掉广播,避免一直开着浪费功耗。
这里有一个重要的细节:BLE和WiFi任务都涉及射频,不能同时在两个核上跑,而蓝牙协议栈的Host层跑在Core 0的控制器里。所以我把蓝牙相关操作全部放在Core 0上,业务逻辑放在Core 1,避免优先级反转和硬件中断互相踩踏。
传感器任务用了一个状态机,每2秒读一次DHT20和BH1750。读取I2C传感器时要考虑首次上电或传感器异常时返回的脏数据,我的处理方式是连续三次读取失败才判定为故障,单次失败时使用上一次的缓存值,这样不会因为某次总线忙就误报高温。
3.3 BLE配网服务的GATT设计
BLE配网是整个方案里比较核心的环节。我在ESP32上初始化一个BLE服务,UUID用的自定义值(也可以直接在乐鑫示例代码基础上改),服务里定义了两个characteristic:一个用来接收手机发来的WiFi SSID和密码,另一个用来通知手机配网结果。
手机端我用微信小程序作为配网工具,实现起来不难:小程序里通过wx.openBluetoothAdapter初始化蓝牙,扫描外围设备时只过滤掉设备名含有“ESP32”前缀的设备,匹配后连接并监听对应characteristic的通知。配网流程我把SSID和密码按JSON字符串直接塞进一个characteristic里写入,比如“{“ssid”:“myhome”,“passwd”:“12345678”}”,ESP32收到后解包,调用WiFi.begin,然后在另一个characteristic里异步通知手机“connected”或“failed”。
用BLE配网的好处在于整个过程不需要用户进入手机系统设置去连热点,小程序内一步到位。而且BLE的通信距离足够近场使用,不存在WiFi热点配网时“连上了热点却上不了网”的尴尬。
我推荐在固件里给配网加一个超时机制:如果60秒内没有收到有效配网信息,ESP32自动恢复BLE广播并重新等待,避免设备卡在半配网状态。收到配网信息但WiFi连接失败时,也要复位BLE,让用户可以重新尝试。这个逻辑看似简单,却是实际使用中体验差异最大的地方,很多配网的失败都是死在“一次失败就不知道怎么恢复”上。
3.4 WiFi连接与本地Web控制页面
WiFi连接成功后,Web服务器开始工作。我用的ESPAsyncWebServer库,它基于异步TCP,不会阻塞主循环,配合SPIFFS或LittleFS存放Web静态资源。
Web页面里有设备状态卡片、温湿度曲线、继电器开关按钮。前端通过fetch接口轮询后端返回的JSON,比如GET /api/status会返回如下数据:
{ "temp": 26.5, "humidity": 58.2, "light": 430, "relay1": 0, "relay2": 1, "rssi": -51 }原来我考虑用WebSocket实现实时推送,后来发现局域网内HTTP轮询完全够用,500ms拉一次JSON对ESP32压力也不大,而且省去了维护WebSocket连接状态的复杂性。只有需要低延迟控制的时候(比如灯光亮度调节滑条),我发现HTTP请求的延迟也能稳定在20ms以内,WebSocket不是必需品。
这里也有一个安全层面的经验:局域网Web页面如果暴露到公网,务必加一层简单认证。我在ESPAsyncWebServer里实现了一个简单的HTTP Basic认证,用户名和密码硬编码在固件里,平时不开放公网映射。做智能家居控制页面最简单的原则就是:能局域网控制就别公开到公网,除非你做好了完整的身份认证和流量审计。
3.5 OTA升级与配置持久化
智能家居设备最大的特点是要长期运行,早期固件的bug不可能一次性休完,所以OTA在线升级是我在上线前就考虑进去的。ESP32支持通过HTTP下载bin文件升级,我实现了一个简单的升级页面:在Web服务器上添加一个/update接口,上传编译好的固件bin文件,调用Update API开始写Flash,写完后自动重启。
OTA升级对应的分区表必须在编译时调整:默认的Arduino分区表通常只有1.3MB左右的APP空间,而带OTA的分区表要同时容纳app0和app1两个固件槽。我在menuconfig里选择“Huge APP (3MB No OTA/1MB SPIFFS)”还是“16M Flash (3MB APP/9MB FS)”这类方案时,要根据自己实际Flash大小选。我用的是4MB Flash的模组,就给每个OTA槽分1.5MB左右,再留一点LittleFS空间存Web页面。
配置持久化我用Preferences库,把WiFi SSID、密码、设备名称这些东西存到NVS分区。配网成功后立即写入NVS,下次开机直接读取并尝试连接,如果没有有效配置才启动BLE配网。这一点对智能家居很重要:设备停电重启后不能每次都重新配网,不然家里老人根本用不了。
4. 实操实录:从烧录到联网的全过程
4.1 第一次烧录固件的正确姿势
买回开发板后,先把CP2102或CH340的驱动装上,这两个是最常见的USB转串口芯片。然后打开Arduino IDE,选择开发板为“ESP32 Dev Module”,烧录速度我一般选921600bps,但第一次用USB线连接可能不稳定,我建议先用115200bps把代码烧进去,之后再提速。
ESP32进入下载模式有两个办法:一是按住BOOT按键,点下载后再松开;二是上传代码时如果IDE自动控制不了复位时序,手动按住BOOT能保证进入下载模式。实测DevKitC板载的自动下载电路大多数时候没问题,但如果选错了开发板类型或串口占用后却下载失败,就手动按BOOT,这个辅助操作能解决八成下载失败。
接线全部接好之后第一次上电,最好先用一个简单的Blink程序验证板子没问题,再烧录完整的智能家居固件。我第一次就把传感器和继电器全部接好再烧固件,结果某个GPIO的高电平异常导致一直不断重启,最后挨个拔外设才定位到问题,这个弯路很浪费时间。正确流程是先烧最小系统、确认能跑,再加外设调逻辑。
4.2 配网实测过程与日志解读
配网阶段我打开手机微信小程序,扫描到名为“ESP32-HomeKit”的设备。这个设备名是在固件里通过bleAdvertising.setName配置的,方便识别多个设备。点击连接,填入我家的WiFi名称和密码,小程序提示发送成功,随后ESP32的串口打印出连接日志:
I (48241) wifi:new phy init I (48311) wifi:mode : sta (xx:xx:xx:xx:xx:xx) I (48882) wifi:connection established... I (48912) wifi:ip address: 192.168.1.134看到ip address出现,说明WiFi已经连接成功。这时再用浏览器访问http://192.168.1.134,就能打开内置的Web控制面板。
这些日志是通过USB串口看到的。实际设备部署之后没有电脑可看,我把关键状态又通过BLE上报了一份,手机小程序在设备连接后也能读取到当时的IP地址和WiFi信号强度,这个设计在排查设备离线问题时特别管用——设备没联网时用户还能通过BLE近场查看它的状态。
4.3 多设备联动的场景实现
单设备能联网控制只是第一步,智能家居最关键的是联动。我在这套方案里做了几个典型自动化场景,全部在ESP32本地固件里实现,不依赖云端:
场景一是“人来灯亮”:PIR传感器检测到高电平,如果当前光照强度低于200lx(傍晚或阴天),自动开继电器1;持续两分半钟没有再次检测到人,自动关灯。这里的逻辑很简单,但有一个容易忽略的点:继电器频繁开关会影响寿命,所以我在固件里做了最小切换间隔,无论PIR怎么抖动,同一分钟内只允许一次开关动作。
场景二是“联动报警”:当温度超过28℃且湿度超过75%时,继电器2自动开启风扇;等温度降到26℃以下再关,中间有1℃的滞回区间,防止风扇在临界点附近反复启停。
场景三是“远程开关”:通过MQTT订阅一个主题,比如home/bedroom/relay1/set,云端或者手机App下发0或1来控制继电器。同时设备订阅了自己状态的反馈主题,通过发布home/bedroom/relay1/state让其他系统实时感知。
这些自动化规则我存放在JSON配置里,在Web页面上可以调整阈值,用户体验比直接改固件好很多。固件内部用一个配置管理器把用户修改的参数保存在NVS中,重启后不会丢。
4.4 内嵌Web页面的前端实现要点
Web控制页面的代码不多,但有几个细节值得分享。首先页面全部静态文件放在LittleFS里,文件系统镜像需要先用Arduino IDE的ESP32 Sketch Data Upload插件烧录,否则Web服务器找不到HTML文件,访问IP会404。
前端代码我用的原生HTML+JavaScript,没有引第三方框架,因为引了jQuery、Vue这类库后文件体积很容易超过LittleFS的容量限制。用原生fetch轮询数据、用CSS做一个简陋但清晰的仪表盘,完全够用。字符串拼JSON时注意转义问题,避免把界面搞崩。
页面里的图表我用的是Canvas绘图,没有用ECharts,因为这个库体积太大。自己在Canvas上画一个简单的温湿度折线图并不难,还能直观显示最近两小时的变化趋势。实际上手写Canvas也就一百行左右的代码,比为了一个折线图塞进一个几百KB的库要划算得多。
5. 排查技巧与常见问题实录
5.1 下载不了固件的常规排查顺序
将ESP32插上电脑但Arduino IDE提示“A fatal error occurred: Failed to connect to ESP32”,这是最常见的报错。我遇到这个问题时,首先检查串口号是不是被其他程序占用,打开设备管理器看驱动是否正常,然后换USB线。这里有个很坑的点:普通手机充电线可能只有电源线没有数据线,看起来插上了,实际上串口根本没枚举。
如果驱动正常且串口可见,按住BOOT键不放再点上传,上传开始后再松手,这是最保险的进下载模式方法。如果还不行,就检查GPIO0是否被外设拉低了——我第一次接继电器模块时用了一个把GPIO0拉低的外设,导致芯片永远进不了运行模式。开发阶段尽量别在GPIO0上接任何东西,它是标准的下载控制脚,保持悬空最好。
5.2 传感器读数异常与I2C总线故障
DHT20或者BH1750读数飘忽,甚至返回65535或NaN,多半是I2C总线问题。检查接线时,SDA和SCL是否接反是一个高频错误。再加上ESP32的I2C引脚不是固定的,如果你在用Wire.begin(21,22)之后又在外部拉了别的引脚做Wire,那么通信会异常。
另一个常见问题是总线上有设备地址冲突。BH1750的地址可以通过ADDR引脚拉高或拉低切换,DHT20如果和别的I2C设备预留地址重复,就改地址跳线。我的排查方法是写一个I2C扫描程序,枚举总线上所有应答的设备地址,5分钟内定位问题:
#include <Wire.h> void setup() { Serial.begin(115200); Wire.begin(21, 22); Serial.println("I2C Scanner"); } void loop() { for (byte addr = 1; addr < 127; addr++) { Wire.beginTransmission(addr); if (Wire.endTransmission() == 0) { Serial.print("Found I2C device at 0x"); Serial.println(addr, HEX); } } delay(3000); }如果你发现扫描不到任何设备,先查上拉电阻。ESP32虽然有内部上拉,但某些第三方的传感器模块自带下拉,或者用了过长的跳线造成信号畸变。稳妥的做法是外接两个4.7k欧电阻到3.3V,这是I2C总线的标准做法。
5.3 WiFi频繁掉线或延迟飙高的成因
ESP32连WiFi后掉线,常见原因按优先级排查:
首先是电源问题。WiFi发射瞬间电流大,如果你的电源适配器质量差或者USB线太长,电压跌落会导致ESP32射频部分异常,表现为信号弱或者掉线重连。我遇到过用劣质USB线时,测电压只有4.4V,WiFi发送数据时电压降到4.0V以下,设备频繁重启。换一根粗短线就好。
其次是路由器设置了5GHz优先或者频段漫游。ESP32只支持2.4GHz,路由器如果开启“双频合一”,手机可能已经跑到5GHz了,ESP32还在2.4GHz挣扎,看起来信号满格但延迟很高。解决方法是把智能家居设备的2.4GHz SSID独立出来,关闭AP隔离、关闭WiFi节能模式,减少无谓的漫游扫描。
最后是WiFi与BLE的共存问题。如果设备在启用BLE广播的同时WiFi也不能断,比如我为了保留手机近场调试能力没有彻底停掉BLE,实测会发现WiFi丢包率上升。乐鑫官方建议非必要不要同时保持高强度BLE连接和WiFi吞吐,我的方案是在配网完成、进入日常工作模式后,把BLE的广播间隔拉长甚至完全关闭广播,只保留可被主动扫描发现的能力。这样既降低了干扰,也减少了功耗。
5.4 继电器误触发与GPIO状态抖动的处理
GPIO在复位期间会出现短暂的高阻态或电平浮动,如果继电器是低电平触发,而GPIO默认在复位时被外部电路拉低,设备一上电继电器就会闪断一次。很多人以为继电器闪断是固件bug,其实很可能是硬件电平在MCU启动完成前的“预触发”。
我的处理方式有两层:在继电器模块的触发输入引脚上并一个10k下拉电阻,让未定义状态期间默认是高电平(不触发);固件启动后先初始化GPIO为高阻输入,再配置为输出并立即输出高电平,再接逻辑。另外继电器模块如果是低电平触发,代码里一定要把默认状态设为HIGH而不是LOW,否则一上电就直接吸合。
我还遇到过继电器吸合瞬间导致ESP32重启的问题。原因是继电器线圈断电时反电动势干扰了电源。解决方法是继电器使用独立5V电源,同时靠近继电器电源引脚加一个100uF电解电容和0.1uF陶瓷电容吸收尖峰。这个电容组合便宜、效果好,做硬件时就别省这几毛钱。
6. 更多落地经验和后续扩展方向
6.1 功耗优化与电池供电可能性
其实我的这套方案一直用USB电源,因为家里不缺插座。但一旦想做成无线传感器节点、或者放到没有插座的角落,功耗就成为必须面对的问题。ESP32光是WiFi连上路由器后的平均电流就有80mA左右,如果一直开着,18650电池也只能撑一两天。所以做低功耗节点时的思路是:平时用深度睡眠(Deep Sleep)把电流压到10uA级别,定时醒来采集一次传感器数据,通过WiFi上报后马上再睡。
BLE在这里也能帮上忙:在深度睡眠期间保留一个极低功耗的唤醒源,比如外部GPIO触发的EXT0唤醒或者定时器唤醒。PIR检测到人时立刻唤醒ESP32,再连WiFi上报,这样既保证了响应速度,又不像WIFI常驻那样耗电。对智能家居里的门磁、人体感应这类节点来说,一节18650撑半年到一年是可以实现的。
6.2 多设备管理与本地化控制的组织方式
家里不可能只有一个ESP32。多个设备各自连同一个路由器,它们之间如何互相发现、如何统一管理,我也有一些实际做法。虽然地址也可以通过Web页面的设备列表填写,但更工程化的办法是让每个设备启动时向局域网广播自己的信息(用UDP组播),其他设备或者主控节点监听后自动维护一张设备表。这样不需要人工记住每个IP。
设备与设备之间的联动,我目前用的是MQTT在本地Broker中转,没有走云端。这个选择的理由是:本地Broker响应快、断网时本地自动化依然可以运转,而且隐私更好。你可以把ESP32当作一个MQTT客户端,订阅其他节点的状态主题,再结合规则做联动。和云平台方案比,多了一次搭建Broker的成本,但换回来的是稳定性和可控性,家用数量不多时这个搭设成本可以接受。
6.3 和我踩过的那些坑说再见
最后聊几个散碎但对体验影响很大的点。第一个是串口波特率:ESP32默认的log波特率是115200,如果你改了monitor_speed却没改代码里的Serial.begin,终端会乱码。调试时我习惯统一成115200,很好记。
第二个是Flash分区表的坑:如果编译时内存或者文件系统空间不够,报错PSTR not in RAM这种看不懂的信息,先检查分区表。很多人喜欢把分区表改成最大APP大小,结果LittleFS的空间就没了,Web页面资源烧不进去。分区表的选择一定要符合实际需求:有OTA就选OTA分区,有文件系统就留文件系统。
第三个是LED指示灯的控制逻辑:我在GPIO2上接了状态灯,但GPIO2是板载Flash的SPI-CLK引脚之一(实际连接了Flash),在启动阶段会有特殊电平变化,导致它接的LED会闪两下。不要把这当成故障,如果你在GPIO2上接了其他负载,需要确认它不会影响启动。
第四个是配置持久化的粒度:不要每次保存配置都往NVS里写一堆键值,NVS有擦写寿命限制。我采取的方式是只有配网成功或用户主动点“保存设置”时才调用Preferences的end和put操作,运行时参数全部放内存里,这样可以把NVS擦写频率降到极低。
6.4 这套方案的下一步演变
从“单设备控制”往“全屋场景”走,我有几个已经验证过可行的扩展方向。如果你手头的ESP32节点多了,可以考虑写一个Android或者iOS的小App,而不是每次都开浏览器输IP。其实控制端用WebView套壳把Web页面包进去,就能省掉一次原生开发的功夫。
另一个方向是把ESP32接入Apple HomeKit或者米家生态,这一步需要借助Home Assistant或者自定义的HomeKit Accessory Protocol实现,复杂度比现在这套方案高不少,但做出来后用户可以用系统自带的家庭App直接控制这些DIY设备,交互体验会上一个大台阶。乐鑫官方已经提供了ESP-HomeKit SDK的参考实现,我之前评估过,可行但投入时间不少,适合作为进阶项目。
再一个是把传感器数据周期性推送到一个轻量级时序数据库(比如InfluxDB),配Grafana做可视化面板。ESP32本身存储和算力都不适合做数据历史,但上报给服务器后,全屋的温湿度、用电量变化都可以变成分析图表,后续还能结合简单的算法做异常检测。我目前已经把自己的温度数据接入到了本地InfluxDB,跑了两周,曲线非常稳定,这套数据链路非常适合当作智能家居数据体系的地基。
我个人在实际使用中最大的体会是:ESP32这套WiFi+BLE双模方案并不是单纯省了一颗蓝牙芯片的钱,而是给设备的交互方式留出了极大的灵活性。配网靠BLE、控制靠WiFi、排查靠BLE近场调试,三种场景各取所长,整个系统的易用性比纯WiFi方案高出一个档次。如果你也想从零搭一套属于自己的智能家居节点,别急着堆功能,先把一块ESP32、一个继电器、一个温湿度传感器玩明白,把配网、控制、上报这条链路走通,然后再慢慢往里边加场景、加联动。这才是最稳、也最有成就感的一条路。