ESP32双模实战:WiFi+BLE智能家居架构设计与低功耗方案
1. 为什么是ESP32一块板子同时扛起WiFi和BLE到底值在哪1.1 双协议共存不是堆料是智能家居最省事的组合智能家居这块我最早是从ESP8266入坑的单WiFi方案做得再花哨配网和低功耗这两个问题永远绕不开。家里断一次网所有依赖云端的开关就全瘫了。后来试过NRF52832做BLE低功耗确实漂亮但要在它旁边再架一个网关把蓝牙数据转成WiFi这套桥接逻辑听着简单真做起来很烦。ESP32把WiFi和BLE放到一颗芯片里这不是堆料是给智能家居做减法同一个开发环境、同一份日志、同一套OTA就解决了在线服务需要WiFi、离线兜底和传感器续航需要BLE这个基本矛盾。我的经验是能一根线供电、需要远程访问的设备让它走WiFi稳定、带宽高、好管理需要电池供电、只上报几十字节数据的传感器让它走BLE续航有保障。真正要动手做一套智能家居方案别一上来就All in WiFi那是拿功耗换简单也别迷信BLE Mesh调试成本高到你想退货。先学会让WiFi和BLE各干各的再谈整合。1.2 选型优先级ESP32、ESP32-S3还是ESP32-C3好多朋友第一句就问这颗芯片够用吗其实更该问的是你选对型号了吗。ESP32家族在WiFiBLE这件事上差别挺大选错不单是钱的问题是整个方案的功耗和体积都受影响。型号WiFi蓝牙处理器适合场景ESP32802.11 b/g/n蓝牙4.2 BLE240MHz双核网关、多协议开发、调试点多ESP32-S3802.11 b/g/nBLE 5.0240MHz双核带屏、摄像头、本地语音ESP32-C3802.11 b/g/nBLE 5.0160MHz单核RISC-V低功耗节点、小体积模块这里有个很容易被忽略的点S3和C3都没有经典蓝牙。如果你只是做BLE那完全够但老ESP32的资料、例程、排错帖子最多新手阶段绝对推荐先用它跑通全套逻辑。我的实际分工是从ESP32网关和灯控节点起步等低功耗节点需求明确后才把传感器节点换成了C3。C3单核跑BLE广播和深度休眠完全没压力而且模块便宜焊在最小系统板上才能做成真正的纽扣电池传感器。1.3 这套方案能覆盖哪些智能家居场景这套WiFiBLE双模方案我目前在家里跑了小半年覆盖的场景很实用卧室床头灯用WiFi控制、走到门口时用BLE近场开灯客厅用一块ESP32做网关挂着DHT22和红外发射管负责温湿度采集、空调红外控制以及把各类BLE传感器的数据转上云门口门磁和一间储物间的温湿度传感器是C3低功耗节点电池供电靠BLE广播把数据送给网关。再往大里说这个架构还能扩展烟雾报警、漏水检测、宠物喂食器、人体存在感应。它不依赖单一云端也不会因为路由器重启就全家失联。对刚入门的人照这个架子搭第一个节点会很有成就感对想量产的人这套分层逻辑也基本能迁移到正式产品上。唯一建议是第一版先把灯开关门磁温湿度上报这三样做扎实不要贪多后面所有诡异问题都是从这三个基础场景里冒出来的。2. 整套方案的分层架构设备端、网关端、手机端各管什么2.1 三种典型组网方式别一上来就All in WiFi我见过太多人掉进既然ESP32有WiFi那所有设备都走WiFi不就好了的思维定式里。全WiFi直连确实是最简单的组网方式每个设备都有独立IP代码直接对接MQTT或HTTP调试也直观。可你真接上三四个电池供电的门磁就知道WiFi的功耗根本不是电池能扛的哪怕不传数据光跟路由器保持关联也是一笔不小的电流开销。第二种方案是BLE传感器加蓝牙网关低功耗节点只做广播或轻量连接由网关统一扫到后转为WiFi/MQTT上报。这种结构省电但网关会成为单点网关挂掉整屋传感器都收不到。第三种才是比较完整的分层思路——重要设备WiFi和BLE双链路并存WiFi为主做远程和OTABLE做近场控制、配网兜底。我现在用的就是执行器走WiFi、传感器走BLE、关键节点两条腿走路的混合结构既不复杂又保证了断网时基本操作不受影响。2.2 我这个项目的具体节点划分把架构落到节点上一共四类角色灯控节点卧室床头灯ESP32带动一路继电器。平时通过WiFi接收MQTT指令网络异常时BLE还能直接接收手机近场指令。网关节点客厅的ESP32自带DHT22和红外发射。它既是WiFi设备也是一个BLE中心负责扫描周边传感器广播、解析后转发到MQTT。传感器节点C3干簧管做门磁C3BME280做温湿度。平时深度睡眠每10分钟醒来广播一次广播完继续睡。手机端日常用Home Assistant App看状态和点按钮调试BLE用nRF Connect临时控制直接用浏览器访问设备内嵌Web页。这套划分的关键是别让网关和设备两种角色混在一起。一开始我图省事让灯控节点也顺手扫描BLE结果WiFi和BLE同时扫描板子又热又卡。后来严格把集中处理逻辑收口到网关其他节点只做自己的事稳定性提升一大截。2.3 消息格式与设备发现机制的设计智能家居设备多起来之后最怕的是每个设备一种自定义协议。我在项目最开始就定了统一的JSON消息结构所有WiFi设备收发都长这样{src:bedroom_light,type:switch,state:on,ts:1700000000,rssi:-52}src设备唯一ID。type数据类型比如switch、sensor、event。state业务字段灯就是on/off传感器就是具体数值。tsUnix时间戳。rssi信号强度排查用。MQTT主题也按统一规则来home/{device}/state是设备上报home/{device}/set是下发给设备。设备发现这块WiFi设备用mDNS广播自己的名字比如esp32-gateway.localBLE传感器则在广播包里塞一个固定前缀的厂商自定义UUID网关只处理白名单内的前缀避免被邻居设备的广播淹没。消息结构定得早后面接Home Assistant、写Web页面、做日志仪表盘都会省很多事。3. 环境搭建与硬件准备先把烧录和接线弄利索3.1 PlatformIO工程初始化与国内源配置工具链我强烈建议直接用PlatformIO而不是Arduino IDE。Arduino IDE装库虽然方便但项目一超过两个文件、依赖一多就乱。PlatformIO按工程管理依赖一份配置文件搞定编译、烧录、监视器还能直接切ESP32和C3两块板子不用来回换IDE。我的platformio.ini长这样[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 lib_deps bblanchon/ArduinoJson^6.21.4 knolleary/PubSubClient^2.8 me-no-dev/ESPAsyncWebServer^1.2.4国内下载espressif32平台包有时候会慢到怀疑人生我那时候的土办法是找离线包直接解压到PlatformIO的缓存目录或者配置本地镜像源加速速度能快一个量级。换Arduino IDE的人也类似板卡管理地址填官方JSON之后网络不好的时候同样需要镜像兜底。工具链这块我咬碎了牙强调一句装好环境后先烧一个Blink确认编译、烧录、串口输出整条链路通了再开始写应用。不然你会把环境问题和代码问题混在一起排查浪费大半天。3.2 核心外设接线与供电注意事项灯控节点接线不多但继电器最容易出幺蛾子。继电器模块我习惯选低电平触发、带光耦的那类IO口接GPIO26VCC接5VGND和ESP32共地。别指望用3.3V去推继电器的线圈大部分模块需要5V驱动强行上3.3V会出现继电器动作不稳定。而且继电器通断瞬间会有感应尖峰我在模块VCC和GND之间加了一个100uF电解电容再在IO口前串一个1k电阻肉眼可见地减少了复位问题。温度和红外部分BME280走I2CSDA接GPIO21、SCL接GPIO22地址默认0x76DHT22的DATA随便挑一个空闲GPIO但线一长就容易丢数据所以网关这类要求稳定的采集点我直接用了BME280。红外发射管不能直接用IO灌电流我加了三极管驱动GPIO4控制基极这样即使长时间发射也不会把芯片IO烧了。供电统一从12V适配器进先经过DC-DC降到5V给继电器和ESP32模块ESP32板载LDO再出3.3V给传感器。离别折腾共用USB供电再外接继电器一动作重负载电压跌落一次你就得跪着排查一个下午。3.3 烧录方式串口与OTA两套方案并行开发阶段我用串口烧录开发板自带CP2102驱动装好之后pio run -t upload一条命令走完。如果你用的是裸模块记得把EN引脚拉低再上电进入下载模式或者按住BOOT键插USB。串口监视器用pio device monitor波特率记得和monitor_speed保持一致。最常见的失败报错是Failed to connect to ESP32: Timed out别慌查三样串口驱动装没装、IO0是不是被拉低、有没有别的进程占着COM口。产品化之后串口烧录就不现实了一定得上OTA。我在所有节点都加了ArduinoOTA同时在Web管理页里留了HTTP固件上传入口。OTA固件里的密码千万别留空网上很多示例是空密码局域网内谁都能刷你的设备。另一个铁律OTA目标分区要留rollback空间新固件启动后5分钟内没有崩溃才确认过否则回退旧版本。失败率在量产阶段会从偶尔一次变成批量事故提前设计好回退才是正经做法。4. WiFi侧的实战实现配网、Web控制页、断网自愈4.1 三步搞定连接STAAP共存、自动重连、看门狗兜底WiFi连接看着简单写起来全是细节。我第一版只在STA模式连路由器结果换一个SSID就得重刷固件后来老老实实改成AP_STA共存正常情况设备连家里的WiFi如果检测到没配置过或者连不上就开一个ESP32-Gateway热点手机连上去直接配网配好的SSID和密码存进Preferences下次启动自动读。代码骨架是这样的#include WiFi.h WiFi.mode(WIFI_AP_STA); WiFi.softAP(ESP32-Gateway); WiFi.begin(ssid, password); WiFi.setAutoReconnect(true); // WiFi事件回调里记录最近一次断开/连接时间 WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) { if (event ARDUINO_EVENT_WIFI_STA_DISCONNECTED) { lastDisconnectMillis millis(); } else if (event ARDUINO_EVENT_WIFI_STA_CONNECTED) { lastConnectedMillis millis(); } });setAutoReconnect(true)只是让协议栈自动重连应用层并不知道网络什么时候恢复过。最稳妥的做法是在loop()里跑一个状态机如果超过5分钟没收到任何WiFi事件或者MQTT掉了重连超过10次直接ESP.restart()。我后来还把配网信息存到了NVS长按按键10秒清空配置恢复出厂这套逻辑到现在还在用。4.2 内嵌Web控制页不依赖App也能操作App越做越漂亮但我始终觉得设备的最后一公里必须是本地可控的。我在网关和灯控节点上都放了一个轻量Web页面不用装任何App浏览器输IP就能开关灯、看温湿度、触发OTA升级。这在自己家里是懒人福音在客户现场是救命的调试入口。用的库是ESPAsyncWebServer页面HTML和JS直接编译进固件用raw string保存#include ESPAsyncWebServer.h const char INDEX_HTML[] PROGMEM Rrawliteral( !DOCTYPE html html body h1Bedroom Light/h1 button onclickfetch(/api/on,{method:POST})ON/button button onclickfetch(/api/off,{method:POST})OFF/button /body /html )rawliteral; AsyncWebServer server(80); server.on(/, HTTP_GET, [](AsyncWebServerRequest *req) { req-send(200, text/html, INDEX_HTML); }); server.on(/api/on, HTTP_POST, [](AsyncWebServerRequest *req) { digitalWrite(RELAY_PIN, HIGH); req-send(200, text/plain, ok); }); server.begin();页面小的时候完全够用设备多了之后HTTP轮询会吃力我在网关里加了WebSocket状态变化直接推给页面按钮延迟体感在200ms以内。有一个细节提醒WebSocket断线重连的逻辑很容易写漏页面上要有心跳定时器发现断开就主动重新连接否则刷新一次页面或网络闪断UI就永远停在旧状态里了。4.3 MQTT接入与Home Assistant等平台对接设备联网的核心是MQTT我本地用Docker跑了一个Mosquitto三个容器配置起一个轻量broker如果你想省事公共broker也能用但我会建议至少在topic里加项目前缀别跟全世界的测试消息混在一起。PubSubClient这个库有一个人尽皆知但总有人踩的坑WiFi断线重连之后如果只是mqtt.connect()成功订阅的topic是不会自动恢复的必须在重连成功的回调里重新subscribe()。我的重连函数长这样WiFiClient espClient; PubSubClient mqtt(espClient); void reconnectMqtt() { if (mqtt.connected()) return; if (mqtt.connect(bedroom_light)) { mqtt.subscribe(home/bedroom/light/set); } }收到命令后在回调里解析JSON注意别在回调里做耗时操作digitalWrite没问题但如果要去处理文件系统或者重启WiFi就丢给一个标志位在loop()里再处理。对接Home Assistant时直接在homeassistant/switch/bedroom_light/config里发一条MQTT Discovery消息带上state_topic、command_topic、unique_idHA会自动把这个开关加入实体列表整个过程不需要写yaml。5. BLE侧的实战实现GATT服务、广播与低功耗策略5.1 GATT服务设计把灯、传感器、门磁抽象成特征值BLE这边一定要先把GATT结构想清楚再写代码。BLE世界是外设和中心两层模型我的灯控节点是外设手机或网关是中心。控制逻辑就抽象成一个自定义服务里的特征值读它拿当前状态写它下指令通知它推状态变化。UUID不要自造太长的版本我直接固定成FF01、FF02、FF03这种简单标识在前面统一加一个项目级base UUID。初始化代码的骨架#include BLEDevice.h #include BLEServer.h #include BLECharacteristic.h BLEDevice::init(BedroomLight); BLEServer *server BLEDevice::createServer(); BLEService *svc server-createService(5e12a001-0000-1000-8000-00805f9b34fb); BLECharacteristic *switchChr svc-createCharacteristic( 5e12a001-0001-1000-8000-00805f9b34fb, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE ); switchChr-setValue(off); svc-start(); server-getAdvertising()-start();真正干活的地方是特征值回调。继承BLECharacteristicCallbacks在onWrite里检查新值、执行IO再把状态写回特征值。千万别在loop()里轮询特征值的getValue()去判断状态那样CPU占用高不说实时性也很差。另外我还会加一个标准的Device Information服务暴露固件版本这样手机端扫到设备后能直接看到是哪一版固件调试省了猜版本的时间。5.2 用nRF Connect或手机App调试BLEBLE调试我推荐两个工具Nordic出的nRF Connect功能全、免费、还能看原始广播包iOS上LightBlue也顺手界面清爽。调试流程很简单手机开蓝牙nRF Connect里扫描能找到BedroomLight就说明广播出来了连接后进自定义服务读FF01看到off写on观察继电器动作。Android 12以上扫不到BLE是权限问题定位权限和附近设备权限都得给这坑我帮你们踩过了。连上之后立刻断开最常见的两个原因服务没有start()就开始广播或者广播里带的连接参数不合法。外设支持单个central连接手机断开后一定要在回调里重新启动广播否则这台设备从此就消失了。5.3 低功耗调优广播间隔、连接间隔与数据吞吐的平衡低功耗节点的核心是数据量和唤醒频率的平衡。我这边门磁和温湿度传感器按广播模式工作平时深度睡眠定时唤醒后采集一次数据发一个广播包然后esp_deep_sleep_start()继续睡。广播间隔从20ms拉到1s看起来只是参数变化功耗却能省一大截但门磁这种实时性敏感的节点延迟2秒可以接受烟雾报警就不能这么干。关键代码长这样#include esp_sleep.h esp_sleep_enable_timer_wakeup(600ULL * 1000000ULL); // 10分钟 esp_deep_sleep_start();实测下来C3 BME280的节点10分钟上报一次两节AA电池跑了差不多7个月如果改成一小时上报一次跨年问题不大。老ESP32做低功耗节点就惨了WiFi功放和蓝牙协议栈常驻开销会吃掉大半省电成果。功耗测出来才算数别用万用表脑补平均值我后来直接在供电线上串INA219采样电流把整个唤醒周期的电流波形拉出来看一个周期多少uAh一清二楚。6. 联调实测与那些文档里不写的坑6.1 双协议并存的信号干扰问题WiFi和BLE共用2.4GHz频段同板同时跑的时候真的会互相踩。我做网关时先只开BLE扫描广播接收率很高一打开WiFi MQTT上传BLE广播接收率立刻往下掉。最直观的实例网关在WiFi传大文件时BLE传感器上报丢包明显。软件上能做的事情不少把WiFi发射功率调低一档室内覆盖完全够用BLE的生存空间反而大了BLE广播间隔从20ms改成200ms减少和WiFi帧冲突的概率在做网关的节点上把BLE扫描窗口设成30ms、扫描周期200ms而不是一直扫描。结构上如果有得选让两个天线物理分开同一个板卡上分到两端比什么都管用。这块属于文档里不会写但实测一定会遇到的领域。6.2 重连风暴与多设备同时OTA设备数量一多最怕全屋断电又来电。第一次全屋停电实验我就翻车了家里7个ESP32同时上电全都在同一秒发DHCP、连MQTT路由器直接CPU打满一半设备连不上网。解决办法是让每台设备在开机后做1到60秒的随机延迟延迟种子从设备ID算出来保持每台设备固定但互相错开。多设备OTA比断电更刺激。我把固件一次性推给5个节点结果WiFi信道被挤爆3个成功、2个断在中间。后来改成升级窗口机制同一时间最多2台设备开放升级每台设备升级完先自检自检不过自动回退。你要是家里设备不超过10个最简单也最稳的做法是一台一台来别拿生产环境的思路去赌路由器。6.3 实测功耗数据与后续优化空间给别人看方案我觉得光说架构太虚贴一组功耗数据最实在。下面这组数字是我在自己这批板子上实测的不同开发板差异很大尤其板载稳压器、LED指示灯都会影响结果但量级可以参考节点工作模式平均电流参考供电网关WiFiBLE扫描MQTT常开90~130mA 5V12V转5V灯控WiFiBLE继电器OFF常开65~110mA 5V独立5V传感器C3深度睡眠10分钟周期唤醒平均不到0.1mA两节AA网关和灯控这块的优化空间其实很大关掉板载LED换低静态电流的LDO开启modem sleep待机电流能再下来一点。真要做电池供电的WiFi设备就要面对现实WiFi常连状态下的电流是BLE的几十倍设计产品的时候把协议选型放在第一位。后面我打算把传感器的BLE广播改成连接notify模式减少网关漏收广播再给MQTT通道加TLS防住局域网里的明文抓包。这些都属于功能跑通之后的第二步但这一步不做方案就只能停在玩具阶段。智能家居项目真正吃掉时间的从来不是把某个功能点亮而是把断网重连、路由器重启、OTA失败、功耗失控这些边缘情况逐个填平。每一个坑填完整套系统才敢说一句真的能落地。如果你也准备用ESP32搞WiFiBLE的智能家居建议先从一枚灯控节点一枚门磁传感器开始跑通上述所有环节再去铺全屋。这条路我已经替你试过了值得走。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →