/*

  • ESP32 蓝牙配网(Bluetooth Provisioning)完整示例
  • 功能:通过蓝牙BLE接收WiFi配置信息并连接网络
  • 支持:BLE广播、接收SSID/密码、WiFi连接、状态反馈
  • 作者:示例代码
  • 日期:2025
    */

#include <BLE2902.h> // BLE描述符库(用于通知功能)
#include <BLEDevice.h> // BLE设备库
#include <BLEServer.h> // BLE服务器库
#include <BLEUtils.h> // BLE工具库
#include <Preferences.h> // NVS存储库,用于保存WiFi配置
#include <WiFi.h> // WiFi库,用于连接无线网络

// ==================== 配置参数 ====================
// 蓝牙设备名称(手机端会看到这个名字)
#define BLE_DEVICE_NAME “ESP32_Config”

// 蓝牙服务和特征UUID(通用唯一识别码)
// 这些UUID需要在手机APP中对应配置
#define SERVICE_UUID “4fafc201-1fb5-459e-8fcc-c5c9c331914b” // 配网服务UUID
#define CHARACTERISTIC_UUID_RX “beb5483e-36e1-4688-b7f5-ea07361b26a8” // 接收特征UUID(手机→ESP32)
#define CHARACTERISTIC_UUID_TX “beb5483e-36e1-4688-b7f5-ea07361b26a9” // 发送特征UUID(ESP32→手机)

// LED指示灯引脚(可选,用于状态指示)
#define LED_PIN 2

// WiFi连接超时时间(秒)
#define WIFI_CONNECT_TIMEOUT 20

// ==================== 全局变量 ====================
// BLE相关对象指针
BLEServer *pServer = NULL; // BLE服务器指针
BLECharacteristic *pTxCharacteristic = NULL; // 发送特征指针(用于向手机发送数据)
bool deviceConnected = false; // 蓝牙连接状态标志
bool oldDeviceConnected = false; // 上一次蓝牙连接状态
bool wifiConfigReceived = false; // WiFi配置接收标志

// WiFi配置信息
String receivedSSID = “”; // 接收到的WiFi名称
String receivedPassword = “”; // 接收到的WiFi密码

// Preferences对象(用于NVS存储)
Preferences preferences; // NVS存储对象,保存配置到Flash

// WiFi连接状态
bool wifiConnected = false; // WiFi连接成功标志

// ==================== BLE服务器回调类 ====================
// 当蓝牙连接状态改变时会调用此类的方法
class MyServerCallbacks : public BLEServerCallbacks
{
// 当手机连接到ESP32时调用
void onConnect(BLEServer *pServer)
{
deviceConnected = true; // 设置连接标志为真
Serial.println(“[BLE] 客户端已连接”);

    // 点亮LED表示有设备连接
    digitalWrite(LED_PIN, HIGH);
}

// 当手机断开连接时调用
void onDisconnect(BLEServer *pServer)
{
    deviceConnected = false; // 设置连接标志为假
    Serial.println("[BLE] 客户端已断开连接");

    // 熄灭LED表示设备断开
    digitalWrite(LED_PIN, LOW);

    // 延迟后重新开始广播,允许其他设备连接
    delay(500);
    pServer->startAdvertising();
    Serial.println("[BLE] 重新开始广播...");
}

};

// ==================== BLE特征回调类 ====================
// 当接收到手机发送的数据时会调用此类的方法
class MyCallbacks : public BLECharacteristicCallbacks
{
// 当手机向ESP32写入数据时调用
void onWrite(BLECharacteristic *pCharacteristic)
{
// 获取接收到的数据
std::string rxValue = pCharacteristic->getValue();

    // 检查是否接收到数据
    if (rxValue.length() > 0) {
        Serial.println("\n[BLE] 收到数据:");
        Serial.print("[BLE] 长度: ");
        Serial.println(rxValue.length());

        // 打印接收到的原始数据(十六进制)
        Serial.print("[BLE] 数据(HEX): ");
        for (int i = 0; i < rxValue.length(); i++) {
            Serial.printf("%02X ", (uint8_t)rxValue[i]);
        }
        Serial.println();

        // 打印接收到的字符串数据
        Serial.print("[BLE] 数据(String): ");
        Serial.println(rxValue.c_str());

        // 解析WiFi配置数据
        // 数据格式:SSID|PASSWORD
        // 例如:MyWiFi|12345678
        parseWiFiConfig(String(rxValue.c_str()));
    }
}

};

// ==================== 初始化设置 ====================
void setup()
{
// 初始化串口通信,波特率115200
Serial.begin(115200);
delay(1000); // 等待串口稳定

// 打印启动信息
Serial.println();
Serial.println("===================================");
Serial.println("  ESP32 蓝牙配网系统 v1.0");
Serial.println("===================================");

// 初始化LED引脚
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW); // 初始熄灭

// LED闪烁3次表示启动
for (int i = 0; i < 3; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(200);
}

// 打印设备信息
Serial.println("\n--- 设备信息 ---");
Serial.printf("芯片型号: %s\n", ESP.getChipModel());
Serial.printf("MAC地址: %s\n", WiFi.macAddress().c_str());
Serial.printf("可用内存: %d bytes\n", ESP.getFreeHeap());
Serial.println("-------------------\n");

// 尝试从NVS加载已保存的WiFi配置
if (loadWiFiConfig()) {
    Serial.println("[配置] 发现已保存的WiFi配置");
    Serial.printf("[配置] SSID: %s\n", receivedSSID.c_str());

    // 尝试连接WiFi
    if (connectToWiFi(receivedSSID, receivedPassword)) {
        Serial.println("[WiFi] 使用已保存配置连接成功!");
        wifiConnected = true;

        // 连接成功后可以选择不启动BLE,节省功耗
        // 这里为了演示,仍然启动BLE
        Serial.println("[系统] WiFi已连接,但仍启动BLE以便重新配置");
    } else {
        Serial.println("[WiFi] 使用已保存配置连接失败");
        Serial.println("[系统] 将启动BLE进行配网");
    }
} else {
    Serial.println("[配置] 未找到已保存的WiFi配置");
    Serial.println("[系统] 启动BLE进行首次配网");
}

// 初始化BLE
initBLE();

Serial.println("\n[系统] 初始化完成,进入主循环");
Serial.println("[提示] 请使用手机BLE调试APP连接设备");
Serial.printf("[提示] 设备名称: %s\n", BLE_DEVICE_NAME);

}

// ==================== 主循环 ====================
void loop()
{
// ========== 处理蓝牙连接状态变化 ==========
// 检查蓝牙连接状态是否发生变化
if (deviceConnected != oldDeviceConnected) {
oldDeviceConnected = deviceConnected; // 更新状态

    if (deviceConnected) {
        // 新设备连接
        Serial.println("[状态] 蓝牙已连接");
    } else {
        // 设备断开连接
        Serial.println("[状态] 蓝牙已断开");
    }
}

// ========== 处理WiFi配置 ==========
// 如果接收到新的WiFi配置
if (wifiConfigReceived) {
    wifiConfigReceived = false; // 重置标志

    Serial.println("\n[WiFi] 准备连接WiFi...");
    Serial.printf("[WiFi] SSID: %s\n", receivedSSID.c_str());
    Serial.printf("[WiFi] 密码长度: %d\n", receivedPassword.length());

    // 向手机发送"开始连接"的状态
    sendBLEMessage("CONNECTING");

    // 尝试连接WiFi
    if (connectToWiFi(receivedSSID, receivedPassword)) {
        // 连接成功
        Serial.println("[WiFi] ✓ 连接成功!");
        wifiConnected = true;

        // 保存WiFi配置到NVS
        saveWiFiConfig(receivedSSID, receivedPassword);

        // 向手机发送成功消息
        String successMsg = "SUCCESS|" + WiFi.localIP().toString();
        sendBLEMessage(successMsg);

        // LED快速闪烁5次表示成功
        for (int i = 0; i < 5; i++) {
            digitalWrite(LED_PIN, HIGH);
            delay(100);
            digitalWrite(LED_PIN, LOW);
            delay(100);
        }

        // 可选:连接成功后关闭BLE以节省功耗
        // BLEDevice::deinit();
        // Serial.println("[BLE] 已关闭BLE功能");

    } else {
        // 连接失败
        Serial.println("[WiFi] ✗ 连接失败!");

        // 向手机发送失败消息
        sendBLEMessage("FAILED");

        // LED慢速闪烁3次表示失败
        for (int i = 0; i < 3; i++) {
            digitalWrite(LED_PIN, HIGH);
            delay(500);
            digitalWrite(LED_PIN, LOW);
            delay(500);
        }
    }
}

// ========== WiFi状态监控 ==========
// 如果之前连接过WiFi,检查连接状态
if (wifiConnected) {
    // 检查WiFi是否断开
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("[WiFi] 连接断开,尝试重连...");
        wifiConnected = false;

        // 尝试重新连接
        if (connectToWiFi(receivedSSID, receivedPassword)) {
            Serial.println("[WiFi] 重连成功");
            wifiConnected = true;
        } else {
            Serial.println("[WiFi] 重连失败");
        }
    }
}

// ========== 定期打印状态 ==========
static unsigned long lastStatusPrint = 0;
if (millis() - lastStatusPrint > 10000) { // 每10秒打印一次
    lastStatusPrint = millis();
    printStatus();
}

// 短暂延迟,避免CPU占用过高
delay(100);

}

// ==================== 初始化BLE函数 ====================
void initBLE()
{
Serial.println(“\n[BLE] 初始化蓝牙…”);

// 初始化BLE设备,设置设备名称
BLEDevice::init(BLE_DEVICE_NAME);
Serial.printf("[BLE] 设备名称: %s\n", BLE_DEVICE_NAME);

// 创建BLE服务器
pServer = BLEDevice::createServer();

// 设置服务器回调函数(处理连接/断开事件)
pServer->setCallbacks(new MyServerCallbacks());
Serial.println("[BLE] BLE服务器已创建");

// 创建BLE服务(使用预定义的UUID)
BLEService *pService = pServer->createService(SERVICE_UUID);
Serial.println("[BLE] BLE服务已创建");

// 创建BLE发送特征(ESP32 → 手机)
// 属性:READ(可读)+ NOTIFY(可通知)
pTxCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID_TX,
    BLECharacteristic::PROPERTY_READ |
        BLECharacteristic::PROPERTY_NOTIFY);

// 添加BLE2902描述符(用于启用通知功能)
pTxCharacteristic->addDescriptor(new BLE2902());
Serial.println("[BLE] TX特征已创建(用于发送数据到手机)");

// 创建BLE接收特征(手机 → ESP32)
// 属性:WRITE(可写)
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID_RX,
    BLECharacteristic::PROPERTY_WRITE);

// 设置接收特征的回调函数(当接收到数据时调用)
pRxCharacteristic->setCallbacks(new MyCallbacks());
Serial.println("[BLE] RX特征已创建(用于接收手机数据)");

// 启动服务
pService->start();
Serial.println("[BLE] 服务已启动");

// 获取广播对象
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

// 添加服务UUID到广播数据
pAdvertising->addServiceUUID(SERVICE_UUID);

// 设置广播参数
pAdvertising->setScanResponse(true); // 启用扫描响应

// 设置最小连接间隔(提高兼容性)
pAdvertising->setMinPreferred(0x06); // 0x06 * 1.25ms = 7.5ms

// 设置最大连接间隔
pAdvertising->setMaxPreferred(0x12); // 0x12 * 1.25ms = 22.5ms

// 开始广播
BLEDevice::startAdvertising();
Serial.println("[BLE] ✓ 广播已开始");
Serial.println("[BLE] 等待客户端连接...\n");

}

// ==================== 解析WiFi配置函数 ====================
void parseWiFiConfig(String data)
{
Serial.println(“\n[解析] 开始解析WiFi配置…”);

// 查找分隔符位置(使用|作为SSID和密码的分隔符)
int separatorIndex = data.indexOf('|');

// 检查数据格式是否正确
if (separatorIndex == -1) {
    Serial.println("[解析] ✗ 数据格式错误,未找到分隔符'|'");
    Serial.println("[解析] 正确格式: SSID|PASSWORD");
    sendBLEMessage("ERROR|格式错误");
    return;
}

// 提取SSID(分隔符之前的部分)
receivedSSID = data.substring(0, separatorIndex);
receivedSSID.trim(); // 去除首尾空格

// 提取密码(分隔符之后的部分)
receivedPassword = data.substring(separatorIndex + 1);
receivedPassword.trim(); // 去除首尾空格

// 验证SSID是否为空
if (receivedSSID.length() == 0) {
    Serial.println("[解析] ✗ SSID不能为空");
    sendBLEMessage("ERROR|SSID为空");
    return;
}

// 打印解析结果
Serial.println("[解析] ✓ 解析成功");
Serial.printf("[解析] SSID: %s\n", receivedSSID.c_str());
Serial.printf("[解析] 密码: %s\n", receivedPassword.c_str()); // 生产环境中应该隐藏密码

// 设置配置接收标志
wifiConfigReceived = true;

// 向手机发送确认消息
sendBLEMessage("CONFIG_OK");

}

// ==================== 连接WiFi函数 ====================
bool connectToWiFi(String ssid, String password)
{
Serial.println(“\n[WiFi] 开始连接…”);

// 断开当前连接(如果有)
WiFi.disconnect(true);
delay(1000);

// 设置WiFi为Station模式(客户端模式)
WiFi.mode(WIFI_STA);

// 开始连接WiFi
WiFi.begin(ssid.c_str(), password.c_str());
Serial.printf("[WiFi] 正在连接到: %s\n", ssid.c_str());

// 等待连接,设置超时时间
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < WIFI_CONNECT_TIMEOUT) {
    delay(500);
    Serial.print(".");

    // LED闪烁表示正在连接
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));

    timeout++;
}
Serial.println();

// 检查连接结果
if (WiFi.status() == WL_CONNECTED) {
    // 连接成功
    Serial.println("[WiFi] ✓ 连接成功!");
    Serial.printf("[WiFi] IP地址: %s\n", WiFi.localIP().toString().c_str());
    Serial.printf("[WiFi] 子网掩码: %s\n", WiFi.subnetMask().toString().c_str());
    Serial.printf("[WiFi] 网关: %s\n", WiFi.gatewayIP().toString().c_str());
    Serial.printf("[WiFi] DNS: %s\n", WiFi.dnsIP().toString().c_str());
    Serial.printf("[WiFi] 信号强度: %d dBm\n", WiFi.RSSI());

    // LED常亮表示连接成功
    digitalWrite(LED_PIN, HIGH);

    return true;
} else {
    // 连接失败
    Serial.println("[WiFi] ✗ 连接失败");
    Serial.print("[WiFi] 状态码: ");
    Serial.println(WiFi.status());

    // 熄灭LED
    digitalWrite(LED_PIN, LOW);

    return false;
}

}

// ==================== 保存WiFi配置函数 ====================
void saveWiFiConfig(String ssid, String password)
{
Serial.println(“\n[存储] 保存WiFi配置到NVS…”);

// 打开Preferences命名空间(wifi_config)
// 第二个参数false表示以读写模式打开
preferences.begin("wifi_config", false);

// 保存SSID
preferences.putString("ssid", ssid);

// 保存密码
preferences.putString("password", password);

// 关闭Preferences
preferences.end();

Serial.println("[存储] ✓ 配置已保存");

}

// ==================== 加载WiFi配置函数 ====================
bool loadWiFiConfig()
{
Serial.println(“\n[存储] 从NVS加载WiFi配置…”);

// 打开Preferences命名空间(wifi_config)
// 第二个参数true表示以只读模式打开
preferences.begin("wifi_config", true);

// 读取SSID
receivedSSID = preferences.getString("ssid", "");

// 读取密码
receivedPassword = preferences.getString("password", "");

// 关闭Preferences
preferences.end();

// 检查是否读取到有效配置
if (receivedSSID.length() > 0) {
    Serial.println("[存储] ✓ 配置加载成功");
    return true;
} else {
    Serial.println("[存储] 未找到已保存的配置");
    return false;
}

}

// ==================== 清除WiFi配置函数 ====================
void clearWiFiConfig()
{
Serial.println(“\n[存储] 清除WiFi配置…”);

// 打开Preferences命名空间
preferences.begin("wifi_config", false);

// 清除所有数据
preferences.clear();

// 关闭Preferences
preferences.end();

Serial.println("[存储] ✓ 配置已清除");

}

// ==================== 通过BLE发送消息函数 ====================
void sendBLEMessage(String message)
{
// 检查是否有设备连接
if (deviceConnected && pTxCharacteristic != NULL) {
Serial.printf(“[BLE] 发送消息: %s\n”, message.c_str());

    // 设置特征值
    pTxCharacteristic->setValue(message.c_str());

    // 发送通知给连接的设备
    pTxCharacteristic->notify();

    // 短暂延迟,确保数据发送完成
    delay(100);
} else {
    Serial.println("[BLE] 无法发送消息:没有设备连接");
}

}

// ==================== 打印系统状态函数 ====================
void printStatus()
{
Serial.println(“\n========== 系统状态 ==========”);

// 蓝牙状态
Serial.print("BLE状态: ");
Serial.println(deviceConnected ? "已连接" : "未连接");

// WiFi状态
Serial.print("WiFi状态: ");
if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("已连接 (%s)\n", WiFi.localIP().toString().c_str());
    Serial.printf("信号强度: %d dBm\n", WiFi.RSSI());
} else {
    Serial.println("未连接");
}

// 内存状态
Serial.printf("可用内存: %d bytes\n", ESP.getFreeHeap());

// 运行时间
Serial.printf("运行时间: %lu 秒\n", millis() / 1000);

Serial.println("==============================\n");

}

/*

  • ==================== 使用说明 ====================
    1. 硬件准备:
    • ESP32开发板
    • LED(可选,连接到GPIO2)
    • USB数据线
    1. 软件准备:
    • Arduino IDE安装ESP32支持
    • 安装手机BLE调试APP:
  •  * iOS: LightBlue, nRF Connect
    
  •  * Android: nRF Connect, BLE Scanner
    
    1. 上传代码:
    • 连接ESP32到电脑
    • 选择正确的开发板和端口
    • 上传代码
    1. 使用流程:
  • a) ESP32上电后会启动BLE广播
  • b) 打开手机BLE调试APP
  • c) 搜索并连接到"ESP32_Config"
  • d) 找到RX特征(UUID: beb5483e-36e1-4688-b7f5-ea07361b26a8)
  • e) 写入WiFi配置,格式:SSID|PASSWORD
  •   例如:MyWiFi|12345678
    
  • f) ESP32会自动连接WiFi并返回结果
    1. 数据格式:
  • 发送到ESP32(手机→ESP32):
    • “SSID|PASSWORD” - WiFi配置
  • ESP32返回(ESP32→手机):
    • “CONFIG_OK” - 配置接收成功
    • “CONNECTING” - 正在连接WiFi
    • “SUCCESS|IP” - 连接成功+IP地址
    • “FAILED” - 连接失败
    • “ERROR|msg” - 错误消息
    1. 串口监视器查看日志:
    • 波特率设置为115200
    • 可以看到详细的运行日志
    1. 测试命令:
  • 使用nRF Connect APP:
    • 连接设备
    • 选择RX特征
    • 写入: “TestWiFi|password123”
    • 订阅TX特征查看返回消息
    1. 清除已保存的WiFi配置:
  • 在setup()中添加一行:
  • clearWiFiConfig();
    1. 注意事项:
    • SSID和密码不能包含’|'字符
    • 密码可以为空(开放网络)
    • WiFi配置保存在NVS中,断电不丢失
    • 连接成功后BLE仍然运行(可选择关闭)
  • ==================== 高级功能扩展 ====================
  • 可以添加的功能:
    1. WiFi扫描:返回周围的WiFi列表
    1. 配置加密:对传输的密码进行加密
    1. 设备绑定:只允许特定设备配置
    1. 多WiFi配置:保存多个WiFi信息
    1. 远程控制:通过BLE控制GPIO
    1. 状态上报:定时上报温度、湿度等数据
    1. 固件升级:通过BLE进行OTA升级
    1. Web配网:WiFi连接后提供Web配置界面
  • ==================== 故障排除 ====================
  • 问题1: 手机搜索不到设备
  • 解决:
    • 确保ESP32已上电并运行
    • 检查手机蓝牙是否开启
    • 尝试重启ESP32
    • 查看串口输出确认BLE已启动
  • 问题2: 无法连接WiFi
  • 解决:
    • 确认SSID和密码正确
    • 确保WiFi是2.4GHz(ESP32不支持5GHz)
    • 检查WiFi信号强度
    • 查看串口输出的错误信息
  • 问题3: 配置丢失
  • 解决:
    • 检查NVS分区是否正确
    • 确保调用了saveWiFiConfig()
    • Flash可能已损坏,尝试重新烧录
  • 问题4: BLE连接不稳定
  • 解决:
    • 减小手机与ESP32的距离
    • 避免WiFi和BLE同时高负载使用
    • 调整BLE连接参数
      */
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐