如何通过Flutter实现指定ESP32设备的SmartConfig配置
问题:ESP32 SmartConfig批量配置问题
我正在开发一款配套Android App的IoT设备,硬件采用ESP32,App端使用Flutter开发。已通过esp_smartconfig包完成SmartConfig代码编写,代码运行正常,但存在一个问题:它会配置所有处于SmartConfig模式的ESP32设备,我希望仅对列表中选中的某一特定设备进行配置。
原Flutter代码
import 'package:flutter/material.dart'; import 'package:esp_smartconfig/esp_smartconfig.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter ESP SmartConfig Demo'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { final provisioner = Provisioner.espTouch(); String msg = 'Press Button to Upload'; @override void initState() { super.initState(); } Future<void> _incrementCounter() async { PermissionWithService locationPermission = Permission.locationWhenInUse; var permissionStatus = await locationPermission.status; if (permissionStatus == PermissionStatus.denied) { permissionStatus = await locationPermission.request(); if (permissionStatus == PermissionStatus.denied) { permissionStatus = await locationPermission.request(); } } if (permissionStatus == PermissionStatus.granted) { bool isLocationServiceOn = await locationPermission.serviceStatus.isEnabled; if (isLocationServiceOn) { final info = NetworkInfo(); String ssid = await info.getWifiName() as String; String bssid = await info.getWifiBSSID() as String; setState(() { msg = "Sending WiFi Details"; }); await provisioner.start(ProvisioningRequest.fromStrings( ssid: ssid.split('"')[1], bssid: bssid, password: '123454321')); } else { print('Location Service is not enabled'); } } await Future.delayed(const Duration(seconds: 10)); provisioner.stop(); setState(() { msg = "Press Button"; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( msg, style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
原ESP32-Arduino代码
#include "WiFi.h" #include "EEPROM.h" #define LENGTH(x) (strlen(x) + 1) // length of char string #define EEPROM_SIZE 200 // EEPROM size #define WiFi_rst 0 //WiFi credential reset pin (Boot button on ESP32) #define LED 2 String ssid; //string variable to store ssid String pss; //string variable to store password unsigned long rst_millis; void setup() { Serial.begin(115200); //Init serial pinMode(WiFi_rst, INPUT); pinMode(LED, OUTPUT); digitalWrite(LED, LOW); if (!EEPROM.begin(EEPROM_SIZE)) { //Init EEPROM Serial.println("failed to init EEPROM"); delay(1000); } else { ssid = readStringFromFlash(0); // Read SSID stored at address 0 Serial.print("SSID = "); Serial.println(ssid); pss = readStringFromFlash(40); // Read Password stored at address 40 Serial.print("psss = "); Serial.println(pss); } WiFi.begin(ssid.c_str(), pss.c_str()); delay(3500); // Wait for a while till ESP connects to WiFi if (WiFi.status() != WL_CONNECTED) // if WiFi is not connected { //Init WiFi as Station, start SmartConfig WiFi.mode(WIFI_AP_STA); WiFi.beginSmartConfig(); //Wait for SmartConfig packet from mobile Serial.println("Waiting for SmartConfig."); while (!WiFi.smartConfigDone()) { digitalWrite(LED, !digitalRead(LED)); delay(100); Serial.print("."); } Serial.println(""); Serial.println("SmartConfig received."); //Wait for WiFi to connect to AP Serial.println("Waiting for WiFi"); while (WiFi.status() != WL_CONNECTED) { digitalWrite(LED, !digitalRead(LED)); delay(500); Serial.print("."); } Serial.println("WiFi Connected."); digitalWrite(LED, HIGH); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); // read the connected WiFi SSID and password ssid = WiFi.SSID(); pss = WiFi.psk(); Serial.print("SSID:"); Serial.println(ssid); Serial.print("PSS:"); Serial.println(pss); Serial.println("Store SSID & PSS in Flash"); writeStringToFlash(ssid.c_str(), 0); // storing ssid at address 0 writeStringToFlash(pss.c_str(), 40); // storing pss at address 40 } else { Serial.println("WiFi Connected"); digitalWrite(LED, HIGH); } } void loop() { rst_millis = millis(); while (digitalRead(WiFi_rst) == LOW) { // Wait till boot button is pressed } if (millis() - rst_millis >= 3000) { Serial.println("Reseting the WiFi credentials"); writeStringToFlash("", 0); // Reset the SSID writeStringToFlash("", 40); // Reset the Password Serial.println("Wifi credentials erased"); Serial.println("Restarting the ESP"); delay(500); ESP.restart(); // Restart ESP } } void writeStringToFlash(const char* toStore, int startAddr) { int i = 0; for (; i < LENGTH(toStore); i++) { EEPROM.write(startAddr + i, toStore[i]); } EEPROM.write(startAddr + i, '\0'); EEPROM.commit(); } String readStringFromFlash(int startAddr) { char in[128]; // char array of size 128 for reading the stored data int i = 0; for (; i < 128; i++) { in[i] = EEPROM.read(startAddr + i); } return String(in); }
解决方案:添加设备标识实现定向配置
默认SmartConfig仅传输WiFi账号密码,所有处于监听状态的ESP32都会接收配置。要实现定向配置,需在SmartConfig数据包中加入唯一设备标识,ESP32只处理匹配自身标识的指令。
步骤1:ESP32端修改
- 用ESP32的MAC地址作为天然唯一标识,无需额外配置
- 解析SmartConfig中的自定义JSON数据,验证设备标识后才执行配置
- 需要提前安装
ArduinoJson库用于JSON解析
修改后的ESP32代码:
#include "WiFi.h" #include "EEPROM.h" #include <ArduinoJson.h> #define LENGTH(x) (strlen(x) + 1) #define EEPROM_SIZE 200 #define WiFi_rst 0 #define LED 2 String ssid; String pss; String deviceId; unsigned long rst_millis; void setup() { Serial.begin(115200); pinMode(WiFi_rst, INPUT); pinMode(LED, OUTPUT); digitalWrite(LED, LOW); // 获取MAC地址作为设备唯一ID uint8_t macAddr[6]; WiFi.macAddress(macAddr); char macStr[18]; sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); deviceId = String(macStr); Serial.print("Device ID: "); Serial.println(deviceId); if (!EEPROM.begin(EEPROM_SIZE)) { Serial.println("failed to init EEPROM"); delay(1000); } else { ssid = readStringFromFlash(0); Serial.print("SSID = "); Serial.println(ssid); pss = readStringFromFlash(40); Serial.print("psss = "); Serial.println(pss); } WiFi.begin(ssid.c_str(), pss.c_str()); delay(3500); if (WiFi.status() != WL_CONNECTED) { WiFi.mode(WIFI_AP_STA); WiFi.beginSmartConfig(); Serial.println("Waiting for SmartConfig."); while (!WiFi.smartConfigDone()) { digitalWrite(LED, !digitalRead(LED)); delay(100); Serial.print("."); } Serial.println("\nSmartConfig received."); String configData = WiFi.smartConfigGetData(); Serial.print("Received data: "); Serial.println(configData); // 解析JSON配置数据 StaticJsonDocument<256> doc; DeserializationError error = deserializeJson(doc, configData); if (error) { Serial.print("JSON parse failed: "); Serial.println(error.f_str()); ESP.restart(); return; } // 验证设备标识 String targetId = doc["deviceId"].as<String>(); if (targetId != deviceId) { Serial.println("Config not for this device, ignoring."); ESP.restart(); return; } // 获取并连接WiFi ssid = doc["ssid"].as<String>(); pss = doc["password"].as<String>(); WiFi.begin(ssid.c_str(), pss.c_str()); Serial.println("Waiting for WiFi"); while (WiFi.status() != WL_CONNECTED) { digitalWrite(LED, !digitalRead(LED)); delay(500); Serial.print("."); } Serial.println("\nWiFi Connected."); digitalWrite(LED, HIGH); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); // 保存WiFi信息到EEPROM writeStringToFlash(ssid.c_str(), 0); writeStringToFlash(pss.c_str(), 40); } else { Serial.println("WiFi Connected"); digitalWrite(LED, HIGH); } } void loop() { rst_millis = millis(); while (digitalRead(WiFi_rst) == LOW); if (millis() - rst_millis >= 3000) { Serial.println("Reseting WiFi credentials"); writeStringToFlash("", 0); writeStringToFlash("", 40); Serial.println("Credentials erased, restarting..."); delay(500); ESP.restart(); } } void writeStringToFlash(const char* toStore, int startAddr) { int i = 0; for (; i < LENGTH(toStore); i++) { EEPROM.write(startAddr + i, toStore[i]); } EEPROM.write(startAddr + i, '\0'); EEPROM.commit(); } String readStringFromFlash(int startAddr) { char in[128]; int i = 0; for (; i < 128; i++) { in[i] = EEPROM.read(startAddr + i); } return String(in); }
步骤2:Flutter端修改
- 添加设备选择页面,展示扫描到的ESP32设备
- 发送包含目标设备ID和WiFi信息的JSON格式配置数据
修改后的Flutter代码:
import 'package:flutter/material.dart'; import 'package:esp_smartconfig/esp_smartconfig.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'dart:convert'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'ESP32 定向配置', theme: ThemeData(primarySwatch: Colors.blue), home: const DeviceSelectionPage(), ); } } class DeviceSelectionPage extends StatefulWidget { const DeviceSelectionPage({Key? key}) : super(key: key); @override State<DeviceSelectionPage> createState() => _DeviceSelectionPageState(); } class _DeviceSelectionPageState extends State<DeviceSelectionPage> { final provisioner = Provisioner.espTouch(); List<String> availableDevices = []; String? selectedDeviceId; String msg = '扫描设备中...'; bool isScanning = true; @override void initState() { super.initState(); _scanDevices(); } // 扫描处于SmartConfig模式的设备(实际项目需实现UDP广播监听逻辑) Future<void> _scanDevices() async { await Future.delayed(const Duration(seconds: 2)); setState(() { availableDevices = [ "AA:BB:CC:DD:EE:FF", "11:22:33:44:55:66" ]; isScanning = false; msg = '请选择要配置的设备'; }); } Future<void> _sendConfig() async { if (selectedDeviceId == null) { setState(() => msg = '请先选择设备'); return; } PermissionWithService locationPermission = Permission.locationWhenInUse; var status = await locationPermission.status; if (status == PermissionStatus.denied) { status = await locationPermission.request(); if (status == PermissionStatus.denied) { setState(() => msg = '需要位置权限才能配置'); return; } } if (status == PermissionStatus.granted) { bool locationEnabled = await locationPermission.serviceStatus.isEnabled; if (locationEnabled) { final info = NetworkInfo(); String ssid = await info.getWifiName() as String; String bssid = await info.getWifiBSSID() as String; // 构造包含设备ID的JSON配置 final configData = jsonEncode({ "deviceId": selectedDeviceId, "ssid": ssid.split('"')[1], "password": "123454321" }); setState(() => msg = "发送配置中..."); await provisioner.start(ProvisioningRequest.fromStrings( ssid: ssid.split('"')[1], bssid: bssid, password: "123454321", data: configData, )); await Future.delayed(const Duration(seconds: 10)); provisioner.stop(); setState(() => msg = "配置完成"); } else { setState(() => msg = '请开启位置服务'); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('ESP32 定向配置')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(msg, style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center), const SizedBox(height
相关产品推荐
相关产品推荐

