1. 为什么我们需要“一键配网”?
如果你玩过ESP8266或者ESP32,肯定对下面这个场景不陌生:拿到一块新开发板,兴冲冲地写好了一个联网控制LED的程序,结果第一步就卡住了——怎么让这个小玩意儿连上家里的Wi-Fi?传统做法是,你得在代码里硬编码你的Wi-Fi名称(SSID)和密码,编译、上传。万一换个地方,或者密码改了,就得重新修改代码、重新上传,麻烦不说,对普通用户来说简直就是灾难。想象一下,你做了一个智能插座送给爸妈,难道还要教他们怎么用Arduino IDE改代码吗?
所以,“配网”成了智能硬件产品化的第一个拦路虎。而“一键配网”的目标,就是让用户完全无需在设备上做任何复杂的输入,甚至不需要知道Wi-Fi密码具体是什么(当然手机本身得连上这个Wi-Fi),就能让设备轻松入网。这体验,就像用手机给蓝牙耳机配对一样简单。今天我们要聊的,就是结合了SmartConfig技术和我们每天都会打开的微信,来实现这个优雅的解决方案。我做过不少物联网项目,实测下来,这套方案非常稳定,用户体验的提升是质的飞跃。
2. 核心原理:SmartConfig与微信生态如何“握手”?
2.1 SmartConfig:设备如何“听”到Wi-Fi密码?
SmartConfig(也叫ESP-TOUCH)是乐鑫公司为其ESP8266/ESP32芯片提供的一种专利配网技术。它的原理非常巧妙,我把它比喻成“隔空传音”。
你的手机和ESP设备在配网前,都连接在同一个Wi-Fi路由器下吗?不,恰恰相反。此时,ESP设备处于混杂模式(Promiscuous Mode),就像一个站在十字路口、耳朵特别灵的人,它能监听到这个区域内所有的无线数据包,但自己还没加入任何网络。
配网时,手机App(或者我们用的微信小程序)会通过手机本身的Wi-Fi接口,发送一系列特殊的UDP广播包。这些数据包的内容里,就编码了你要连接的目标Wi-Fi的SSID和密码。关键点来了:这些包并不是发给ESP设备的(因为设备还没网络地址),而是以一种“喊话”的形式发向空中。
ESP设备在监听模式下,“听”到了这些特殊的包,从中解析出SSID和密码。一旦拿到这些信息,它就会立刻尝试用这个密码去连接对应的Wi-Fi路由器。连接成功后,它通常会再通过UDP或TCP向手机报告自己的IP地址,完成整个“握手”过程。
整个过程,用户只需要在手机上输入Wi-Fi密码(或者从系统获取),而设备端完全“无感”,实现了零输入配网。
2.2 微信小程序:为什么它是完美的配网入口?
理解了SmartConfig,我们再来看微信。为什么选择微信小程序或公众号作为配网入口,而不是自己开发一个独立的App?
第一,零安装成本。用户无需下载任何额外的App,扫个码或者搜一下公众号就能用,转化路径极短。对于轻量级智能硬件(比如一个智能灯、一个温湿度计),让用户为你专门下载一个App是非常困难的事。
第二,生态成熟,接口稳定。微信提供了完善的蓝牙、Wi-Fi、局域网通信等API。特别是wx.startWifi()和wx.sendSocketMessage等API,可以让我们的小程序获取当前手机连接的Wi-Fi信息,并发送UDP广播包,完美契合SmartConfig的需求。
第三,用户信任度高。在微信环境内操作,用户心理门槛低,感觉更安全。
所以,我们的技术架构就清晰了:微信小程序作为前端,负责触发并执行SmartConfig协议;ESP8266/ESP32设备作为后端,负责监听并解析协议,完成联网。 两者通过空气中的无线电波完成一次优雅的协作。
3. 实战开始:搭建你的微信配网小程序(前端)
虽然我们不是专业的前端开发,但借助微信开发者工具,实现一个简单的配网页面并不复杂。这里我分享一个最核心的流程和代码片段。
首先,你需要注册一个微信小程序账号,获取AppID。然后安装微信开发者工具,创建一个新的小程序项目。
配网页面的核心逻辑集中在两个环节:获取当前Wi-Fi信息和发送SmartConfig广播包。微信小程序API对网络操作有域名限制,通常我们需要在开发设置中配置不校验合法域名,以便进行局域网UDP广播。
下面是一个极简的index.js页面逻辑示例:
Page({
data: {
wifiSSID: '',
wifiBSSID: '',
password: '',
status: '等待开始'
},
onLoad: function() {
this.initWifi();
},
// 初始化Wi-Fi模块,获取当前连接信息
initWifi: function() {
const that = this;
wx.startWifi({
success: function(res) {
console.log('Wi-Fi模块初始化成功');
wx.getConnectedWifi({
success: function(res) {
const wifi = res.wifi;
that.setData({
wifiSSID: wifi.SSID,
wifiBSSID: wifi.BSSID
});
console.log('当前连接Wi-Fi:', wifi.SSID);
},
fail: function(err) {
console.error('获取Wi-Fi信息失败:', err);
that.setData({status: '获取Wi-Fi信息失败,请检查手机连接'});
}
});
},
fail: function(err) {
console.error('初始化Wi-Fi失败:', err);
}
});
},
// 用户输入密码后,触发配网
startSmartConfig: function() {
const that = this;
const ssid = this.data.wifiSSID;
const bssid = this.data.wifiBSSID;
const password = this.data.password;
if (!password) {
wx.showToast({ title: '请输入Wi-Fi密码', icon: 'none' });
return;
}
this.setData({ status: '正在发送配网信息...' });
// 注意:此处为模拟逻辑。实际SmartConfig包发送需要调用更底层的Socket接口,
// 并按照乐鑫的协议编码SSID、密码和BSSID(可选)。
// 微信小程序官方可能没有直接提供发送原始UDP广播包的API,
// 一种常见做法是,小程序将SSID和密码通过HTTPS发送到你的云端服务器,
// 由服务器生成一个临时的Token。然后小程序在局域网内,通过UDP或TCP向特定端口(如8266)广播这个Token。
// ESP设备监听到Token后,再向服务器请求真正的Wi-Fi配置信息。
// 这是对纯SmartConfig的一种变通,兼容性更好,且不依赖微信支持原始UDP广播。
// 伪代码:假设我们有一个云函数 `getSmartConfigToken`
wx.cloud.callFunction({
name: 'getSmartConfigToken',
data: { ssid: ssid, bssid: bssid, password: password },
success: function(res) {
const token = res.result.token;
that.sendUDPBroadcast(token); // 调用自定义方法发送UDP广播
},
fail: function(err) {
console.error('获取配网令牌失败:', err);
that.setData({ status: '配网启动失败' });
}
});
},
// 模拟发送UDP广播(实际需用wx.createUDPSocket)
sendUDPBroadcast: function(token) {
const udp = wx.createUDPSocket();
udp.bind();
// 向广播地址 255.255.255.255 和 端口 18266 (示例)发送数据
const broadcastAddress = '255.255.255.255';
const port = 18266;
const arrayBuffer = this.stringToArrayBuffer(token);
udp.send({
address: broadcastAddress,
port: port,
message: arrayBuffer,
success: function() {
console.log('UDP广播发送成功');
// 可以重复发送几次增加成功率
},
fail: function(err) {
console.error('UDP广播发送失败:', err);
}
});
},
// 工具函数:字符串转ArrayBuffer
stringToArrayBuffer: function(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0; i < str.length; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
})
注意:由于微信小程序平台策略和API限制,直接实现标准的乐鑫SmartConfig协议可能受限。上述代码中的云函数方案是一种更通用、更可靠的变通方法。其核心思想是**“间接配网”**:小程序将配置信息安全地交给云端,云端生成一个短期有效的配网码;小程序在局域网内广播这个码;设备收到码后,再从云端拉取真正的配置。这避免了在小程序端处理敏感密码,也绕开了原始UDP广播可能存在的限制。
4. 设备端编程:让ESP8266/ESP32“听懂”指令(后端)
前端准备好了,接下来就是重头戏:设备端的Arduino程序。我们要让ESP芯片能够监听网络上的配网广播,并做出响应。这里我提供一个功能完整、注释清晰的代码框架,你几乎可以直接使用。
4.1 基础监听与配网代码
首先,你需要安装好ESP8266或ESP32的Arduino开发环境。这里以ESP8266为例,ESP32的修改方法我会在后面说明。
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
// ******************** 配置区 ********************
// 这里不需要预先填写Wi-Fi信息!
const int udpListenPort = 18266; // 监听UDP广播的端口,需与小程序的发送端口一致
const char* cloudServer = "api.your-cloud.com"; // 你的云端服务器地址(如果使用Token方案)
// ************************************************
WiFiUDP Udp;
bool smartConfigReceived = false;
String configToken = ""; // 用于存储接收到的配网Token
unsigned long configStartTime = 0;
const unsigned long CONFIG_TIMEOUT = 60000; // 配网超时时间(60秒)
void setup() {
Serial.begin(115200);
Serial.println("\n\n设备启动,等待配网...");
// 尝试连接之前保存的Wi-Fi(首次启动会失败)
WiFi.begin();
delay(100);
// 等待一段时间看是否自动连接成功
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 20) {
delay(500);
Serial.print(".");
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n自动连接历史Wi-Fi成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
// 连接成功后,就可以开始你的主业务逻辑了
startMainBusiness();
} else {
Serial.println("\n未找到历史Wi-Fi配置,进入配网监听模式...");
startSmartConfigListener();
}
}
void loop() {
// 如果还没配网成功,就持续监听
if (!smartConfigReceived) {
listenForSmartConfig();
// 检查是否超时
if (millis() - configStartTime > CONFIG_TIMEOUT) {
Serial.println("配网超时,重启监听...");
configStartTime = millis(); // 重置超时计时,或者可以重启设备
}
} else {
// 配网成功后,执行主循环业务
// 例如:连接MQTT服务器、读取传感器、控制IO等
mainBusinessLoop();
delay(100); // 适当的延时
}
}
// 启动UDP监听服务
void startSmartConfigListener() {
Udp.begin(udpListenPort);
Serial.printf("开始监听UDP端口: %d\n", udpListenPort);
configStartTime = millis();
}
// 监听UDP广播包
void listenForSmartConfig() {
int packetSize = Udp.parsePacket();
if (packetSize) {
Serial.printf("收到来自 %s, 端口 %d 的数据包\n", Udp.remoteIP().toString().c_str(), Udp.remotePort());
char incomingPacket[255]; // 缓冲区
int len = Udp.read(incomingPacket, 254);
if (len > 0) {
incomingPacket[len] = 0; // 字符串结束符
}
Serial.printf("UDP包内容: %s\n", incomingPacket);
// 解析接收到的数据
configToken = String(incomingPacket);
// 假设我们收到的是一个云端Token,接下来向云端请求真实配置
requestConfigFromCloud(configToken);
}
}
// 向云端请求真实的Wi-Fi配置
void requestConfigFromCloud(String token) {
Serial.println("向云端请求Wi-Fi配置...");
// 使用WiFiClient或HTTPClient发起HTTPS请求
WiFiClient client;
const int httpPort = 80;
if (!client.connect(cloudServer, httpPort)) {
Serial.println("连接云端服务器失败");
return;
}
// 构建HTTP GET请求
String url = "/api/get_wifi_config?token=" + token;
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + cloudServer + "\r\n" +
"Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println(">>> 客户端超时 !");
client.stop();
return;
}
}
// 读取响应,这里简化处理,假设响应体是 JSON: {"ssid":"YourWiFi","password":"YourPassword"}
String response = "";
while(client.available()){
response += client.readStringUntil('\r');
}
Serial.println("收到云端响应:");
Serial.println(response);
// 你需要一个JSON解析库(如ArduinoJson)来解析响应
// 假设我们手动解析(仅作演示,生产环境请用库)
int ssidStart = response.indexOf("\"ssid\":\"") + 8;
int ssidEnd = response.indexOf("\"", ssidStart);
int pwdStart = response.indexOf("\"password\":\"") + 12;
int pwdEnd = response.indexOf("\"", pwdStart);
if (ssidStart > 7 && pwdStart > 11) {
String ssid = response.substring(ssidStart, ssidEnd);
String password = response.substring(pwdStart, pwdEnd);
Serial.printf("解析到SSID: %s, Password: %s\n", ssid.c_str(), password.c_str());
// 尝试连接Wi-Fi
connectToWiFi(ssid, password);
}
client.stop();
}
// 使用获取到的SSID和密码连接Wi-Fi
void connectToWiFi(String ssid, String password) {
Serial.printf("尝试连接Wi-Fi: %s\n", ssid.c_str());
WiFi.begin(ssid.c_str(), password.c_str());
int retryCount = 0;
while (WiFi.status() != WL_CONNECTED && retryCount < 30) { // 尝试30次,约15秒
delay(500);
Serial.print(".");
retryCount++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWi-Fi连接成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
smartConfigReceived = true;
// 连接成功后,可以向手机或云端发送成功通知(可选)
sendConfigSuccessNotice();
// 开始主业务
startMainBusiness();
} else {
Serial.println("\nWi-Fi连接失败,请检查密码或信号强度。");
// 可以重置监听状态,等待下一次配网
smartConfigReceived = false;
}
}
// 配网成功后的通知(例如通过UDP回传或HTTP请求)
void sendConfigSuccessNotice() {
// 示例:向发送配网Token的源IP发送一个成功回执
// 实际项目中,可以调用云API更新设备状态
Serial.println("配网成功,发送通知...");
// 实现略
}
// 你的主业务初始化函数
void startMainBusiness() {
Serial.println("主业务启动...");
// 在这里初始化MQTT、传感器、IO等
// 例如:pinMode(LED_BUILTIN, OUTPUT);
}
// 你的主业务循环函数
void mainBusinessLoop() {
// 在这里写你的主要控制逻辑
// 例如:digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // LED闪烁
// delay(1000);
}
这段代码实现了一个完整的、基于Token间接配网的流程。它比简单的SmartConfig更健壮,因为将复杂的协议解析和密码传输交给了更可靠的云端服务器,设备端只负责监听一个简单的Token,安全性也更高。
4.2 适配ESP32的修改要点
如果你使用的是ESP32,代码的修改非常简单,主要就是Wi-Fi库的不同。
- 头文件修改:将
#include <ESP8266WiFi.h>替换为#include <WiFi.h>。 - 开发板选择:在Arduino IDE的“工具”->“开发板”中,选择你的ESP32型号(如“ESP32 Dev Module”)。
- 引脚定义:如果代码中使用了像
D4这样的ESP8266引脚宏定义,需要改为ESP32对应的GPIO数字,例如GPIO_NUM_2,或者直接使用数字2。
其他逻辑和函数调用基本完全一致。乐鑫的Arduino核心库为这两款芯片保持了高度一致的API,这大大降低了我们的移植成本。
5. 调试技巧与常见问题排查
配网过程涉及手机、网络、设备三端,调试起来可能有点头疼。我把自己踩过的坑和总结的技巧分享给你,能帮你节省大量时间。
5.1 串口调试是你的“眼睛”
一定要充分利用串口监视器。在代码的关键节点(如开始监听、收到UDP包、连接Wi-Fi、连接成功/失败)添加详细的Serial.print日志。把波特率设置为115200,打开串口监视器,你就能看到设备内部的实时状态。
典型的问题排查流程:
- 设备根本没启动监听? 检查串口开头日志,看是否打印了“开始监听UDP端口”。如果没有,可能是代码没上传成功,或者ESP模块硬件有问题。
- 收不到UDP广播包? 确保手机和ESP设备在同一个2.4GHz Wi-Fi频段下(ESP一般只支持2.4G)。检查小程序发送的UDP端口号是否和设备监听端口号一致。有些路由器会禁止局域网内的广播包(AP隔离),可以尝试用另一部手机开热点来测试,排除路由器问题。
- 收到包但连接Wi-Fi失败? 查看串口打印的SSID和密码是否正确。特别注意Wi-Fi密码是否包含特殊字符,在传输过程中是否被错误编码。检查Wi-Fi信号强度(RSSI),信号太弱也会导致连接失败。
- 连接成功但获取不到IP? 可能是路由器DHCP服务器的问题,尝试重启路由器,或者为ESP设备设置静态IP(在
WiFi.config()中设置)。
5.2 网络环境与路由器的影响
这是最容易出问题的地方。我强烈建议在开发阶段,使用手机热点作为测试网络。因为手机热点通常功能简单,没有企业级路由器那些复杂的防火墙、AP隔离、多频段聚合等功能,干扰最少,成功率最高。
如果必须在家庭路由器下测试,请进入路由器管理后台,检查以下设置:
- 关闭“AP隔离”或“客户端隔离”:这个功能会禁止连接在同一个路由器下的设备之间互相通信,UDP广播自然也就被屏蔽了。
- 确保使用2.4GHz网络:ESP8266/ESP32通常不支持5GHz Wi-Fi。
- 尝试关闭路由器的防火墙(仅临时测试)。
5.3 微信小程序的调试
微信开发者工具提供了真机调试功能,但网络相关API在模拟器上可能行为不一致。务必使用“真机调试”。在手机上预览小程序,并打开调试模式(vConsole),查看console.log输出的网络请求和错误信息。
对于UDP发送,如果遇到权限问题,检查小程序的app.json文件中是否声明了所需的网络权限:
{
"requiredPrivateInfos": [
"getWifiList",
"startWifi",
"onWifiConnected",
"getConnectedWifi"
]
}
6. 进阶优化:让配网体验更稳定可靠
基础功能跑通后,我们可以从产品化角度考虑,做一些优化来提升成功率和用户体验。
6.1 增加超时与重试机制
就像上面的示例代码中已经有了CONFIG_TIMEOUT,设备不能无限期等待。通常设置60-120秒的超时时间比较合适。超时后,设备可以自动重启并重新进入配网模式,或者通过闪烁特定的LED灯来提示用户配网失败。
在小程序端,发送UDP广播包也应该有间隔地重复发送多次(比如每秒一次,连续发10秒),而不是只发一次,以应对可能的丢包。
6.2 提供多种备选配网方案
SmartConfig虽然方便,但受环境影响大。一个成熟的产品应该有Plan B。常见的备选方案包括:
- 蓝牙配网(BLE):对于ESP32,可以同时开启BLE。小程序通过蓝牙将Wi-Fi配置信息发送给设备。这种方式速度极快,抗干扰能力强,是很多智能家居产品的首选。
- AP模式(热点)配网:让ESP设备自己变成一个Wi-Fi热点(如“ESP32-Config”),手机连接这个热点后,访问一个内置的网页(Web Server),在网页上选择家庭Wi-Fi并输入密码。这是最通用、兼容性最好的方案,但步骤稍多。
可以在设备启动时,先尝试快速SmartConfig(比如30秒),如果失败,则自动切换到AP模式,并通过LED灯颜色变化提示用户切换配网方式。
6.3 状态反馈与用户引导
用户需要知道当前在发生什么。可以通过设备上的LED灯来提供状态反馈:
- 慢闪(如每秒1次):等待配网中。
- 快闪:正在接收数据/尝试连接Wi-Fi。
- 常亮:配网成功,已连接网络。
- 特定错误码闪烁(如连闪3次停顿):配网失败,引导用户查看手册。
在小程序界面,也要有清晰的进度提示:“正在搜索设备” -> “发送网络中” -> “设备连接中” -> “成功!”或“失败,请重试”。
6.4 安全性考量
在我们的Token方案中,密码通过HTTPS传输到你的云端服务器,再由服务器下发给设备,避免了在局域网内明文传播密码,安全性更好。此外,Token应该是一次性的、短时间有效的(比如5分钟),即使被截获也无法重复使用。云端服务器还需要对配网请求做简单的频率限制,防止被暴力攻击。
最后,记得在产品固件中提供一个“恢复出厂设置”的物理方式,比如长按某个按键10秒。这样即使配网信息错误导致设备“失联”,用户也能轻松重置,重新开始。就像原始文章里提到的,连续按5次RESET键,这个设计就很直观。实现起来也不难,在loop()函数里检测按键次数,达到阈值后调用WiFi.disconnect(true)来清除保存的Wi-Fi配置,然后重启即可。

326

被折叠的 条评论
为什么被折叠?



