1. 为什么选ESP32做智能家居主控
1.1 智能家居主控的核心需求
整套智能家居方案的第一个问题是:主控选谁?我先后试过STM32、树莓派、ESP8266,最后在ESP32上稳定跑了小半年才敢说“一站式”这三个字。原因其实不复杂:ESP32原生同时支持WiFi和BLE,也就是说局域网远程控制走WiFi,近场配网和手机直连走BLE,两颗通信模块都焊在一块板子上,省掉中继器、也省掉两块芯片之间接线的麻烦。
大多数入门者第一反应是“ESP8266不行吗?”不行。ESP8266只有WiFi,没有BLE,蓝牙配网、蓝牙调试、手机近场控制全都要额外接模块,板子贵不说,调试链路拉长之后排查问题特别难受。STM32做纯逻辑控制没毛病,但WiFi、BLE协议栈几乎是要自己移植全套,学习成本直接翻几倍。树莓派倒是算力强,也能跑Linux跑Home Assistant,问题在于启动慢、功耗高、体积大,做节点控制太浪费,更适合做整个家里的中央服务器。
ESP32的方案平衡点正好在中间:240MHz双核、520KB SRAM、4MB Flash,跑WiFi协议栈的同时还能干大量GPIO控制逻辑;BLE4.2协议栈是原生集成的,不用外挂芯片。做单个房间的灯光、温湿度、门锁控制,它绰绰有余;做整套房子的核心网关节点,通过MQTT把数据上抛到树莓派或NAS上的Home Assistant,它也能撑住。
1.2 这套方案的项目定位
文章标题里的“WiFi+BLE一站式”不是营销话术,我理解的“一站式”是:配网不需要电脑命令行串口输入WiFi密码、日常控制可以离线路由器直接用手机蓝牙控制、远程访问不需要暴露公网端口。这三种能力恰好是当下智能家居用户最刚需的三个痛点。
- 配网体验:设备上电后进入BLE配网模式,手机小程序用蓝牙把WiFi的SSID和密码写进设备,设备连上路由器之后自动切换为工作模式。整个过程不需要USB线,不需要打开浏览器输IP。
- 控制体验:局域网内通过网页端或MQTT完成控制,人在设备旁边可以直接用BLE通道近场控制,断网也能用。
- 维护体验:用BLE持续广播设备状态,比如固件版本、IP地址、运行时长,排查问题时不用登路由器翻设备列表。
所以这篇文章的关键词不只是“ESP32”,而是怎么把WiFi和BLE两条链路配合起来用:BLE管配网和近场控制,WiFi管远程控制和状态上报,两者在固件层面做指令合并与优先级处理。我会把整条开发链路从硬件接线、环境配置、源码框架、到调试排错都写出来,适合手里有ESP32开发板但不知道怎么把WiFi和BLE真正用到一个场景里的朋友。
1.3 前置知识准备
这套项目不需要你很懂射频,但至少要用过Arduino IDE,知道怎么选开发板、怎么编译烧录。如果你完全零基础,建议先拿一个ESP32 DevKitC点亮板载LED,跑通“Blink”再继续。后面我会尽量把步骤写细,但不会停留在“新建工程、复制粘贴”这种保姆级程度,代码里每一段做什么用,我会直接解释清楚。
2. 硬件选型与开发环境
2.1 硬件清单
以下是我这套方案里实际在用的硬件清单,没有太多额外费用,大多是你手头已经有的东西:
| 硬件 | 型号/规格 | 作用 | 大致成本 |
|---|---|---|---|
| 主控开发板 | ESP32 DevKitC V4(ESP32-WROOM-32模组) | 跑WiFi/BLE双协议栈,控制外设 | 20-30元 |
| 温湿度传感器 | DHT20(I2C) | 采集环境数据 | 5-8元 |
| 继电器模块 | 5V低电平触发继电器 x4 | 控制灯光、插座、风扇等强电设备 | 10-15元 |
| 蜂鸣器 | 无源蜂鸣器(有源也行) | 本地告警提示 | 2-3元 |
| 光敏电阻模块 | 数字量输出型 | 判断环境亮度,联动灯光 | 3-5元 |
| 电源 | 9V 1A DC电源 + AMS1117-3.3V模块 | 给ESP32和传感器供电 | 8-12元 |
| 手机调试App | nRF Connect 或“蓝牙调试助手” | 查看BLE服务、特征值,模拟小程序发指令 | 免费 |
为什么不直接用USB 5V供电?USB供电在调试阶段能用,但一旦接继电器,继电器吸合瞬间电流变化会让USB口掉电,严重的还会把电脑USB口搞挂。我后来改用9V DC电源,经过AMS1117降压到3.3V给ESP32供电,继电器的5V用稳压模块直接走DC输入。强电部分一定注意:继电器模块是低压控制高压的隔离器件,但市面上很多继电器模块没有隔离光耦,控制强电设备时务必在断电状态下接线,并且把继电器模块和ESP32保持物理距离。
2.2 开发环境配置:Arduino IDE与国内源
开发平台我用的是Arduino IDE,配ESP32 3.x核心。不是说ESP-IDF不好,ESP-IDF是乐鑫官方全功能框架,但写双层通信这种逻辑时Arduino库抽象度更高,调试速度更快。
先装ESP32开发板核心。Arduino IDE里“文件→首选项→附加开发板管理器网址”填上乐鑫官方JSON地址。国内用户如果官方源下载太慢,可以换成乐鑫在国内的CDN镜像地址,这部分网上很容易查到,我不展开讲具体域名,只说一个经验:如果下载时卡在“xtensa-esp32-elf-gcc”这个包上,多数不是网速问题,是防火墙拦截了GitHub附件,多试几次或者手动下载工具链放到packages/esp32/tools目录下。
安装完成之后,在“开发板管理器”里搜“esp32”,安装3.0.7左右或者更新的稳定版。注意Arduino IDE 2.x和1.8.x对ESP32核心的兼容性不同,我用的是Arduino IDE 2.3.x,需要选择“ESP32 Dev Module”。烧录参数里,Flash Size选“4MB”,Partition Scheme在调试期选“Default 4MB with spiffs”可以以后放网页资源;如果只跑代码逻辑,选“Huge App”让代码空间更大。
2.3 引脚分配与避坑
ESP32没有真正的“5V容忍引脚”,所有GPIO最大输入3.3V。接线时继电器模块如果需要3.3V电平触发,选低电平触发型号;如果是5V高电平触发,记得加光耦模块,或者用三极管电平转换电路。以下是我这个项目的引脚分配表:
| 外设 | 引脚 | 说明 |
|---|---|---|
| DHT20 | GPIO 4(SDA)、GPIO 5(SCL) | 使用Wire库默认I2C0通道 |
| 继电器1-4 | GPIO 13、12、14、27 | 低电平触发,上电默认拉高 |
| 无源蜂鸣器 | GPIO 15 | 使用ledcPWM做提示音 |
| 光敏电阻模块 | GPIO 35 | 仅输入模式,不能输出 |
| 板载LED | GPIO 2 | 配网模式闪烁,正常模式常亮 |
ESP32的GPIO 34-39是纯输入引脚,没有内部上拉,也没有PWM输出能力,千万不要用来接继电器或蜂鸣器。GPIO 6-11默认接Flash芯片,尽量避免复用。GPIO 0是BOOT引脚,GPIO 1和3是串口引脚,这些在调试时容易出问题,尽量避开。我第一次做的时候把继电器接在GPIO 2上,结果开发板自带的红色LED和继电器状态混在一起,排查时白白折腾了两个小时。动手前先画一张引脚分配表,能省掉大量低级错误。
电源方面还要注意:ESP32开启WiFi时峰值电流可以到300-500mA,BLE广播时大约几十毫安,如果板载LDO余量小,尽量用外部3.3V模块供电,避免USB供电导致复位频繁。
3. WiFi与BLE双通道功能拆解
3.1 为什么需要双通道
很多现成的智能家居方案只走WiFi,逻辑上“手机发HTTP请求到设备→设备执行”就够了。但真实的家庭场景里,你会碰到几件很麻烦的事:
- 第一次上电的设备,怎么告诉它你家WiFi叫什么、密码是什么?连不上网,Web页面打不开,只能靠串口或者BLE。
- 路由器重启的时候,设备重连WiFi需要几秒到几十秒,这段时间里如果人在房间内,想开关灯还得等网络恢复,离谱。
- 家里网线被宠物咬了、光猫被断电了,智能设备全部变砖,只能手动按物理开关。
BLE这条链路就是为了解决这些边角场景。BLE不需要加入局域网,它走的是点对点通信,手机和设备之间直接建立GATT连接,带宽小但控制指令完全够用。WiFi和BLE同时开启,两个协议栈在ESP32上是分时共存的,不会真的“同时收发”,但频率调度由底层处理,应用层几乎无感。
3.2 BLE配网的核心设计
BLE配网的完整流程我拆成三个阶段:广播与发现、写入WiFi凭据、切换到WiFi工作模式。
设备上电后先判断Flash里有没有保存过有效的WiFi配置。如果没有,进入“配网模式”:初始化BLE,开启广播,广播名称比如ESP32-Config-XXXX。手机端用小程序或App扫描到该设备后,调用GATT连接的写特征值,把包含SSID和密码的字符串写入。固件接收到之后,先把数据存进Preferences(NVS),然后断开BLE连接,尝试连接WiFi。连WiFi成功后,设备停止BLE广播,进入正常工作模式。如果WiFi连接失败,设备重新进入配网模式并保持BLE开启,这是典型的状态机逻辑。
我在实际项目中给BLE服务定义了三个特征值:
| 特征值 | 属性 | 内容 |
|---|---|---|
| 0xFF01 | Write | 接收配网数据,格式如SSID:password |
| 0xFF02 | Read/Notify | 返回配网状态,如CONNECTING、SUCCESS、FAIL |
| 0xFF03 | Write | 近场控制指令,如RELAY1_ON |
0xFF01用来配网,0xFF03用来日常BLE控制。为什么把控制指令单独拆一个特征值?如果配网和实时控制共用同一个特征,配网状态机还没跑完,用户又连续开关几次灯,数据就会互相覆盖,很难排查。
3.3 WiFi通道的功能设计
WiFi通道我分成三个子功能:Web控制页面、MQTT状态上报、HTTP API接口。
Web控制页面跑在ESP32的WebServer上,通过浏览器输入IP地址打开。页面用简洁的HTML+JavaScript,控制四个继电器、显示温湿度和亮度。页面不需要特别花哨,核心是JavaScript通过fetch调用/api/relay?state=1这类接口,ESP32解析参数后把指令传给GPIO控制逻辑。我特别推荐把页面资源用PROGMEM编译进固件,而不是放SPIFFS文件系统。原因后面讲。
MQTT通道用于“远程控制”。ESP32连接本地路由器后,再连接局域网内的MQTT Broker(我用的Mosquitto或者云服务),订阅home/room1/relay1/set这类Topic,发布home/room1/sensor/temp。家里如果跑了Home Assistant,HA直接通过MQTT发现设备,整个仪表盘就自动出来了。
MQTT的Topic命名要统一规划,我踩过坑之后现在的命名规则是:家庭名/区域/设备类型/动作,比如home/room1/relay/1/set表示“客厅的第一个继电器收到控制指令”。Topic字面里含中文和空格会有兼容问题,全用小写英文加数字。
4. 核心源码架构与关键实现
4.1 固件整体任务划分
用Arduino编写ESP32固件时,setup()和loop()这两个函数容易写成“一个大循环遍历一切”。但我建议把整体拆成几个清晰模块,我用的是下面这个结构:
#include <WiFi.h> #include <WebServer.h> #include <BLEUtils.h> #include <BLEDevice.h> #include <BLEServer.h> #include <Preferences.h> #include <ArduinoJson.h> // 用于解析JSON指令 #include <Wire.h> #include <DHT20.h> // 温湿度传感器驱动 // 全局对象 WebServer server(80); Preferences prefs; DHT20 dht20; BLECharacteristic *pControlChar = NULL; bool isConfigMode = false; bool wifiConnected = false; unsigned long lastSensorPublish = 0;在setup()里顺序执行四步:读取NVS判断是否已有WiFi配置、初始化BLE(如果有配置则BLE可以继续待机,没有配置则进入配网模式)、连接WiFi并启动WebServer和MQTT、初始化GPIO。不要把这四步堆在一个函数里写完,而是拆成initNVS()、initBLE()、initWiFi()、initGPIO()四个子函数,每个函数里用串口打印关键日志。
4.2 BLE服务搭建的关键代码
创建BLE服务器的代码结构大概是这样的:
#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e" #define CHAR_CONFIG_UUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e" #define CHAR_STATUS_UUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e" #define CHAR_CONTROL_UUID "6e400004-b5a3-f393-e0a9-e50e24dcca9e" class ServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* server) { Serial.println("BLE client connected"); } void onDisconnect(BLEServer* server) { Serial.println("BLE client disconnected"); // 设备端重启广播,允许再次连接 BLEDevice::startAdvertising(); } }; void initBLE() { BLEDevice::init("ESP32-Home"); BLEServer *pServer = BLEDevice::createServer(); pServer->setCallbacks(new ServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); pConfigChar = pService->createCharacteristic( CHAR_CONFIG_UUID, BLECharacteristic::PROPERTY_WRITE ); pStatusChar = pService->createCharacteristic( CHAR_STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY ); pControlChar = pService->createCharacteristic( CHAR_CONTROL_UUID, BLECharacteristic::PROPERTY_WRITE ); pService->start(); BLEDevice::startAdvertising(); }这里UUID可以直接用乐鑫文档里公开的自定义UUID范例,也可以自己生成,重点是三个特征的UUID不能重复。很多新手第一次做,复制了网上教程的UUID,结果手机App里看到服务了但读不了数据,就是因为服务和特征的UUID和App代码里不匹配。建议直接用十六进制生成网站生成一组,写到代码注释里备用。
配网和控制的回调逻辑都通过BLECharacteristicCallbacks实现,关键是先区分特征值:如果是配置特征,解析字符串;如果是控制特征,直接调用handleLocalCommand()。配网特征不能在一次写入里就切掉BLE广播,必须等WiFi连接成功之后才能停广播,否则用户手滑多写一次数据,设备直接失去配网能力。
4.3 WiFi、WebServer与MQTT实现
WiFi配置完成后,我先尝试连接之前保存的SSID和密码:
bool initWiFi() { String ssid = prefs.getString("ssid", ""); String pass = prefs.getString("pass", ""); if (ssid.length() == 0) { isConfigMode = true; return false; } WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); int retries = 0; while (WiFi.status() != WL_CONNECTED && retries < 40) { delay(500); retries++; } if (WiFi.status() == WL_CONNECTED) { wifiConnected = true; return true; } isConfigMode = true; return false; }WiFi连接成功后,WebServer的API路由如下:
server.on("/", HTTP_GET, []() { String html = FPSTR(index_html); server.send(200, "text/html", html); }); server.on("/api/relay", HTTP_GET, []() { int relay = server.arg("id").toInt(); bool state = server.arg("state").equals("1"); setRelay(relay, state); server.send(200, "application/json", "{\"ok\":true}"); });如果走MQTT,则在loop()里调用mqttClient.loop(),并在回调里解析订阅主题和控制内容。MQTT控制指令我用JSON格式,比如{"relay":1,"state":true},用ArduinoJson库解析。刚开始用字符串拼接RELAY1_ON这种协议也能用,但后面要扩展场景自动化时,JSON的扩展性就重要了。
4.4 双通道指令合并与优先级
双通道的难点在于:如果用户在手机Web页面上关灯,同时又用BLE小程序开灯,到底听谁的?
我设计了简单的优先级策略:BLE指令优先级最高(因为它是近场直接控制,说明用户就在设备旁边);MQTT指令其次;Web API最后。这里的实现不复杂,在setRelay()函数里加一个来源参数:
void setRelay(int relay, bool state, ControlSource src) { // 如果当前是BLE来源,直接覆盖 // 如果是MQTT来源且状态不等于当前状态,覆盖 // 如果是Web来源且状态和当前不同,覆盖 if (src == SOURCE_BLE) { digitalWrite(relayPins[relay], state ? LOW : HIGH); } else if (src == SOURCE_MQTT || src == SOURCE_WEB) { // 按当前实际状态做状态机切换 } }这个优先级不是死的,你可以把“物理墙开关优先”加进来:每次GPIO引脚状态变化也视为一个控制源。这里有一个真实教训,我最初把WiFi指令优先级设成最高,结果老婆在房间按物理开关关了灯,远程端状态没同步,回头远程再开一次灯,灯没反应,排查了半天。现在我的设计是:在任何外部指令到达时,先读一次当前GPIO电平,再结合指令来源决定是否动作。远程端的状态展示通过MQTT回传的真实状态来同步,而不是用指令状态当作物理状态。
5. 实测高频问题与排查技巧实录
5.1 WiFi连不上、连接不稳定
ESP32的2.4GHz WiFi本身抗干扰能力有限,排查时先分三类:完全没有Scan到路由器、扫描到但连接超时、连接成功但运行几小时掉线。
扫描不到路由器时,先用手机开一个2.4GHz热点,把设备放到热点附近测试,排除路由器加密方式兼容性问题。ESP32对WPA3的兼容性在部分旧固件上不好,如果家里路由器开了WPA3,建议先改成WPA2/WPA3混合模式。连接成功但掉线,多半是电源供电不稳。WiFi发射瞬间电流需求大,如果电源电流裕量不足,设备会自动重启或者WiFi模块复位。解决方法是把供电改成9V电源加AMS1117,并在电源输入端并联一个470uF电解电容和一个100nF陶瓷电容。
如果路由器开了“AP隔离”功能,设备之间无法互相访问,Web页面和MQTT也都会连不上。这个坑排查方式很直接:手机连同一个WiFi后,用ping工具ping一下ESP32的IP地址,不通大概率就是AP隔离或者防火墙。
5.2 BLE扫描不到设备、连接后掉线
BLE扫描不到设备,先检查设备是否进入了广播状态。有的USB供电板子一插电就进入“配置模式”,广播了几十秒之后如果没等到连接,广播可能已经不发了。可以在initBLE()里调用BLEDevice::startAdvertising()后再加一句Serial.println("start advertising"),串口打印的及时性能帮你判断代码是否执行到广播那一步。
连接后频繁掉线,需要重点排查两个位置:一是广播参数里是否设置了过短的setMinInterval和setMaxInterval,广播间隔太短会加快耗电但不提升稳定性,建议设置成100ms-150ms;二是在onDisconnect回调里,如果调用了pServer->getAdvertising()->start()但没有先删掉旧Client对象,系统会在内存紧张时主动断开连接。
BLE和WiFi同时存在的另一个问题是:在BLE配网模式下,如果WiFi连接线程阻塞在while循环里,BLE广播会受影响。我的解决方案是把WiFi连接超时重试放到一个FreeRTOS任务中执行,不让它卡住主程序。如果用Arduino的loop(),至少要保证delay(500)而不是delay(5000),长时间阻塞会崩溃。
5.3 Web页面打不开、加载慢
我前面提到把Web页面放到PROGMEM而不是SPIFFS文件系统,原因很现实:SPIFFS在频繁读写后会产生碎片和文件系统损坏,而Flash放代码区编译成只读常量,永远不会因为非正常断电损坏。但PROGMEM的字符串内容不能包含太长的模板,如果页面超过几十KB,编绎和内存占用都会上升,所以页面尽量精简。我现在的页面压缩后大约10KB,包括CSS和JavaScript。
如果Web页面打开后显示不正常,优先检查开发板Flash分区表是否选对了。默认分区表是“Default 4MB with spiffs”,如果你刷了其他分区表,web页面所在的地址可能会被擦掉。另一个快速排查方式是打开浏览器开发者工具,看Network里HTML状态码是不是200。
5.4 掉电后配置丢失
配网NVS里保存的WiFi信息,如果在断电后丢失,大概率是你没有调用prefs.end()或者写入了错误的命名空间。Preferences在ESP32上是写入NVS分区,该分区掉电后保留数据。我的写法是:
prefs.begin("wifi", false); // false表示读写模式 prefs.putString("ssid", ssid); prefs.putString("pass", pass); prefs.end();注意,prefs.begin()第二个参数如果是true,是只读模式,写入时会返回错误。还有一个常见坑:同一个应用里用了多个Preferences命名空间,比如一个叫“wifi”,一个叫“relay”,但底层NVS存储空间是共享的,如果写入太频繁而Flash剩余空间不足,也会静默失败。我建议在设置完WiFi信息后,立即调用一次prefs.end()并打印读回结果,直接验证是否真的写入成功。
5.5 常见问题速查表
| 现象 | 可能原因 | 快速解决 |
|---|---|---|
| 无法烧录 | 开发板自动进入下载模式失败 | 按住BOOT键再插USB,点击烧录 |
| 串口输出乱码 | 波特率不匹配 | 统一为115200 |
| GPIO电平异常 | 引脚被复用 | 检查是否用了GPIO 6-11、GPIO 34-39 |
| WiFi连上但手机访问不了HTTP | AP隔离开启 | 关闭路由器AP隔离,或使用BLE控制 |
| BLE连接后马上断开 | 广播参数过短或内存不足 | 调大广播间隔,检查堆内存剩余 |
| 继电器频繁误动作 | 电平翻转时未加延时 | 在切换后加50ms延时,再做状态回读 |
6. 从单设备到全屋系统的一些扩展思路
6.1 多节点设备的组网拓扑
一个ESP32做单房间控制能跑通之后,自然会想扩展到全屋。此时如果每个房间放一个ESP32,每个都跑WebServer,管理和控制就会非常乱。更好的拓扑是:每个房间的ESP32只作为“传感器/执行节点”,通过MQTT上报状态;另外用一个NUC、树莓派或旧笔记本跑Home Assistant做中央控制。手机端只面对Home Assistant一个入口,不用直接访问每个ESP32的IP。
这种架构下,WiFi和BLE的分工会有变化:BLE不再负责控制指令,而是退化成“配网通道”,WiFi负责运行时通信。配网时,手机通过BLE把WiFi凭据写入设备,完成后BLE可以彻底停掉以省电。我的建议是保留BLE广播但把广播间隔拉大到500ms,以便以后现场维护时可以再次通过BLE连接。
6.2 本地自动化与离线场景
把这套方案做到“哪怕断网也能自动化”才称得上智能家居。最简单的是在ESP32节点本地做光照联动逻辑:光敏电阻检测到亮度低于阈值超过10秒,自动打开继电器1对应的灯。这个逻辑一定要加“超过10秒”的滞回判断,否则晚上窗帘一动、光影一闪,灯就会频繁开关。把滞回时间设为可配置项,用NVS保存,后续调参时不用重新编译固件。
离线语音控制可以接离线语音识别模块,比如SU-03T这类,模块识别到“打开客厅灯”后通过串口发指令给ESP32,ESP32走本地控制逻辑。这里的扩展不难,但记得语音指令识别有个误唤醒问题,建议在代码里加“连续两次相同指令才执行”的防抖。
6.3 功耗优化建议
如果设备要用电池供电,ESP32默认的WiFi+BLE双开会把电流拉到200mA级别,充电宝都撑不了几天。简单优化是使用esp_wifi_set_ps(WIFI_PS_MIN_MODEM)开启Modem Sleep,WiFi连接状态下的平均功耗可以降到50mA以内;再用esp_ble_gap_set_scan_params把BLE广播间隔调大,进一步降低功耗。如果是纯传感器节点,甚至可以让ESP32进入Deep Sleep,每30秒醒来一次采集并上报温湿度,上报完立即睡回去,电池供电能撑几个月。
这里要注意:开启Modem Sleep后,TCP连接(如MQTT)需要定期发送心跳,否则服务器因为Keep Alive超时会断开连接。我的经验是MQTT心跳设成60秒,WiFi保活靠底层自动处理,实测不会断连。
6.4 从复用角度进一步改造
你完全可以把这套双通道代码框架抽出来当作“模板工程”,以后做任何ESP32联网项目时直接套用。我的做法是写一个HomeNode_Base类,初始化时统一注册WiFi连接状态回调、BLE配网回调、MQTT消息回调,业务代码只关心三个事件:设备配置变化、收到上游指令、传感器上报触发。这样一来,不同的执行器项目只需要改handleControlCommand()和传感器采集函数,开发效率提升很明显。
最后一件事,把这套方案落地时,我最珍惜的是“反复调试后设备还能正常配网”的体验。以前做纯WiFi设备,每次换网络环境都要连串口线重新刷配置,非常痛苦。BLE配网通道一旦跑通,整个设备的交付流程就从“开发者模式”变成了“用户模式”,这就真正配得上“一站式”这三个字。后续如果你也准备动手做这套方案,建议先从“双通道控制一个继电器”这个最小闭环开始,跑通之后再加传感器、再加Web页面、再接MQTT,一步步把复杂度加上去,这样排查问题时永远知道是哪一层出的问题。