ESP32对接巴法云实现配网

发布于:2025-05-31 ⋅ 阅读:(22) ⋅ 点赞:(0)

序言

本文部分内容摘抄原创作者巴法云-做优秀的物联网平台
代码有部分修改并测试运行正常

巴法云支持免费用户通过开发对接实现各智能音箱设备语音控制智能家居设备,并有自己的App进行配网和控制,在开发过程中省去了很多工作,推荐大家使用

巴法云关于个人开发者使用描述

准备工作

巴法云注册与使用

巴法云开发者文档
进入巴法云
下载App
首先需要在巴法云注册用户。
注册完成之后下载App并登录
至此巴法云准备工作就完成啦

Arduino准备

本次开发基于Arduino
请移步ESP32入门之arduino IDE环境搭建 1
转载

开发

以下是实现的具体配网流程代码

//需要在arduino IDE软件中---工具-->管理库-->搜索arduinojson并安装
#include <WiFi.h>
#include <WiFiUDP.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <HTTPClient.h>

//根据需要修改的信息
String aptype = "009";   //设备类型,001插座设备,002灯类设备,003风扇设备,005空调,006开关,009窗帘
String Name = "窗帘";    //设备昵称,可随意修改
String verSion = "3.1";  //3是tcp设备端口8344,1是MQTT设备
String room = "卧室";    //房间。例如客厅、卧室等,默认空
int protoType = 3;       //3是tcp设备端口8344,1是MQTT设备
int adminID = 0;         //默认空即可。企业id,建议企业用户配置,该设备会自动绑定到该企业下,获取id方法见接入文档5.17节
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;

//检测是否是第一次连接WIFI
bool firstWIfiConfig = false;
String topic = "";
struct config_type {
  char stassid[32];
  char stapsw[16];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
};
config_type config;


char config_flag = 0;      //判断是否配网
#define MAGIC_NUMBER 0xAA  //判断是否配网
char packetBuffer[255];    //发送数据包
WiFiUDP Udp;


/*
 * 从EEPROM加载参数
*/
uint8_t* p = (uint8_t*)(&config);
void loadConfig() {

  uint8_t mac[6];
  Serial.println(" LoadConfig.......");
  WiFi.macAddress(mac);
  EEPROM.begin(512);
  for (int i = 0; i < sizeof(config); i++) {
    *(p + i) = EEPROM.read(i);
  }
  config.reboot = config.reboot + 1;
  if (config.reboot >= 4) {
    restoreFactory();
  }
  if (config.magic != 0xAA) {
    config_flag = 1;
  }
  EEPROM.begin(512);
  for (int i = 0; i < sizeof(config); i++) {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
  delay(2000);
  Serial.println("loadConfig Over");
  EEPROM.begin(512);
  config.reboot = 0;
  for (int i = 0; i < sizeof(config); i++) {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}


/* 
 * 恢复出厂设置
*/
void restoreFactory() {
  Serial.println("\r\n Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  config.magic = 0x00;
  saveConfig();
  delayRestart(1);
}
/*
保存WIFI信息
*/
void saveConfig() {
  config.reboot = 0;
  EEPROM.begin(512);  // 与loadConfig统一为512字节(足够存储config结构体)
  uint8_t* p = (uint8_t*)(&config);
  for (int i = 0; i < sizeof(config); i++) {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
Ticker delayTimer;
void delayRestart(float t) {
  delayTimer.attach(t, []() {
    ESP.restart();
  });
}
void apConfig(String mac) {
  if (config_flag == 1) {
    WiFi.softAP("bemfa_" + mac);
    Udp.begin(8266);
    Serial.println("Started Ap Config...");
  }
  topic = mac + aptype;
  // Removed blocking while loop
}

/*
  第一次配网检查WIFI,保存WIFI配置信息,并创建主题
*/
void checkFirstConfig() {

  if (firstWIfiConfig) {
    // 设置目标 URL
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://pro.bemfa.com/vs/web/v1/deviceAddTopic");

    // 创建 JSON 对象
    StaticJsonDocument<200> jsonDoc;
    jsonDoc["uid"] = config.cuid;
    jsonDoc["name"] = Name;
    jsonDoc["topic"] = topic;
    jsonDoc["type"] = protoType;
    jsonDoc["room"] = room;
    jsonDoc["adminID"] = adminID;
    jsonDoc["wifiConfig"] = 1;  //必填字段

    // 将 JSON 对象转换为字符串
    String jsonString;
    serializeJson(jsonDoc, jsonString);
    http_bemfa_HTTPClient.addHeader("Content-Type", "application/json; charset=UTF-8");
    // 发送请求
    int httpCode = http_bemfa_HTTPClient.POST(jsonString);
    if (httpCode == 200) {
      Serial.println("POST succeeded with code:");
      Serial.println(httpCode);
      String payload = http_bemfa_HTTPClient.getString();
      Serial.println(payload);

      //json数据解析
      StaticJsonDocument<200> doc;
      DeserializationError error = deserializeJson(doc, payload);
      if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
      }

      int code = doc["code"];
      if (code == 0) {
        int resCode = doc["data"]["code"];
        if (resCode == 40006 || resCode == 0) {
          String docUID = doc["uid"];
          Serial.print("create topic ok:");
          Serial.println(topic);
          if (firstWIfiConfig) {
            config.reboot = 0;
            config.magic = 0xAA;
            saveConfig();
          }
        } else {
          Serial.println(" config ERROR.........");
        }
      } else {
        Serial.println(" config ERROR.........");
      }
    } else if (httpCode != 200) {
      Serial.println("POST failed with code:");
      Serial.println(httpCode);
    } else {
      Serial.println("Unknown error");
    }

    http_bemfa_HTTPClient.end();
  }
}

// 复位或上电后运行一次:
void setup() {
  //在这里加入初始化相关代码,只运行一次:
  Serial.begin(115200);
  String mac = WiFi.macAddress();
  mac.replace(":", "");                            //去掉:号
  topic = mac.substring(8) + aptype; //取mac地址的后半部分做主题用,并拼接设备类型
  
  // 初始化WiFi模式以确保MAC地址正确获取(关键修改)
  WiFi.mode(WIFI_STA);  // 设置为STA模式初始化硬件
  delay(1000);           // 等待WiFi模块初始化完成

  // 初始化配网(此时可正确获取MAC地址)
  mac = WiFi.macAddress();
  mac.replace(":", "");

  loadConfig();                                      //加载存储的数据
  apConfig(mac);                                   //加载ap

  // Only try to connect to WiFi if not in config mode
  if (config_flag == 0) {
    Serial.println("Connecting to WiFi...");
    WiFi.disconnect();                          //断开连接
    WiFi.mode(WIFI_STA);                        //STA模式
    WiFi.begin(config.stassid, config.stapsw);  //连接路由器
    // Removed blocking while loop here
  }
}

//一直循环执行:
void loop() {

  if (config_flag == 1) { // If in config mode, handle UDP packets
    int packetSize = Udp.parsePacket();
    if (packetSize) {
      Serial.print("Received packet of size ");
      Serial.println(packetSize);
      Serial.print("From ");
      IPAddress remoteIp = Udp.remoteIP();
      Serial.print(remoteIp);
      Serial.print(", port ");
      Serial.println(Udp.remotePort());


      int len = Udp.read(packetBuffer, 255);
      if (len > 0) {
        packetBuffer[len] = 0;
      }
      Serial.println("Contents:");
      Serial.println(packetBuffer);
      StaticJsonDocument<200> doc;

      DeserializationError error = deserializeJson(doc, packetBuffer);
      if (error) {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      int cmdType = doc["cmdType"].as<int>();

      if (cmdType == 1) {
        const char* ssid = doc["ssid"];
        const char* password = doc["password"];
        const char* token = doc["token"];
        strcpy(config.stassid, ssid);
        strcpy(config.stapsw, password);
        strcpy(config.cuid, token);
        //收到信息,并回复
        String ReplyBuffer = "{\"cmdType\":2,\"productId\":\"" + topic + "\",\"deviceName\":\"" + Name + "\",\"protoVersion\":\"" + verSion + "\"}";

        const char* replyBufferData = ReplyBuffer.c_str();
        size_t replyBufferLength = ReplyBuffer.length();

        Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
        Udp.write((const uint8_t*)replyBufferData, replyBufferLength);
        Udp.endPacket();

      } else if (cmdType == 3) {
        config_flag = 0;
        firstWIfiConfig = true;
        
        // 彻底停止UDP并清理网络资源
        Udp.stop();
        WiFi.disconnect(true);  // 强制断开所有连接(包括AP和STA)
        WiFi.mode(WIFI_OFF);    // 关闭所有WiFi模式
        delay(1500);            // 延长等待时间确保硬件完成释放
        
        // 切换为STA模式并连接WiFi(添加连接前的参数校验)
        Serial.println("Connecting to WiFi after provisioning...");
        if (strlen(config.stassid) == 0 || strlen(config.stapsw) == 0) {
          Serial.println("Error: SSID or password is empty");
          return;
        }
        WiFi.mode(WIFI_STA);
        WiFi.begin(config.stassid, config.stapsw);
        
        // 等待连接结果(设置超时避免永久阻塞)
        unsigned long start = millis();
        while (WiFi.status() != WL_CONNECTED && (millis() - start) < 15000) {
          delay(100);
        }
        if (WiFi.status() == WL_CONNECTED) {
          Serial.printf("Connected to %s, IP: %s\n", config.stassid, WiFi.localIP().toString().c_str());
          checkFirstConfig();
        } else {
          Serial.println("WiFi connection failed (timeout)");
        }
      }
    }
  } else { // If not in config mode, run normal operation
      // Your normal device operation code goes here
      // Serial.println("Config success"); // This will print repeatedly, move to setup or only print once
      // Removed delay(1000)
  }
}

代码中的aptype与Name变量将是你在配往后自动添加的设备类型与名称
代码中的protoType参数将会控制您在巴法云建立MQTT主题还是TCP主题,根据智能家居类型自行配置
自行烧录哦,如不会烧录请查询官方文档

开始配网

第一次开机后硬件会检查是否已配置网络,没有配置将会打开WiFi热点,名称为bemfa_mac地址

  1. 连接ESP32需要连接的WiFi不是ESP32的WiFi哦
  2. 打开巴法App-右上角+号或微信小程序搜索巴法,找到一键配网小程序
  3. 进入配网页面,切换协议为AP配网,小程序为Soft AP 配网
  4. WiFi名称会自动填写你当前连接的WiFi,输入WiFi密码,点击开始配网
  5. 此时会提示您连接到设备WiFi,点击打开设置,在WiFi界面连接bemfa开头的WiFi
  6. 回到App或小程序,此时将开始配网流程,硬件接收到信息后开始连接目标WiFi
  7. 连接成功后会在用户的巴法云控制台建立硬件主题,主题一般为:硬件mac地址+设备类型
  8. App或小程序检测到主题创建成功,代表配网结束,在App中就可以看到该设备啦

至此,配网功能结束
当然,该文章仅为使用巴法云方式实现配网功能,开发者也可以通过代码逻辑,来建立自己的MQTT服务,进行自己的个人后端开发,实现家庭智能


  1. ESP32-C6接入巴法云,Arduino方式 ↩︎


网站公告

今日签到

点亮在社区的每一天
去签到