You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 14:06:18