
1. 项目概述与整体方案拆解1.1 为什么最终选了ESP32做智能家居的主力这个项目最初不是一拍脑袋定的。我最早用ESP8266做开关和灯控价格确实便宜也能跑WiFi但做完整套智能家居后痛点很明显没有BLE、内存和Flash偏小、跑HTTPS和异步Web吃力、多IO复用也不够灵活。后来换到ESP32才算把WiFiBLE这条链路彻底打通。你可以把它理解为一个兼具无线网关和本地控制能力的通用平台WiFi负责连接云端、局域网高速通信BLE负责配网、近场控制、低功耗传感器采集两个协议栈共存在一颗低成本的MCU里不用再额外加蓝牙网关。这个组合对于小户型家庭或学生DIY项目来说性价比极高。1.2 一站式不是个口号而是明确的职责分工我设计这套方案时的核心理念很简单WiFi管连接稳定性BLE管连接便捷性和低功耗节点接入。WiFi链路承载MQTT消息、Web页面、OTA升级这些高数据量或长连接任务BLE链路承担三类事第一是给无屏幕设备快速配网第二是作为BLE网关接收周围温度计、门磁、开关这类设备的广播数据第三是手机近距离直连控制比如你站在设备旁App哪怕没有路由器也能直接开灯。这三件事如果全交给WiFi做要么配网体验很差要么节点功耗压不下来。用表格看分工更直观链路承担任务典型场景WiFiMQTT远程控制、Web页面、固件OTA、状态上报人不在家、局域网统一控制BLE无屏配网、近场调试、低功耗传感器采集首次安装、传感器待机、手机靠进设备直控共用设备状态维护、配网信息存储、掉线重连日常运行这套方案做下来家里十几个设备不需要每台都接电源线去路由器附近配置BLE先把WiFi账号密码传给设备设备自己切换到WiFi然后统一走MQTT进Home Assistant或自研App。整体调试下来稳定性和灵活性确实比单WiFi方案好一大截。2. 硬件搭建与关键器件选择2.1 ESP32主控模组怎么选才不翻车ESP32家族型号很多这里不介绍太细只说我实际用下来最推荐的三类。经典款ESP32-WROOM-32依然是性价比之王双核240MHz、4MB FlashArduino和ESP-IDF生态最全普通灯控、传感器、小屏显示完全够用。如果你的主控不仅要跑MQTT还要同时做Web服务器和BLE网关优先选它。ESP32-S3多了一些AI加速和更多GPIOUSB原生支持做带摄像头或屏幕的面板很合适但注意它用的是RISC-V核少数老库要适配。ESP32-C3便宜、单核、体积小做纯BLE低功耗节点或简单开关够用但如果你要跑Web页面加MQTT加BLE扫描内存会紧张我建议新手第一块板子不要选C3。我自己最终部署的主节点用的是ESP32-WROOM-32理由就是稳定、资料多、不折腾。2.2 传感器、继电器和显示模块的搭配智能家居最常用的几类外设我列一下温湿度采集我用过DHT11、DHT22和SHT30DHT11便宜但精度差同一个环境测出来能差好几度DHT22勉强够用SHT30是I2C接口、精度高、长期稳定性好预算够就直接上SHT30。温度单独监控可以用DS18B20防水封装版本适合放在水箱、暖气管道旁边。继电器模块要选带光耦隔离的低电平触发版本ESP32的GPIO输出能力有限直接驱动线圈容易导致重启或烧引脚必须经过三极管或光耦。显示方面OLED用SSD13060.96寸的I2C两线就能接调优时把IP地址、配网状态直接显示出来比看串口方便很多。另外如果你不打算做实体按键最好留两个GPIO做触摸按钮用于恢复出厂设置和手动开关这是调试时的救命按键。2.3 供电和IO分配的坑供电是整个项目里最容易被忽视的部分。ESP32在WiFi发射瞬间电流能飙到300mA以上如果用普通USB线接稳压模块电压跌落会造成反复重启。我的经验是使用5V 2A以上的适配器通过AMS1117-3.3稳压给模组供电但AMS1117压差大输入5V时输出3.3V效率一般实际做多个负载还是建议用MP1584或LM2596这类DC-DC降压模块把5V降成3.3V给逻辑电路同时保留5V给继电器模块。IO分配要避免使用GPIO0和GPIO2它们和启动模式、Flash相关容易造成下载失败继电器、蜂鸣器这类电磁负载尽量安排在靠外的引脚同时加续流二极管。我第一版就是因为继电器直接接在GPIO12上上电偶尔启动异常排查了很久。3. WiFi链路实现从配网到MQTT与Web控制3.1 配网方式对比为什么最终选了BLE配网ESP32常见配网方式有三种SmartConfig、AP配网、BLE配网。SmartConfig是手机App发UDP广播包设备监听获取SSID和密码体验不错但兼容性一般有些路由器或者手机系统会限制组播。AP配网是设备自己开热点手机连上热点后访问192.168.4.1填WiFi信息这个最通用但用户操作步骤多尤其设备在客厅手机要断开自己的WiFi去连设备来回切换很烦。BLE配网是最适合一站式的方案手机App直接扫描附近广播通过BLE服务把WiFi账号和密码写入设备设备马上切换WiFi并反馈结果。整个过程用户不需要切换网络也不需要在网页上输密码体验就像用蓝牙耳机一样。如果你的项目是给家人或朋友用强烈建议优先上BLE配网。当然如果只是自己调试AP配网也挺方便。3.2 MQTT通信协议与主题设计设备连上WiFi之后我统一走MQTT协议它比HTTP适合设备间的长连接和实时消息。局域网部署Mosquitto外网映射或云服务器部署同样的Broker设备端通过TLS加密连接。MQTT主题设计如果不规划好设备一多就乱。我的模板是状态上报device/{device_id}/statepayload为JSON包括在线状态、传感器值、固件版本。指令下发device/{device_id}/cmdApp或Home Assistant发到这里。在线遗嘱device/{device_id}/online使用MQTT的LWT机制异常掉线自动发布offline。特别注意QoS等级。控制继电器用QoS 1状态上报用QoS 0就可以不要全用QoS 2资源占用高且对智能家居场景不划算。保留消息Retained flag建议在/state开启这样新设备上线或者App刚打开就能立刻拿到最新状态不用等下一次上报。3.3 内嵌Web页面做本地控制不依赖云端很多方案把控制全部放到云平台结果一旦外网断了灯都开不了。我在ESP32里用LittleFS存了一个轻量级Web页面通过AsyncWebServer提供本地HTTP服务手机连同一个WiFi浏览器访问设备IP就能看到控制界面。这个页面不依赖外网平时作为备份。实现上我用的是ESPAsyncWebServer配合ArduinoJson页面里用原生HTMLJavaScript轮询或者WebSocket拉取状态。WebSocket更实时但代码量会多一点简单场景可以用每1秒轮询一次/api/state返回温度湿度和继电器状态。对于内联Web页面有一个细节小文件可以打包成.h数组直接烧进去更新页面麻烦用LittleFS存单独的HTML文件更新页面时只要通过OTA上传文件系统镜像就行。我个人经验是这个local Web控制不需要做太复杂保留开关、状态显示、配网重置三个功能就可以了。4. BLE链路实现配网、近场控制与网关采集4.1 BLE的GATT基础概念用一句话讲透很多刚开始上手BLE的人被Service、Characteristic、UUID这些词绕晕。我用一个快递柜类比GATT就是货运系统的规范Service是快递柜的分类格口Characteristic是每个格口里的小包裹——实际交换的数据就装在里面。你要告诉App往某个格口塞一个WiFiSSID包裹就是在某个Characteristic上产生一次Write。反过来ESP32要主动告诉手机配网成功了就通过Notify把结果推送过去。动手之前建议先安装esp32 BLE相关库Arduino环境下用官方BLEUtils、BLEServer、BLECharacteristic就可以。不需要额外第三方库官方库足够稳定。4.2 用BLE快速配网的具体设计我的配网流程是这样设计的设备上电后先查NVS里是否已经存有WiFi配置如果有就直接连WiFi如果没有进入BLE配网模式广播名称类似ESP32_Config_XXXX。手机App扫描到后连接这个设备往write_ssid特征写入WiFi名称往write_pwd特征写入WiFi密码然后写一个start_connect命令ESP32收到后开始连接WiFi并通过Notify反馈连接状态。为了避免WiFi密码明文暴露在广播里app和设备之间走的是BLE连接加密通道不需要密码本身就是BLE的配对绑定机制我推荐使用Bonding模式首次配对后设备信任手机比直接开放特征值安全得多。配网超时设定为5分钟超时后自动广播停止并进入深睡省电且避免一直处于可连接状态。成功连接WiFi后把SSID和密码写入NVS下次开机直接读NVS不再进配网模式。只有长按恢复键或连续三次连接失败时才清除NVS重新进入配网。4.3 把ESP32当BLE网关收集低功耗传感器除了自己作为BLE外设提供配网服务ESP32还能切换到中心设备模式扫描周围的BLE广播包。这套方案里我最满意的一点是家里那些用纽扣电池的BLE温湿度计、门窗磁、人体传感器本来都需要专用网关现在直接用ESP32扫它们的广播就能接收数据。BLE广播包里的Manufacture Specific Data是各传感器的私有数据段不同品牌协议不同但大部分厂商会开源解析协议或公开文档。实现上注意连续扫描和被动扫描的区别。ESP32默认可以做主动扫描但为了省电我一般设置被动扫描只监听广播包不发起连接然后设置窗口间隔比扫描窗口占空比调到10%~50%之间。这样ESP32能同时保持WiFi连接和BLE扫描实测对WiFi吞吐影响不大。收到广播后解析出温度、湿度、电池电量包装成JSON再用MQTT上报这一步就让一个普通ESP32变成了多协议网关。4.4 低功耗电池设备怎么配合这个系统如果你希望某些节点完全不拉电线那要考虑省电策略。一颗ESP32-C3在深睡模式下电流可以到10µA左右但要注意深睡期间WiFi和BLE全停只能定时唤醒发一包数据。我的做法是让C3每5分钟醒来一次采集一次传感器数据通过WiFi发MQTT然后睡6分钟。由于WiFi从连接到发送要1~2秒相当于0.4%的占空比三节AA电池能撑很久。如果你用的是纯BLE节点则可以用更省电的方案让ESP32当BLE网关接收即可传感器节点可以用带BLE协议的MCU睡眠电流更低。整个系统的原则是能交给BLE的低频低功耗通信不交给WiFiWiFi留给实时控制和交互。5. 一站式系统集成与实操记录5.1 完整数据流从一个新设备上线说起新设备上电后在配网模式手机App通过BLE写入WiFi信息设备连接WiFi成功后通过MQTT向device/xxx/state发布在线消息Home Assistant通过MQTT Discovery自动发现设备并把传感器数据展示到面板。用户点击打开客厅灯时HA发布控制指令到device/004/relay/set设备订阅到后驱动继电器并回发状态确认。如果路由器断网设备不会傻等它检测到MQTT Broker断开后每5秒重连同时本地Web页面仍然可以访问直接控制继电器。整套流程跑通后给人的感觉就是配网时用BLE使用时全走WiFi掉线时还能本地兜底。5.2 核心代码实现BLE配网后连接WiFi拿Arduino IDE举例我习惯把BLE配网和WiFi连接分文件写便于维护。核心流程大致如下#include BLEDevice.h #include BLEUtils.h #include BLEServer.h #include WiFi.h #include Preferences.h BLECharacteristic *pWriteSSID; BLECharacteristic *pNotify; Preferences prefs; String ssid ; String pwd ; bool shouldStartWiFi false; class WiFiCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *p) override { if (p-getUUID().toString() 0000ff01-0000-1000-8000-00805f9b34fb) { ssid p-getValue().c_str(); } else if (p-getUUID().toString() 0000ff02-0000-1000-8000-00805f9b34fb) { pwd p-getValue().c_str(); } else if (p-getUUID().toString() 0000ff03-0000-1000-8000-00805f9b34fb) { shouldStartWiFi true; } } }; void startBLEConfig() { BLEDevice::init(ESP32_Config_XXXX); BLEServer *server BLEDevice::createServer(); BLEService *svc server-createService(0000ff00-0000-1000-8000-00805f9b34fb); // 创建三个Characteristic分别用于写SSID、写密码、启动连接 pWriteSSID svc-createCharacteristic(0000ff01-0000-1000-8000-00805f9b34fb, BLECharacteristic::PROPERTY_WRITE); ... svc-start(); BLEAdvertising *adv BLEDevice::getAdvertising(); adv-addServiceUUID(0000ff00-0000-1000-8000-00805f9b34fb); adv-start(); }这段代码里需要注意几个点BLE特征值UUID不要随便写保持服务UUID和特征UUID的128位格式写入回调里不要做WiFi连接这种耗时操作否则BLE协议栈会卡住正确做法是在回调里置标志位再在主循环里启动WiFi。Preferences用来存储NVS数据调用putString(ssid, ssid)和putString(pwd, pwd)即可下一次开机直接读。5.3 MQTT订阅与传感器上报代码可直接抄我用PubSubClient库接MQTT回调处理指令loop里定期上报传感器。核心逻辑是#include PubSubClient.h WiFiClient net; PubSubClient mqtt(net); void mqttCallback(char* topic, byte* payload, unsigned int len) { String msg String((char*)payload).substring(0, len); if (String(topic) device/004/relay/set) { int relayState (msg ON); digitalWrite(RELAY_PIN, relayState); mqtt.publish(device/004/state, {\relay\:\ON\}, true); } } void publishSensor() { float t readSHT30Temperature(); float h readSHT30Humidity(); String json {\temp\:\ String(t) \,\hum\:\ String(h) \}; mqtt.publish(device/004/tele, json.c_str(), false); }MQTT发布状态时建议开启retain这样App或HA重启后能快速恢复界面。不过传感器遥测数据不需要retain否则每次打开面板都显示旧值消费者会混淆。实际跑起来后传感器每10秒上报一次WiFi连接异常断开时mqtt.connected()会变成false在如果断线超时5秒就调用ESP.restart()别在库层无限重连那个重连机制不够健壮。5.4 对接Home Assistant的细节如果你用Home Assistant最简单的做法是直接配置MQTT开关和传感器但每次新增设备都要在HA的YAML里写配置很繁琐。更好的方案是用HA的MQTT Discovery设备向homeassistant/switch/device_004/config发布一段JSON描述HA会自动添加设备。我这里给出关键字段{ name: Living Room Relay, state_topic: device/004/state, command_topic: device/004/relay/set, payload_on: ON, payload_off: OFF, unique_id: esp32_relay_004 }把 QoS设为1Discovery消息要带retain否则HA重启后就得重新注册。这个模式特别适合批量设备接入你只需要在ESP32端的配置里写不同device_idHA面板会自动出实体不需要在服务器端改文件。6. 常见问题与排查技巧实录6.1 BLE配网失败或搜不到设备如果手机搜不到广播先确认GPIO0或RST有没有被外设占用部分模组在低电平时射频关闭。其次看一下广播是否只在配网模式开启很多情况下你已经把WiFi配置写进NVS设备默认不广播了。遇到这种情况长按恢复键清空NVS再试。手机端要注意Android的定位权限必须开启BLE扫描在Android 6以上需要精确定位权限iOS需要在App的Info.plist里声明NSBluetoothAlwaysUsageDescription。我踩过最狠的坑是手机之前配对过同一MAC地址的旧设备缓存了密钥新固件换了Service UUID结果连不上。解决办法是在App里取消绑定设备或者把设备端蓝牙MAC地址随机化。6.2 WiFi和BLE同时工作时的信号互相干扰ESP32的WiFi和BLE共用2.4GHz频段如果两者同时收发射频前端会争抢资源表现为WiFi延迟变大、BLE广播丢包。我的经验是把BLE扫描窗口调小例如扫描窗口设置为100ms、间隔900ms这样只占很短的射频时间在WiFi传输大量数据时暂缓BLE扫描用esp_bt_controller_disable()临时关闭蓝牙。如果你用的是ESP32-S3它自带比较好的共存机制但依然不建议在同一个设备上做高吞吐WiFi下载的同时做高频率BLE扫描。6.3 烧录失败开发环境让人崩溃新用户最容易在Arduino IDE里卡住。核心是选对开发板型号和烧录参数ESP32-WROOM-32对应开发板选择ESP32 Dev ModuleFlash大小选4MBPartition Scheme选Huge APP不然固件太大写不进去。国内网络下载慢的话可以把Arduino ESP32的板级JSON换成国内镜像源能省很多时间。另外烧录时按住BOOT键再点上传特别是使用自动下载电路不稳定的开发板。如果你用PlatformIO注意board_build.flash_mode设成dio部分模组在QIO模式下会不稳定。6.4 传感器读数异常或漂移DHT系列如果数据跳变大多数是线太长或供电不稳。SHT30我遇到过一次I2C总线锁死原因是SCL上拉了4.7k电阻但板子内部已经有了两个电阻并联导致信号边沿太缓。排查时用逻辑分析仪看波形如果没有就先恢复默认I2C引脚。DS18B20如果读到85℃这个著名的数值说明传感器没在总线上检查都在一个线上是否缺少4.7k上拉。另外所有模拟信号都要避开DC-DC电感区域否则数据会有周期性噪声。6.5 电池供电节点功耗还是太高很多人说ESP32电池待机撑不了几天没错那是因为忘了唤醒后要先稳定电源再干活。电源轨电压在唤醒瞬间会掉如果传感器还没初始化就读取会上报错误值。我建议电池节点用一颗电容组做缓冲MCU唤醒之后延时200ms再采集然后MQTT连接之前先用WiFi连接探测如果找不到路由器就直接回去睡不然重试会消耗巨量电量。做低功耗设备要始终记住一句话功耗大头是重试不是工作本身。7. 一点个人经验与后续扩展方向这套方案前前后后改了三版现在稳定跑了一个多月。最深的体会是不要一开始就追求把所有功能都塞进一颗ESP32先把WiFiBLE配合的最小路径打通再逐步叠加传感器、Web页面、Home Assistant接入。每一步只改一个变量出现问题好定位。BLE配网和WiFi局域网控制这两个核心链路一旦跑通后面的扩展都是加分项。接下来我计划做的扩展方向一是把BLE网关采集的传感器数据直接通过MQTT转发给Home Assistant这样家里那些纽扣电池设备能统一入网二是给设备加OTA升级通过Web页面直接上传固件不用再打开Arduino IDE插USB线三是尝试用ESP32-C3做纯BLE开关节点和主力ESP32网关配合实现更灵活的低功耗布局。如果你们也在折腾智能家居先从一个小房间的灯控加温湿度采集做起跑通这套WiFiBLE架构再往全屋扩会顺畅得多。