过去一年我把家里的灯光、风扇、阳台浇花和设备用电状态全部统一到了一套系统里。最初用的是 ESP8266 做 WiFi 控制,再单独接一个 HM-10 蓝牙模块做近场控制,接线乱、代码绕,出了网络问题还分不清是路由器的锅还是模块的锅。后来把所有核心节点换成 ESP32,一个芯片同时跑 WiFi 和 BLE,才真正体会到“一站式”三个字的分量:配网、上报、远程控制、离线兜底,全都塞进了一块不到三十块的开发板里。
这篇文章记录的就是我用 ESP32 从零搭建 WiFi+BLE 一站式智能家居方案的全过程。适合手里已经有 ESP32 开发板、想把自己家做成可远程控制又不完全依赖外网的人;也适合玩过 ESP8266 但被配网和断网问题折腾过的朋友。下面我会从芯片选型、系统架构、WiFi 链路、BLE 链路、双模协同、实测节点、踩坑经验这几个维度逐个展开,最后会给出一个能直接照抄的温湿度传感器加继电器开关节点方案。
1. 为什么是ESP32,而不是树莓派、STM32或ESP8266
动手之前先讲选型,这是整个项目最值得花时间想清楚的一步。很多智能家居项目一上来就选树莓派做主控,或者用 STM32 再外挂一个 WiFi 模块,这些方案都能跑,但放在“分布式节点”场景里性价比真的不高。
1.1 一个芯片同时搞定WiFi和BLE
ESP32 最核心的价值是双模通信能力。它内部集成了 2.4GHz WiFi(802.11 b/g/n)和蓝牙 4.2/5.0,同时支持经典蓝牙和低功耗蓝牙 BLE。也就是说,一颗芯片就同时具备局域网接入、互联网访问、近场低功耗通信三种能力。对于智能家居里的传感器节点和开关节点,WiFi 负责远程上报和联网控制,BLE 负责配网、近场控制以及断网兜底,两者正好互补。
以前用 ESP8266 做 WiFi,再外挂一个蓝牙串口模块,两套固件各管各的,数据还要通过 UART 转发。遇到电平不匹配、波特率不一致、蓝牙模块供电电流不够,问题一个接一个。换成 ESP32 之后,WiFi 协议栈和 BLE 协议栈跑在同一片 SoC 上,共享同一份传感器数据和控制状态,代码里直接调用两个库,根本不需要物理接线。这是“一站式”的物理基础。
1.2 和树莓派、STM32、ESP8266的对比
不同方案的取舍点,直接看对比表更清楚。
| 方案 | 通信能力 | 适用场景 | 主要短板 |
|---|---|---|---|
| 树莓派 | 自带WiFi/BLE,性能强 | 做网关、跑Home Assistant、跑MQTT broker | 价格高、体积大、功耗高、开机慢,不适合做大量分布式节点 |
| STM32 + 外挂WiFi模块 | 需外部模块 | 强实时控制、工业环境 | 开发链路长,要自己处理协议栈移植,国内入门门槛高 |
| ESP8266 | 只有WiFi | 纯WiFi传感器节点 | 单核、无原生BLE、GPIO少,配网和断网兜底要做得很费力 |
| ESP32 | WiFi+BLE,双核 | 智能家居节点、网关、带屏交互设备 | 2.4G频段拥堵时需要调参,裸板功耗不如纯BLE芯片低 |
单独看 ESP8266 和 ESP32,价格差距可能只有十块钱左右,但 ESP32 双核跑起来更从容,WiFi 连接的同时还能响应 GPIO 中断和 BLE 事件,不用像 ESP8266 那样处处担心资源不够。STM32 优势在实时性和外设丰富,但如果你不是做电机控制这类硬实时任务,ESP32 的125MHz双核已经能覆盖绝大多数智能家居节点需求。
1.3 选型结论
我最后定下来的方案是:中心网关用一台旧电脑或树莓派跑 Home Assistant 和 MQTT broker,负责消息中转和自动化;边缘节点全部用 ESP32,自己完成传感器采集、继电器输出、WiFi/蓝牙通信。网关挂了不影响本地控制,WiFi 断了还有 BLE 能应急。每个 ESP32 节点就是一个小小的自治终端,不需要像树莓派那样伺候系统、担心SD卡损坏。选型确定之后,剩下的事情就是搭数据链路。
2. 整套方案的路由:传感器、控制端和通信协议怎么排
很多教程一上来就写代码,结果读者做出来的东西只是“能用”,真出了问题不知道怎么排查。我习惯先画一张逻辑图,把数据从哪里来、到哪里去、经过什么协议整理清楚,再做代码实现。
2.1 设备端的分层结构
我把 ESP32 节点里的程序分成四层:感知层、控制层、通信层、应用逻辑层。感知层负责读 DHT22 温湿度、人体红外、门窗磁等传感器;控制层负责继电器、PWM调光、蜂鸣器等输出;通信层把 WiFi 和 BLE 封装成统一的接口,提供“上报数据”“接收命令”两个能力;应用逻辑层则是诸如“温度超过30度就开风扇”“湿度低于50%就启动加湿器”这类本地规则。
这个分层的意义在于,通信方式以后想换,只需要改通信层。比如我今天用 MQTT,明天想改成 HTTP 轮询,上层代码的reportData()函数名不变,内部实现替换即可。实际项目里,我还把传感器读数和通信发送放在不同任务中,I2C 或单总线读取慢不会阻塞 WiFi/BLE 的事件处理。
2.2 MQTT、HTTP、BLE三种通道的角色分配
WiFi 通道下,HTTP 和 MQTT 都可以用,但我强烈建议主控用 MQTT。HTTP 是请求-响应模式,ESP32 做服务器要自己维护一堆连接,客户端做轮询功耗也高。MQTT 是发布-订阅模式,服务器只需要维护一个长连接,消息可以实时推送,Home Assistant、手机 App、Web 面板都能同时订阅同一主题。
BLE 通道则承担两个任务:一个是 WiFi 配置,一个是断网兜底控制。配网时手机直接和 ESP32 建立 BLE 连接,把 WiFi 的 SSID 和密码写进特征值;正常运行时,BLE 保持低功耗广播,等手机靠近后可以直接查状态或者发控制指令,不依赖路由器。严格来说,BLE 不适合做大量数据上报,但做控制指令吞吐量完全够用。
2.3 控制端形态选择
控制端我前后试过三种:自己写 Android App、微信小程序、直接在 Home Assistant 里配卡片。结论是:不要重复造轮子。自己写 App 维护成本太高,还要处理手机通知推送和各种尺寸屏幕;小程序适合分享给家人,但需要服务器域名和审核,调试周期长。Home Assistant 是现成的开源平台,MQTT discovery 功能能自动发现新节点,配合自带的前端卡片,半小时就能做出一个非常专业的控制面板。
如果你不想引入 Home Assistant,至少也用一个支持 MQTT 的客户端做调试,比如手机上的 MQTT Tool,能把每个主题的消息看得明明白白。别跳过这一步,调试拓扑远比调代码更花时间。
2.4 一条完整的数据链路
举个例子,我卧室的温湿度节点上线流程是这样的:ESP32 冷启动,读取 DHT22 得到温度和湿度,通过 MQTT 发布到home/bedroom/temperature和home/bedroom/humidity,同时把自身状态发布到home/bedroom/status。Home Assistant 订阅这些主题,手机上的控制页面就会实时更新数据。如果我在页面上点“打开风扇”,Home Assistant 会向home/bedroom/fan/set发送ON,ESP32 的 MQTT 回调收到后解析、拉高继电器引脚,随后发布home/bedroom/fan/state回报结果。这条链路里每一步都只有一两个功能模块,排查起来很直接。
3. WiFi通道落地:配网、断线重连、远程控制
WiFi 通道是我所有节点的主干道,实现内容无非三块:配网、连接管理、数据交互。但每一块都有不少坑,尤其是配网方式选错,后面的体验会很糟糕。
3.1 SoftAP配网流程
ESP32 的配网方式有很多,最古老的是 SmartConfig:手机和 ESP32 连同一个路由器,手机把 SSID 和密码编码进 UDP 广播包,ESP32 抓包解码。原理听起来很妙,实测在多个 AP、AP 隔离或者手机数据流量开着的情况下,成功率飘忽不定。我后来统一改为 SoftAP 配网:ESP32 上电后自己开一个名字为ESP32-Setup的无线热点,手机连上这个热点后,浏览器打开 192.4.1 就能看到配置页面,填好家里 WiFi 的账号密码,ESP32 收到后切换成 STA 模式接入路由器。
SoftAP 的代码实现非常简单,核心就是 WebServer 处理表单:
#include <WiFi.h> #include <WebServer.h> #include <Preferences.h> const char* apSSID = "ESP32-Setup"; const char* apPass = "12345678"; WebServer server(80); Preferences prefs; void handleRoot() { String html = "<html><body><h1>ESP32 WiFi Config</h1>" "<form action='/save' method='POST'>" "<label>SSID</label><input name='ssid'><br>" "<label>Password</label><input type='password' name='pass'><br>" "<input type='submit' value='Save'></form></body></html>"; server.send(200, "text/html", html); } void handleSave() { String ssid = server.arg("ssid"); String pass = server.arg("pass"); prefs.begin("wifi", false); prefs.putString("ssid", ssid); prefs.putString("pass", pass); prefs.end(); server.send(200, "text/plain", "Saved, rebooting..."); delay(200); ESP.restart(); } void setup() { WiFi.mode(WIFI_AP); WiFi.softAP(apSSID, apPass); server.on("/", handleRoot); server.on("/save", handleSave); server.begin(); } void loop() { server.handleClient(); }有人可能觉得开热点配网太原始,但它的好处非常实在:不需要额外的 App,任何手机浏览器都能操作;不依赖路由器环境,网络再复杂也能用;而且保存到 Preferences 之后重启就能直接自动连网,用户可以反复配网覆盖旧配置。我在多个节点上用了大半年,没有一次配网失败。
3.2 本地Web控制面板
配网完成之后,ESP32 还可以继续保留一个本地 Web 服务器,用来做调试和手动控制。这个页面不需要好看,能用就行:一个按钮控制继电器,一个文本框显示当前传感器读数。这样在没有 Home Assistant 的情况下,手机连上同一 WiFi,直接在浏览器访问 ESP32 的 IP 就能操作,非常方便。
注意不要让 WebServer 和 MQTT 冲突。我习惯让 WebServer 只在 AP 模式下启用,STA 模式下只跑 MQTT,减少本地 HTTP 请求和云端长连接的资源竞争。如果你想在 STA 模式下也开放 Web 端口,记得加一个简单的 Token 认证,否则局域网里任何人都能控制你的设备。
3.3 用MQTT接入Home Assistant等平台
MQTT 接入是 WiFi 远控的正路。ESP32 上我用的库是 PubSubClient,选它不是因为功能强,而是因为轻、稳定、文档多。核心逻辑是:连接 broker,订阅控制主题,循环loop()保持心跳和处理消息。
#include <PubSubClient.h> #include <WiFi.h> WiFiClient espClient; PubSubClient mqtt(espClient); void mqttCallback(char* topic, byte* payload, unsigned int len) { String msg = ""; for (unsigned int i = 0; i < len; i++) msg += (char)payload[i]; if (String(topic) == "home/bedroom/fan/set") { if (msg == "ON") { digitalWrite(FAN_PIN, HIGH); mqtt.publish("home/bedroom/fan/state", "ON"); } else if (msg == "OFF") { digitalWrite(FAN_PIN, LOW); mqtt.publish("home/bedroom/fan/state", "OFF"); } } } void reconnectMQTT() { while (!mqtt.connected()) { if (mqtt.connect("ESP32-Bedroom")) { mqtt.subscribe("home/bedroom/fan/set"); mqtt.subscribe("home/bedroom/switch/set"); } else { delay(3000); } } }Topic 的命名要用一种一致的结构:home/{房间}/{设备}/{动作}。动作用set表示下行命令,state表示设备上报状态,temperature、humidity表示传感器数据。这样做的好处是 Home Assistant 的 MQTT discovery 可以自动生成实体,不需要手写 YAML 配置,设备上电后直接在前端页面出现。
3.4 断线重连与看门狗
WiFi 断线是智能家居项目里最容易出现、也最容易被忽略的问题。ESP32 默认的WiFi.begin()只能保证第一次连接,路由器重启、AP 切换、DHCP 租约过期之后,连接并不会自动恢复。一定要在loop()里周期性检查状态:
unsigned long lastWifiCheck = 0; void checkWiFiConnection() { if (millis() - lastWifiCheck < 10000) return; lastWifiCheck = millis(); if (WiFi.status() != WL_CONNECTED) { WiFi.disconnect(); WiFi.reconnect(); } }我还给每个节点加了软件看门狗。ESP32 在极端情况下会死机,一个esp_task_wdt定时器加上任务内喂狗,比任何云端监控都靠谱。看门狗超时之前保留现场,方便复位后主动上报一次“设备重启”原因。
4. BLE通道落地:GATT服务设计、低功耗和蓝牙配网
WiFi 是一种“远程依赖网络”的技术,一旦路由器重启、光猫故障或者云端服务掉线,纯 WiFi 设备就成了一块砖。我加 BLE 通道的初衷很简单:给所有节点留一条不经过路由器的本地逃生通道。
4.1 为什么智能家居需要BLE兜底
智能家居最怕的不是 Wi-Fi 慢,而是“进不了门控制不了灯”。我在客厅装了一个纯 WiFi 的插座,路由器升级固件重启的短短三分钟,家里灯全灭,只能手动拔插头,非常狼狈。后来所有关键节点必须支持 BLE 控制,路由器再抽风也不影响最基本的安全照明。BLE 还有广播距离短、功耗低、配网安全可控的特点,非常适合做敏感配置传输。
4.2 自定义GATT服务
BLE 的通信模型是 GATT,设备作为 Server 暴露服务和特征,手机作为 Client 去读写。我每个节点定义两个核心特征:一个命令写特征,用于手机下发控制指令;一个状态通知特征,用于节点主动推送状态变化。UUID 最好是统一规划,不要随手复制网上片段。
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" #define CHAR_CMD_UUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e" #define CHAR_STATE_UUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e" BLECharacteristic *pCmdChar; BLECharacteristic *pStateChar; class CommandHandler : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string value = pCharacteristic->getValue(); if (value.length() > 0) { String cmd = value.c_str(); if (cmd == "ON") { digitalWrite(FAN_PIN, HIGH); pStateChar->setValue("ON"); pStateChar->notify(); } else if (cmd == "OFF") { digitalWrite(FAN_PIN, LOW); pStateChar->setValue("OFF"); pStateChar->notify(); } } } }; void setupBLE() { BLEDevice::init("Bedroom Node"); BLEServer *pServer = BLEDevice::createServer(); BLEService *pService = pServer->createService(SERVICE_UUID); pCmdChar = pService->createCharacteristic(CHAR_CMD_UUID, BLECharacteristic::PROPERTY_WRITE); pStateChar = pService->createCharacteristic(CHAR_STATE_UUID, BLECharacteristic::PROPERTY_NOTIFY); pCmdChar->setCallbacks(new CommandHandler()); pStateChar->addDescriptor(new BLE2902()); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); }注意CHAR_STATE_UUID必须加BLE2902描述符,否则手机收不到notify消息。这也是很多新手说“BLE 能写不能收”的常见原因。
4.3 用BLE做入网配置
BLE 配网的原理比 SoftAP 更适合需要批量配置的场景:ESP32 上电后进入配网模式,广播自己的蓝牙名称;手机 App 扫描到之后建立连接,通过一个专门的特征把 WiFi SSID 和密码写入;ESP32 收到后用这个凭证连接路由器,再把连接结果通过另一个特征返回,整个过程不需要手机切换 WiFi 网络。
我在项目里实现了简单的配网服务:定义CHAR_CFG_SSID和CHAR_CFG_PASS两个写特征,收到数据后保存到 Preferences,触发WiFi.begin(),再在CHAR_CFG_STATUS特征里写入状态码。App 端只需要一个最基本的 BLE 扫描和写到指定特征的能力,比定制一套 SoftAP 页面更省事,尤其是路由器和手机不在同一网段时优势明显。
4.4 低功耗策略与广播配置
很多人误以为 BLE 广播一直开着会特别费电。实际上,广播间隔设为 100ms 到 200ms 时,平均电流也就几百微安到几毫安,对于市电供电的节点根本不是问题;但如果你做的是电池供电的传感器,就需要把广播间隔拉长,甚至只在事件触发时开启广播。
我这里给出几个实际参数参考:广播间隔setMinInterval(1000)、setMaxInterval(2000),iOS 连接会有额外限制,连接间隔setMinInterval(12)(单位 1.25ms)左右,实测不会掉线也能接受。真正省电的关键是让 ESP32 进入esp_light_sleep状态,只在定时器唤醒或 GPIO 触发唤醒后处理数据再睡。此时 WiFi 也会断开,适合长时间无人值守的传感器节点。
5. WiFi和BLE协同工作:双模调度、断网降级与一站式体验
有了 WiFi 和 BLE 两条链路,接下来的问题是:让它俩同时工作并自动切换,而不是互相打架。这一步处理得好,才是真正的“一站式”。
5.1 双模并存的资源分配
ESP32 的射频是 WiFi 和 BLE 共用的,两个协议栈同时跑时,硬件会做时分复用。如果 WiFi 正在高速传输,BLE 事件就会被延迟;反过来,BLE 频繁中断也会拖累 WiFi 吞吐。实际经验是:不要让每个节点同时做大量 WiFi 上报和 BLE 数据流传输。我的做法是 BLE 只承担低频控制和配网,所有传感器数据统一走 WiFi/MQTT,这样双方基本没有冲突。
在代码层面,不要把server.handleClient()和mqtt.loop()都放在同一个delay()很长的循环里。ESP32 是双核,可以把 BLE 事件放在一个任务,WiFi/MQTT 放在另一个任务,或者至少保证每个loop()周期里两个库都能被轮询到。
5.2 断网降级:WiFi断开后BLE接管控制
我用一个简单的状态机管理链路状态:默认状态是“WiFi 模式”,ESP32 连路由器并通过 MQTT 上报。当WiFi.status() != WL_CONNECTED持续超过十秒,就认为 WiFi 链路失效,自动切换为“BLE 兜底模式”:BLE 服务始终打开并广播,手机扫描后可以直接连接读取传感器状态、控制继电器。WiFi 恢复后,节点自动重新回连 MQTT,并把断开期间的状态变化补发一次。
状态切换的伪代码不需要太复杂:
enum LinkMode { MODE_WIFI, MODE_BLE_FALLBACK }; LinkMode currentMode = MODE_WIFI; void updateLinkMode() { if (WiFi.status() != WL_CONNECTED) { if (currentMode == MODE_WIFI) { currentMode = MODE_BLE_FALLBACK; startBLEAdvertising(); } } else { if (currentMode == MODE_BLE_FALLBACK) { currentMode = MODE_WIFI; connectMQTT(); publishStatus("online"); } } }这个机制最大的价值,是把“断网体验”从“设备完全不可用”降到“切换到蓝牙继续控制”。家里来客人临时断网,至少灯能开能关,不用摸黑找手机开蓝牙。
5.3 配网状态机:从蓝牙到WiFi的完整切换
配网是用 BLE 还是 WiFi,最终要有一个明确的运行状态机:设备出厂/重置后进入 BLE 配网等待状态,手机通过 BLE 写入 WiFi 凭证后进入连接状态,连接成功进入正常运行状态,正常运行中遇到断网再进入 BLE 兜底状态。
状态名称和典型表现可以整理成一张表:
| 状态 | 触发条件 | WiFi行为 | BLE行为 | 用户可见表现 |
|---|---|---|---|---|
| 等待配网 | 首次上电/长按重置 | 关闭 | 不停广播配网服务 | 手机App可扫描到 |
| 正在连接 | 收到WiFi账号密码 | 尝试连接路由器 | 保持配网服务等待结果 | App显示连接中 |
| 正常运行 | WiFi连接成功 | 连接MQTT,收发数据 | 广播服务,响应控制 | 数据实时刷新 |
| 断网兜底 | WiFi断连超过阈值 | 定时尝试重连 | 全功能开放控制 | 手机蓝牙可控制 |
这套状态机实现了“BLE 配网、WiFi 使用、BLE 兜底”的闭环。用户买回一个设备,不需要在网页和蓝牙之间反复切换,App 和固件各司其职,整个流程是顺滑的。
6. 实测:一个温湿度传感器+继电器开关节点的完整样例
理论讲了这么多,落地才是关键。下面这个节点是我家里最普通的一个:卧室温湿度检测,外加一路继电器控制风扇。你可以直接照抄硬件连接和核心代码。
6.1 硬件清单与接线
硬件清单如下:
- ESP32 DevKitC V4 开发板一块
- DHT22 温湿度传感器一个
- 1路继电器模块(低电平触发)
- 5V/2A 电源适配器,USB 线或接线端子
- 若干杜邦线和洞洞板
接线表:
| 模块 | 引脚 | ESP32引脚 |
|---|---|---|
| DHT22 VCC | 5V 或 3.3V | 3.3V |
| DHT22 GND | GND | GND |
| DHT22 DATA | GPIO4 | 上拉10k电阻到3.3V |
| 继电器 VCC | 5V | 外接5V |
| 继电器 GND | GND | GND |
| 继电器 IN | GPIO16 | 低电平触发 |
特别注意继电器电源不要直接从 ESP32 的 3.3V 引脚取,否则吸合瞬间大电流会造成电压跌落,ESP32 直接复位。我用的是独立 5V 给继电器模块供电,只在信号线上用三极管电路控制,实测稳定很多。
6.2 代码实现的关键片段
核心代码逻辑分三块:读取 DHT22、通过 MQTT 发布数据、通过 BLE 和 MQTT 控制继电器。DHT 读取部分用常见的 DHT sensor library:
#include <DHT.h> #define DHTPIN 4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); void readAndPublish() { float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(h) || isnan(t)) { // 读取失败,跳过本轮 return; } mqtt.publish("home/bedroom/temperature", String(t).c_str()); mqtt.publish("home/bedroom/humidity", String(h).c_str()); }继电器控制封装成一个统一接口,MQTT 回调和 BLE 回调都调用它:
void setRelay(bool state) { if (state) { digitalWrite(RELAY_PIN, LOW); // 低电平触发 } else { digitalWrite(RELAY_PIN, HIGH); } mqtt.publish("home/bedroom/fan/state", state ? "ON" : "OFF"); }我在loop()里每 30 秒读一次温湿度并发布,命令响应则由 MQTT 和 BLE 回调直接触发,不阻塞主循环。
6.3 实测效果与调参记录
这个节点运行了两个月,几个关键数据值得记录:DHT22 的温湿度读数在读数间隔 30 秒时非常稳定,误差大约在 0.3 度和 1% 湿度;继电器从命令下发到引脚动作,MQTT 链路平均延迟约 100ms,BLE 直连控制延迟约 30ms,BLE 本地控制响应明显更快。WiFi 信号在室内中等距离时 RSSI 约 -55dBm,MQTT 心跳稳定。功耗方面,市电供电不需要刻意省电;如果改成电池供电,建议把 WiFi 断开、BLE 广播间隔拉到 1000ms,实测平均电流可以压到 10mA 以下。
7. 我踩过的坑和想提醒你的几个点
这部分是血泪经验,不写出来真的可惜。很多坑不是教程里查得到的,只有实际跑上几个月才会遇到。
7.1 天线布局与2.4G干扰
ESP32 的 PCB 天线区域非常敏感。我曾把一个节点放在铁质接线盒里,WiFi 信号从 -55dBm 掉到 -78dBm,BLE 广播也经常扫描不到。后来我把天线部分露出来,让开发板保持直立或悬空,信号立刻恢复。另外,2.4G 频段特别容易受 USB 3.0 外壳、大功率电源适配器干扰,节点附近尽量不要塞满金属外壳设备。实际项目里我统一用 3D 打印塑料外壳,效果明显比金属好。
7.2 BLE连接不稳定问题
手机系统会缓存 BLE 设备的 GATT 服务。如果你改了服务 UUID 或者服务结构,旧版本 App 有时还会按缓存去找旧服务,连接会一直失败。遇到这种情况,最简单的办法是到手机蓝牙设置里“忽略此设备”重新扫描,或者修改设备 MAC 地址。另一点是连接间隔设置太激进,手机容易断线,我最后用 30ms 连接间隔才在 iOS 上稳定下来。
7.3 电源与电压跌落
继电器和电机这类感性负载是 ESP32 的隐形杀手。继电器吸合瞬间电流尖峰,即使信号线看起来没问题,主控也会随机复位。解决思路有三个:继电器独立供电;在继电器模块的三极管输入端加 10k 下拉电阻;在 ESP32 的电源输入并联一个大容量电解电容或者一个 TVS 管防浪涌。我把这三个都加上之后,再没出现过继电器动作导致重启的问题。
7.4 OTA升级的注意项
给已经装入墙盒的节点做 OTA 很有必要,但千万别只留一条 WiFi 升级通道。有一次我新固件里误加了WiFi.mode(WIFI_OFF),设备升级后直接断网失联,最后只能拆墙取出板子重新烧录。从那以后,我所有节点都保留一个 BLE 入口,即使 WiFi 固件挂了,蓝牙兜底还能恢复配置。代码里我也加了一个上电后长按按键恢复出厂配置的功能,算是给后续维护留了一根保险绳。这个学费交得值,现在新节点上线前我都要先模拟一遍 OTA 失败后的恢复路径。