toy-hardware/BluFi蓝牙配网小程序开发需求说明书.md

75 KiB
Raw Blame History

BluFi蓝牙配网小程序开发需求说明书

1. 项目概述

1.1 项目背景

本项目需要开发一个微信小程序用于与ESP32设备进行BluFi蓝牙配网。该小程序需要完全兼容ESP官方的espblufi应用程序功能能够成功进行WiFi配网并返回配网成功报告。

1.2 项目目标

  • 开发微信小程序实现BluFi蓝牙配网功能
  • 与ESP32设备建立稳定的蓝牙连接
  • 完成WiFi网络配置和连接验证
  • 提供用户友好的配网界面和状态反馈
  • 确保与官方espblufi应用程序的完全兼容性

1.3 设备端配置信息

基于项目代码分析,设备端配置如下:

// 设备端配置参数来自bluetooth_provisioning_config.h
const DEVICE_CONFIG = {
    // 设备名称配置
    DEFAULT_DEVICE_NAME: "Airhub_Ble",
    MAX_DEVICE_NAME_LEN: 32,
    
    // 超时配置
    ADV_TIMEOUT_MS: 0,  // 永不超时
    CLIENT_TIMEOUT_MS: 5 * 60 * 1000,  // 5分钟
    WIFI_TIMEOUT_MS: 100 * 1000,  // 100秒
    WIFI_MAX_RETRY: 5,
    
    // 安全配置
    SECURITY_ENABLED: false,
    REQUIRE_PAIRING: false,
    PSK: "Airhub2025",
    
    // 功能开关
    ENABLE_WIFI_SCAN: true,
    AUTO_REPORT_STATUS: true,
    AUTO_STOP_ON_SUCCESS: true,
    AUTO_STOP_DELAY_MS: 5000
};

2. 技术架构

2.1 系统架构图

微信小程序 <---> 蓝牙BLE <---> ESP32设备 <---> WiFi网络
    ↓              ↓              ↓
用户界面        BluFi协议      WiFi连接
状态管理        数据加密       网络验证

2.2 设备端架构

基于bluetooth_provisioning.hbluetooth_provisioning.cc分析:

// 设备端状态枚举对应C++代码)
const BluetoothProvisioningState = {
    IDLE: 0,           // 空闲状态,未启动配网
    INITIALIZING: 1,   // 初始化中正在初始化蓝牙和BluFi服务
    ADVERTISING: 2,    // 广播中,等待手机客户端连接
    CONNECTED: 3,      // 已连接,手机客户端已连接到设备
    PROVISIONING: 4,   // 配网中正在接收和处理WiFi凭据
    SUCCESS: 5,        // 配网成功WiFi连接建立成功
    FAILED: 6,         // 配网失败WiFi连接失败或其他错误
    STOPPED: 7         // 已停止,配网服务已停止
};

// 设备端事件类型对应C++代码)
const BluetoothProvisioningEvent = {
    STATE_CHANGED: 0,      // 状态改变事件,配网状态发生变化
    WIFI_CREDENTIALS: 1,   // 收到WiFi凭据事件从手机接收到WiFi信息
    WIFI_CONNECTED: 2,     // WiFi连接成功事件设备成功连接到WiFi网络
    WIFI_FAILED: 3,        // WiFi连接失败事件设备连接WiFi失败
    CLIENT_CONNECTED: 4,   // 客户端连接事件,手机客户端连接到设备
    CLIENT_DISCONNECTED: 5 // 客户端断开事件,手机客户端断开连接
};

2.3 技术栈

  • 前端: 微信小程序框架
  • 通讯协议: BluFi (基于BLE)
  • 设备端: ESP-IDF BluFi组件
  • 加密: 可选AES加密当前项目未启用

3. BluFi协议详解

3.1 协议概述

BluFi是乐鑫开发的基于蓝牙通道的WiFi网络配置协议通过安全的蓝牙连接传输WiFi凭据。

3.2 GATT服务和特征值

3.2.1 BluFi服务UUIDESP32标准

// BluFi GATT服务和特征值UUID
const BLUFI_SERVICE_UUID = "0000FFFF-0000-1000-8000-00805F9B34FB";
const BLUFI_CHAR_P2E_UUID = "0000FF01-0000-1000-8000-00805F9B34FB"; // 手机到设备(写)
const BLUFI_CHAR_E2P_UUID = "0000FF02-0000-1000-8000-00805F9B34FB"; // 设备到手机(通知)

3.2.2 设备发现和命名规则

// 设备名称识别(基于项目配置)
function isValidBluFiDevice(device) {
    // 检查设备名称是否符合项目规范
    const validNames = [
        "Airhub_Ble",     // 默认名称
        "XiaoZhi-AI"      // 备用名称
    ];
    
    return device.name && (
        validNames.includes(device.name) ||
        device.name.startsWith("Airhub-") ||
        device.name.startsWith("XiaoZhi-")
    );
}

3.3 数据包格式

3.3.1 BluFi数据包结构

// BluFi数据包格式基于ESP-IDF实现
class BluFiPacket {
    constructor() {
        this.type = 0x00;        // 数据包类型 (1字节)
        this.fc = 0x00;          // 帧控制 (1字节)
        this.sequence = 0x0000;  // 序列号 (2字节)
        this.length = 0x0000;    // 数据长度 (2字节)
        this.data = [];          // 数据内容 (变长)
        this.checksum = 0x0000;  // 校验和 (2字节)
    }
    
    // 构建数据包
    build(type, subtype, data = null) {
        const dataLength = data ? data.length : 0;
        const totalLength = 8 + dataLength;
        const buffer = new ArrayBuffer(totalLength);
        const view = new DataView(buffer);
        
        // 设置包头
        view.setUint8(0, type);                    // 类型
        view.setUint8(1, subtype);                 // 子类型  
        view.setUint16(2, this.sequence, true);    // 序列号(小端)
        view.setUint16(4, dataLength, true);       // 数据长度(小端)
        
        // 设置数据
        if (data && dataLength > 0) {
            const dataView = new Uint8Array(buffer, 6);
            dataView.set(new Uint8Array(data));
        }
        
        // 计算并设置校验和
        const checksum = this.calculateChecksum(buffer, totalLength - 2);
        view.setUint16(totalLength - 2, checksum, true);
        
        this.sequence++;
        return buffer;
    }
    
    // 计算校验和
    calculateChecksum(buffer, length) {
        let checksum = 0;
        const view = new Uint8Array(buffer);
        
        for (let i = 0; i < length; i++) {
            checksum += view[i];
        }
        
        return checksum & 0xFFFF;
    }
    
    // 解析数据包
    parse(buffer) {
        const view = new DataView(buffer);
        
        return {
            type: view.getUint8(0),
            subtype: view.getUint8(1),
            sequence: view.getUint16(2, true),
            length: view.getUint16(4, true),
            data: buffer.slice(6, 6 + view.getUint16(4, true)),
            checksum: view.getUint16(buffer.byteLength - 2, true)
        };
    }
}

3.3.2 数据包类型定义

// 控制包类型基于ESP-IDF BluFi实现
const BLUFI_TYPE_CTRL = {
    ACK: 0x00,                   // 确认包
    SET_SEC_MODE: 0x01,          // 设置安全模式
    SET_WIFI_OPMODE: 0x02,       // 设置WiFi操作模式
    CONNECT_WIFI: 0x03,          // 连接WiFi
    DISCONNECT_WIFI: 0x04,       // 断开WiFi
    GET_WIFI_STATUS: 0x05,       // 获取WiFi状态
    DEAUTHENTICATE: 0x06,        // 取消认证
    GET_VERSION: 0x07,           // 获取版本
    CLOSE_CONNECTION: 0x08,      // 关闭连接
    GET_WIFI_LIST: 0x09          // 获取WiFi列表
};

// 数据包类型基于ESP-IDF BluFi实现
const BLUFI_TYPE_DATA = {
    NEG: 0x00,                  // 协商数据
    STA_BSSID: 0x01,            // STA BSSID
    STA_SSID: 0x02,             // STA SSID
    STA_PASSWD: 0x03,           // STA 密码
    SOFTAP_SSID: 0x04,          // SoftAP SSID
    SOFTAP_PASSWD: 0x05,        // SoftAP 密码
    SOFTAP_MAX_CONN: 0x06,      // SoftAP最大连接数
    SOFTAP_AUTH_MODE: 0x07,     // SoftAP认证模式
    SOFTAP_CHANNEL: 0x08,       // SoftAP信道
    USERNAME: 0x09,             // 用户名
    CA_CERT: 0x0A,              // CA证书
    CLIENT_CERT: 0x0B,          // 客户端证书
    SERVER_CERT: 0x0C,          // 服务器证书
    CLIENT_PRIV_KEY: 0x0D,      // 客户端私钥
    SERVER_PRIV_KEY: 0x0E,      // 服务器私钥
    WIFI_REP: 0x0F,             // WiFi报告
    WIFI_LIST: 0x10             // WiFi列表
};

// 包类型标识
const BLUFI_FC_ENC = 0x01;      // 加密标志
const BLUFI_FC_CHECK = 0x02;    // 校验标志
const BLUFI_FC_DATA_DIR = 0x04; // 数据方向标志
const BLUFI_FC_REQUIRE_ACK = 0x08; // 需要确认标志

4. 配网流程详细实现

4.1 第一阶段:蓝牙初始化和设备扫描

4.1.1 小程序端实现

// 蓝牙配网管理类
class BluFiProvisioning {
    constructor() {
        this.deviceId = null;
        this.serviceId = null;
        this.writeCharacteristicId = null;
        this.notifyCharacteristicId = null;
        this.sequenceNumber = 0;
        this.isConnected = false;
        this.provisioningState = 'idle';
        this.packet = new BluFiPacket();
    }
    
    // 初始化蓝牙适配器
    async initBluetooth() {
        try {
            console.log('初始化蓝牙适配器...');
            
            await new Promise((resolve, reject) => {
                wx.openBluetoothAdapter({
                    success: (res) => {
                        console.log('蓝牙适配器初始化成功:', res);
                        resolve(res);
                    },
                    fail: (err) => {
                        console.error('蓝牙适配器初始化失败:', err);
                        reject(new Error(`蓝牙初始化失败: ${err.errMsg}`));
                    }
                });
            });
            
            // 检查蓝牙状态
            await this.checkBluetoothState();
            
            return true;
        } catch (error) {
            console.error('蓝牙初始化异常:', error);
            throw error;
        }
    }
    
    // 检查蓝牙状态
    async checkBluetoothState() {
        return new Promise((resolve, reject) => {
            wx.getBluetoothAdapterState({
                success: (res) => {
                    console.log('蓝牙状态:', res);
                    if (!res.available) {
                        reject(new Error('蓝牙不可用'));
                    } else if (!res.discovering) {
                        console.log('蓝牙可用,准备扫描设备');
                        resolve(res);
                    } else {
                        resolve(res);
                    }
                },
                fail: (err) => {
                    reject(new Error(`获取蓝牙状态失败: ${err.errMsg}`));
                }
            });
        });
    }
    
    // 扫描BluFi设备
    async startScan(onDeviceFound) {
        try {
            console.log('开始扫描BluFi设备...');
            
            // 监听设备发现事件
            wx.onBluetoothDeviceFound((res) => {
                res.devices.forEach(device => {
                    console.log('发现设备:', device);
                    
                    // 检查是否为BluFi设备
                    if (this.isValidBluFiDevice(device)) {
                        console.log('发现BluFi设备:', device.name, device.deviceId);
                        onDeviceFound && onDeviceFound(device);
                    }
                });
            });
            
            // 开始扫描
            await new Promise((resolve, reject) => {
                wx.startBluetoothDevicesDiscovery({
                    services: [BLUFI_SERVICE_UUID],
                    allowDuplicatesKey: false,
                    interval: 0,
                    success: (res) => {
                        console.log('开始扫描设备成功:', res);
                        resolve(res);
                    },
                    fail: (err) => {
                        console.error('开始扫描设备失败:', err);
                        reject(new Error(`扫描失败: ${err.errMsg}`));
                    }
                });
            });
            
            return true;
        } catch (error) {
            console.error('扫描设备异常:', error);
            throw error;
        }
    }
    
    // 停止扫描
    async stopScan() {
        return new Promise((resolve) => {
            wx.stopBluetoothDevicesDiscovery({
                success: (res) => {
                    console.log('停止扫描成功:', res);
                    resolve(res);
                },
                fail: (err) => {
                    console.warn('停止扫描失败:', err);
                    resolve(); // 即使失败也继续
                }
            });
        });
    }
    
    // 验证是否为有效的BluFi设备
    isValidBluFiDevice(device) {
        if (!device.name) return false;
        
        const validNames = [
            "Airhub_Ble",     // 项目默认名称
            "XiaoZhi-AI"      // 备用名称
        ];
        
        return validNames.includes(device.name) ||
               device.name.startsWith("Airhub-") ||
               device.name.startsWith("XiaoZhi-");
    }
}

4.1.2 设备扫描页面实现

<!-- pages/scan/scan.wxml -->
<view class="scan-container">
    <view class="scan-header">
        <text class="title">扫描BluFi设备</text>
        <text class="subtitle">请确保设备处于配网模式</text>
    </view>
    
    <view class="scan-status">
        <view wx:if="{{scanning}}" class="scanning">
            <view class="loading-icon"></view>
            <text>正在扫描设备...</text>
        </view>
        <button wx:else class="scan-btn" bindtap="startScan">开始扫描</button>
    </view>
    
    <view class="device-list">
        <view class="device-item" wx:for="{{devices}}" wx:key="deviceId" bindtap="selectDevice" data-device="{{item}}">
            <view class="device-info">
                <view class="device-name">{{item.name}}</view>
                <view class="device-id">{{item.deviceId}}</view>
                <view class="device-signal">信号强度: {{item.RSSI}}dBm</view>
            </view>
            <view class="device-action">
                <text class="connect-btn">连接</text>
            </view>
        </view>
    </view>
    
    <view wx:if="{{devices.length === 0 && !scanning}}" class="empty-state">
        <text>未发现设备,请检查设备是否开启配网模式</text>
    </view>
</view>
// pages/scan/scan.js
Page({
    data: {
        scanning: false,
        devices: []
    },
    
    onLoad() {
        this.blufi = new BluFiProvisioning();
        this.initBluetooth();
    },
    
    async initBluetooth() {
        try {
            await this.blufi.initBluetooth();
            console.log('蓝牙初始化完成');
        } catch (error) {
            wx.showToast({
                title: '蓝牙初始化失败',
                icon: 'error'
            });
            console.error('蓝牙初始化失败:', error);
        }
    },
    
    async startScan() {
        if (this.data.scanning) return;
        
        this.setData({ 
            scanning: true,
            devices: []
        });
        
        try {
            await this.blufi.startScan((device) => {
                // 检查设备是否已存在
                const exists = this.data.devices.find(d => d.deviceId === device.deviceId);
                if (!exists) {
                    this.setData({
                        devices: [...this.data.devices, device]
                    });
                }
            });
            
            // 30秒后自动停止扫描
            setTimeout(() => {
                this.stopScan();
            }, 30000);
            
        } catch (error) {
            this.setData({ scanning: false });
            wx.showToast({
                title: '扫描失败',
                icon: 'error'
            });
            console.error('扫描失败:', error);
        }
    },
    
    async stopScan() {
        if (!this.data.scanning) return;
        
        try {
            await this.blufi.stopScan();
            this.setData({ scanning: false });
        } catch (error) {
            console.error('停止扫描失败:', error);
        }
    },
    
    selectDevice(e) {
        const device = e.currentTarget.dataset.device;
        console.log('选择设备:', device);
        
        // 停止扫描
        this.stopScan();
        
        // 跳转到连接页面
        wx.navigateTo({
            url: `/pages/connect/connect?deviceId=${device.deviceId}&deviceName=${device.name}`
        });
    },
    
    onUnload() {
        this.stopScan();
    }
});

4.2 第二阶段设备连接和GATT服务发现

4.2.1 连接设备实现

// 在BluFiProvisioning类中添加连接方法
class BluFiProvisioning {
    // ... 前面的代码 ...
    
    // 连接设备
    async connectDevice(deviceId) {
        try {
            console.log('连接设备:', deviceId);
            this.deviceId = deviceId;
            
            // 建立BLE连接
            await new Promise((resolve, reject) => {
                wx.createBLEConnection({
                    deviceId: deviceId,
                    success: (res) => {
                        console.log('设备连接成功:', res);
                        this.isConnected = true;
                        resolve(res);
                    },
                    fail: (err) => {
                        console.error('设备连接失败:', err);
                        reject(new Error(`连接失败: ${err.errMsg}`));
                    }
                });
            });
            
            // 发现服务
            await this.discoverServices();
            
            // 发现特征值
            await this.discoverCharacteristics();
            
            // 启用通知
            await this.enableNotifications();
            
            console.log('设备连接和初始化完成');
            return true;
            
        } catch (error) {
            console.error('连接设备异常:', error);
            this.isConnected = false;
            throw error;
        }
    }
    
    // 自动发现GATT服务
    async discoverServices() {
        return new Promise((resolve, reject) => {
            wx.getBLEDeviceServices({
                deviceId: this.deviceId,
                success: (res) => {
                    console.log('发现服务:', res.services);
                    
                    // 查找BluFi服务
                    const blufiService = res.services.find(service => 
                        service.uuid.toUpperCase() === BLUFI_SERVICE_UUID.toUpperCase()
                    );
                    
                    if (blufiService) {
                        this.serviceId = blufiService.uuid;
                        console.log('找到BluFi服务:', this.serviceId);
                        resolve(blufiService);
                    } else {
                        reject(new Error('未找到BluFi服务'));
                    }
                },
                fail: (err) => {
                    console.error('发现服务失败:', err);
                    reject(new Error(`发现服务失败: ${err.errMsg}`));
                }
            });
        });
    }
    
    // 自动发现特征值
    async discoverCharacteristics() {
        return new Promise((resolve, reject) => {
            wx.getBLEDeviceCharacteristics({
                deviceId: this.deviceId,
                serviceId: this.serviceId,
                success: (res) => {
                    console.log('发现特征值:', res.characteristics);
                    
                    // 查找写特征值(手机到设备)
                    const writeChar = res.characteristics.find(char => 
                        char.uuid.toUpperCase() === BLUFI_CHAR_P2E_UUID.toUpperCase()
                    );
                    
                    // 查找通知特征值(设备到手机)
                    const notifyChar = res.characteristics.find(char => 
                        char.uuid.toUpperCase() === BLUFI_CHAR_E2P_UUID.toUpperCase()
                    );
                    
                    if (writeChar && notifyChar) {
                        this.writeCharacteristicId = writeChar.uuid;
                        this.notifyCharacteristicId = notifyChar.uuid;
                        console.log('找到BluFi特征值:');
                        console.log('写特征值:', this.writeCharacteristicId);
                        console.log('通知特征值:', this.notifyCharacteristicId);
                        resolve({ writeChar, notifyChar });
                    } else {
                        reject(new Error('未找到BluFi特征值'));
                    }
                },
                fail: (err) => {
                    console.error('发现特征值失败:', err);
                    reject(new Error(`发现特征值失败: ${err.errMsg}`));
                }
            });
        });
    }
    
    // 启用通知
    async enableNotifications() {
        return new Promise((resolve, reject) => {
            // 监听特征值变化
            wx.onBLECharacteristicValueChange((res) => {
                console.log('收到设备数据:', res);
                this.handleDeviceData(res.value);
            });
            
            // 启用通知
            wx.notifyBLECharacteristicValueChange({
                deviceId: this.deviceId,
                serviceId: this.serviceId,
                characteristicId: this.notifyCharacteristicId,
                state: true,
                success: (res) => {
                    console.log('启用通知成功:', res);
                    resolve(res);
                },
                fail: (err) => {
                    console.error('启用通知失败:', err);
                    reject(new Error(`启用通知失败: ${err.errMsg}`));
                }
            });
        });
    }
    
    // 处理设备数据
    handleDeviceData(buffer) {
        try {
            const packet = this.packet.parse(buffer);
            console.log('解析数据包:', packet);
            
            // 根据数据包类型处理
            switch (packet.type) {
                case 0x00: // 控制包
                    this.handleControlPacket(packet);
                    break;
                case 0x01: // 数据包
                    this.handleDataPacket(packet);
                    break;
                default:
                    console.warn('未知数据包类型:', packet.type);
            }
        } catch (error) {
            console.error('处理设备数据失败:', error);
        }
    }
    
    // 处理控制包
    handleControlPacket(packet) {
        switch (packet.subtype) {
            case BLUFI_TYPE_CTRL.ACK:
                console.log('收到确认包');
                break;
            case BLUFI_TYPE_CTRL.GET_WIFI_STATUS:
                console.log('设备请求WiFi状态');
                break;
            default:
                console.log('收到控制包:', packet.subtype);
        }
    }
    
    // 处理数据包
    handleDataPacket(packet) {
        switch (packet.subtype) {
            case BLUFI_TYPE_DATA.WIFI_REP:
                this.handleWiFiReport(packet.data);
                break;
            case BLUFI_TYPE_DATA.WIFI_LIST:
                this.handleWiFiList(packet.data);
                break;
            default:
                console.log('收到数据包:', packet.subtype);
        }
    }
    
    // 处理WiFi连接报告
    handleWiFiReport(data) {
        if (data.byteLength >= 2) {
            const view = new DataView(data);
            const status = view.getUint8(0);
            const reason = view.getUint8(1);
            
            console.log('WiFi连接报告 - 状态:', status, '原因:', reason);
            
            if (status === 0) {
                // 连接成功
                this.provisioningState = 'success';
                this.onProvisioningSuccess && this.onProvisioningSuccess();
            } else {
                // 连接失败
                this.provisioningState = 'failed';
                this.onProvisioningFailed && this.onProvisioningFailed(reason);
            }
        }
    }
    
    // 断开连接
    async disconnect() {
        if (!this.isConnected || !this.deviceId) return;
        
        try {
            await new Promise((resolve) => {
                wx.closeBLEConnection({
                    deviceId: this.deviceId,
                    success: (res) => {
                        console.log('断开连接成功:', res);
                        resolve(res);
                    },
                    fail: (err) => {
                        console.warn('断开连接失败:', err);
                        resolve(); // 即使失败也继续
                    }
                });
            });
            
            this.isConnected = false;
            this.deviceId = null;
            this.serviceId = null;
            this.writeCharacteristicId = null;
            this.notifyCharacteristicId = null;
            
        } catch (error) {
            console.error('断开连接异常:', error);
        }
    }
}

4.2.2 连接页面实现

<!-- pages/connect/connect.wxml -->
<view class="connect-container">
    <view class="connect-header">
        <text class="title">连接设备</text>
        <view class="device-info">
            <text class="device-name">{{deviceName}}</text>
            <text class="device-id">{{deviceId}}</text>
        </view>
    </view>
    
    <view class="connect-status">
        <view wx:if="{{connecting}}" class="connecting">
            <view class="loading-icon"></view>
            <text>正在连接设备...</text>
        </view>
        
        <view wx:elif="{{connected}}" class="connected">
            <view class="success-icon"></view>
            <text>设备连接成功</text>
            <button class="next-btn" bindtap="goToConfig">开始配网</button>
        </view>
        
        <view wx:else class="disconnected">
            <text>设备未连接</text>
            <button class="retry-btn" bindtap="connectDevice">重新连接</button>
        </view>
    </view>
    
    <view class="connect-steps">
        <view class="step {{step >= 1 ? 'active' : ''}}">
            <view class="step-number">1</view>
            <text>建立BLE连接</text>
        </view>
        <view class="step {{step >= 2 ? 'active' : ''}}">
            <view class="step-number">2</view>
            <text>发现GATT服务</text>
        </view>
        <view class="step {{step >= 3 ? 'active' : ''}}">
            <view class="step-number">3</view>
            <text>初始化特征值</text>
        </view>
        <view class="step {{step >= 4 ? 'active' : ''}}">
            <view class="step-number">4</view>
            <text>启用数据通知</text>
        </view>
    </view>
</view>
// pages/connect/connect.js
Page({
    data: {
        deviceId: '',
        deviceName: '',
        connecting: false,
        connected: false,
        step: 0
    },
    
    onLoad(options) {
        this.setData({
            deviceId: options.deviceId,
            deviceName: options.deviceName
        });
        
        this.blufi = new BluFiProvisioning();
        this.connectDevice();
    },
    
    async connectDevice() {
        if (this.data.connecting) return;
        
        this.setData({ 
            connecting: true,
            connected: false,
            step: 0
        });
        
        try {
            // 步骤1建立BLE连接
            this.setData({ step: 1 });
            await new Promise(resolve => setTimeout(resolve, 500)); // 显示进度
            
            // 步骤2发现GATT服务
            this.setData({ step: 2 });
            await new Promise(resolve => setTimeout(resolve, 500));
            
            // 步骤3初始化特征值
            this.setData({ step: 3 });
            await new Promise(resolve => setTimeout(resolve, 500));
            
            // 步骤4启用数据通知
            this.setData({ step: 4 });
            
            // 执行实际连接
            await this.blufi.connectDevice(this.data.deviceId);
            
            this.setData({ 
                connecting: false,
                connected: true
            });
            
            wx.showToast({
                title: '连接成功',
                icon: 'success'
            });
            
        } catch (error) {
            this.setData({ 
                connecting: false,
                connected: false,
                step: 0
            });
            
            wx.showToast({
                title: '连接失败',
                icon: 'error'
            });
            
            console.error('连接设备失败:', error);
        }
    },
    
    goToConfig() {
        if (!this.data.connected) return;
        
        // 跳转到WiFi配置页面
        wx.navigateTo({
            url: '/pages/config/config'
        });
    },
    
    onUnload() {
        // 页面卸载时断开连接
        if (this.blufi) {
            this.blufi.disconnect();
        }
    }
});

4.3 第三阶段WiFi扫描和网络选择

4.3.1 WiFi扫描实现

// 在BluFiProvisioning类中添加WiFi扫描方法
class BluFiProvisioning {
    // ... 前面的代码 ...
    
    // 请求WiFi扫描
    async requestWiFiScan() {
        try {
            console.log('请求设备扫描WiFi...');
            
            // 构建获取WiFi列表的控制包
            const packet = this.packet.build(0x00, BLUFI_TYPE_CTRL.GET_WIFI_LIST);
            
            // 发送数据包
            await this.sendData(packet);
            
            console.log('WiFi扫描请求已发送');
            return true;
            
        } catch (error) {
            console.error('请求WiFi扫描失败:', error);
            throw error;
        }
    }
    
    // 发送数据到设备
    async sendData(buffer) {
        if (!this.isConnected || !this.deviceId || !this.writeCharacteristicId) {
            throw new Error('设备未连接或特征值未初始化');
        }
        
        return new Promise((resolve, reject) => {
            wx.writeBLECharacteristicValue({
                deviceId: this.deviceId,
                serviceId: this.serviceId,
                characteristicId: this.writeCharacteristicId,
                value: buffer,
                success: (res) => {
                    console.log('数据发送成功:', res);
                    resolve(res);
                },
                fail: (err) => {
                    console.error('数据发送失败:', err);
                    reject(new Error(`发送失败: ${err.errMsg}`));
                }
            });
        });
    }
    
    // 处理WiFi列表
    handleWiFiList(data) {
        try {
            console.log('收到WiFi列表数据:', data);
            
            // 解析WiFi列表数据
            const wifiList = this.parseWiFiList(data);
            console.log('解析的WiFi列表:', wifiList);
            
            // 触发回调
            this.onWiFiListReceived && this.onWiFiListReceived(wifiList);
            
        } catch (error) {
            console.error('处理WiFi列表失败:', error);
        }
    }
    
    // 解析WiFi列表数据
    parseWiFiList(data) {
        const wifiList = [];
        const view = new DataView(data);
        let offset = 0;
        
        try {
            while (offset < data.byteLength) {
                // 读取SSID长度
                if (offset >= data.byteLength) break;
                const ssidLength = view.getUint8(offset);
                offset += 1;
                
                if (ssidLength === 0 || offset + ssidLength > data.byteLength) break;
                
                // 读取SSID
                const ssidBytes = new Uint8Array(data, offset, ssidLength);
                const ssid = new TextDecoder('utf-8').decode(ssidBytes);
                offset += ssidLength;
                
                // 读取RSSI信号强度
                if (offset >= data.byteLength) break;
                const rssi = view.getInt8(offset);
                offset += 1;
                
                // 读取认证模式
                if (offset >= data.byteLength) break;
                const authMode = view.getUint8(offset);
                offset += 1;
                
                wifiList.push({
                    ssid: ssid,
                    rssi: rssi,
                    authMode: authMode,
                    security: this.getSecurityType(authMode)
                });
            }
        } catch (error) {
            console.error('解析WiFi列表数据异常:', error);
        }
        
        return wifiList;
    }
    
    // 获取安全类型描述
    getSecurityType(authMode) {
        const securityTypes = {
            0: 'OPEN',
            1: 'WEP',
            2: 'WPA_PSK',
            3: 'WPA2_PSK',
            4: 'WPA_WPA2_PSK',
            5: 'WPA2_ENTERPRISE',
            6: 'WPA3_PSK',
            7: 'WPA2_WPA3_PSK'
        };
        
        return securityTypes[authMode] || 'UNKNOWN';
    }
}

4.3.2 WiFi配置页面实现

<!-- pages/config/config.wxml -->
<view class="config-container">
    <view class="config-header">
        <text class="title">WiFi配置</text>
        <text class="subtitle">选择要连接的WiFi网络</text>
    </view>
    
    <!-- WiFi扫描区域 -->
    <view class="scan-section">
        <view class="scan-header">
            <text>可用网络</text>
            <button class="scan-btn" bindtap="scanWiFi" disabled="{{scanning}}">
                {{scanning ? '扫描中...' : '刷新'}}
            </button>
        </view>
        
        <view class="wifi-list">
            <view class="wifi-item" wx:for="{{wifiList}}" wx:key="ssid" bindtap="selectWiFi" data-wifi="{{item}}">
                <view class="wifi-info">
                    <view class="wifi-name">{{item.ssid}}</view>
                    <view class="wifi-details">
                        <text class="wifi-signal">{{item.rssi}}dBm</text>
                        <text class="wifi-security">{{item.security}}</text>
                    </view>
                </view>
                <view class="wifi-icon">
                    <text class="signal-icon">📶</text>
                </view>
            </view>
        </view>
        
        <view wx:if="{{wifiList.length === 0 && !scanning}}" class="empty-wifi">
            <text>未发现WiFi网络请点击刷新重新扫描</text>
        </view>
    </view>
    
    <!-- 手动输入区域 -->
    <view class="manual-section">
        <view class="section-title">或手动输入WiFi信息</view>
        
        <form bindsubmit="submitWiFiConfig">
            <view class="form-group">
                <label>WiFi名称 (SSID)</label>
                <input name="ssid" placeholder="请输入WiFi名称" value="{{selectedSSID}}" bindinput="onSSIDInput"/>
            </view>
            
            <view class="form-group">
                <label>WiFi密码</label>
                <input name="password" type="password" placeholder="请输入WiFi密码" value="{{wifiPassword}}" bindinput="onPasswordInput"/>
            </view>
            
            <view class="form-actions">
                <button form-type="submit" class="config-btn" disabled="{{!canSubmit}}">
                    开始配网
                </button>
            </view>
        </form>
    </view>
</view>
// pages/config/config.js
Page({
    data: {
        scanning: false,
        wifiList: [],
        selectedSSID: '',
        wifiPassword: '',
        canSubmit: false
    },
    
    onLoad() {
        // 获取全局的BluFi实例
        const app = getApp();
        this.blufi = app.globalData.blufi;
        
        if (!this.blufi || !this.blufi.isConnected) {
            wx.showToast({
                title: '设备未连接',
                icon: 'error'
            });
            wx.navigateBack();
            return;
        }
        
        // 设置WiFi列表接收回调
        this.blufi.onWiFiListReceived = (wifiList) => {
            console.log('收到WiFi列表:', wifiList);
            this.setData({ 
                wifiList: wifiList,
                scanning: false
            });
        };
        
        // 自动扫描WiFi
        this.scanWiFi();
    },
    
    async scanWiFi() {
        if (this.data.scanning) return;
        
        this.setData({ 
            scanning: true,
            wifiList: []
        });
        
        try {
            await this.blufi.requestWiFiScan();
            
            // 设置超时
            setTimeout(() => {
                if (this.data.scanning) {
                    this.setData({ scanning: false });
                    wx.showToast({
                        title: '扫描超时',
                        icon: 'none'
                    });
                }
            }, 15000); // 15秒超时
            
        } catch (error) {
            this.setData({ scanning: false });
            wx.showToast({
                title: '扫描失败',
                icon: 'error'
            });
            console.error('WiFi扫描失败:', error);
        }
    },
    
    selectWiFi(e) {
        const wifi = e.currentTarget.dataset.wifi;
        console.log('选择WiFi:', wifi);
        
        this.setData({ 
            selectedSSID: wifi.ssid,
            wifiPassword: '' // 清空密码
        });
        
        this.checkCanSubmit();
    },
    
    onSSIDInput(e) {
        this.setData({ selectedSSID: e.detail.value });
        this.checkCanSubmit();
    },
    
    onPasswordInput(e) {
        this.setData({ wifiPassword: e.detail.value });
        this.checkCanSubmit();
    },
    
    checkCanSubmit() {
        const canSubmit = this.data.selectedSSID.trim().length > 0;
        this.setData({ canSubmit });
    },
    
    submitWiFiConfig(e) {
        const formData = e.detail.value;
        const ssid = formData.ssid || this.data.selectedSSID;
        const password = formData.password || this.data.wifiPassword;
        
        if (!ssid.trim()) {
            wx.showToast({
                title: '请输入WiFi名称',
                icon: 'none'
            });
            return;
        }
        
        console.log('提交WiFi配置:', { ssid, password: '***' });
        
        // 跳转到配网状态页面
        wx.navigateTo({
            url: `/pages/status/status?ssid=${encodeURIComponent(ssid)}&password=${encodeURIComponent(password)}`
        });
    }
});

4.4 第四阶段WiFi凭据传输和连接确认

4.4.1 WiFi凭据发送实现

// 在BluFiProvisioning类中添加WiFi配网方法
class BluFiProvisioning {
    // ... 前面的代码 ...
    
    // 开始WiFi配网
    async startProvisioning(ssid, password) {
        try {
            console.log('开始WiFi配网:', ssid);
            this.provisioningState = 'provisioning';
            
            // 步骤1发送SSID
            await this.sendWiFiSSID(ssid);
            await this.delay(500);
            
            // 步骤2发送密码如果有
            if (password && password.trim().length > 0) {
                await this.sendWiFiPassword(password);
                await this.delay(500);
            }
            
            // 步骤3发送连接命令
            await this.sendConnectWiFi();
            
            console.log('WiFi配网命令已发送等待设备响应...');
            return true;
            
        } catch (error) {
            console.error('WiFi配网失败:', error);
            this.provisioningState = 'failed';
            throw error;
        }
    }
    
    // 发送WiFi SSID
    async sendWiFiSSID(ssid) {
        console.log('发送WiFi SSID:', ssid);
        
        const data = new TextEncoder().encode(ssid);
        const packet = this.packet.build(0x01, BLUFI_TYPE_DATA.STA_SSID, data);
        
        await this.sendData(packet);
        console.log('SSID发送完成');
    }
    
    // 发送WiFi密码
    async sendWiFiPassword(password) {
        console.log('发送WiFi密码');
        
        const data = new TextEncoder().encode(password);
        const packet = this.packet.build(0x01, BLUFI_TYPE_DATA.STA_PASSWD, data);
        
        await this.sendData(packet);
        console.log('密码发送完成');
    }
    
    // 发送连接WiFi命令
    async sendConnectWiFi() {
        console.log('发送连接WiFi命令');
        
        const packet = this.packet.build(0x00, BLUFI_TYPE_CTRL.CONNECT_WIFI);
        
        await this.sendData(packet);
        console.log('连接命令发送完成');
    }
    
    // 获取WiFi连接状态
    async getWiFiStatus() {
        console.log('请求WiFi连接状态');
        
        const packet = this.packet.build(0x00, BLUFI_TYPE_CTRL.GET_WIFI_STATUS);
        
        await this.sendData(packet);
        console.log('状态请求已发送');
    }
    
    // 延迟函数
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // 设置配网回调
    setProvisioningCallbacks(callbacks) {
        this.onProvisioningSuccess = callbacks.onSuccess;
        this.onProvisioningFailed = callbacks.onFailed;
        this.onProvisioningProgress = callbacks.onProgress;
    }
}

4.4.2 配网状态页面实现

<!-- pages/status/status.wxml -->
<view class="status-container">
    <view class="status-header">
        <text class="title">配网状态</text>
        <view class="wifi-info">
            <text class="wifi-name">{{ssid}}</text>
        </view>
    </view>
    
    <view class="progress-section">
        <view class="progress-circle {{provisioningState}}">
            <view wx:if="{{provisioningState === 'provisioning'}}" class="loading-animation"></view>
            <view wx:elif="{{provisioningState === 'success'}}" class="success-icon"></view>
            <view wx:elif="{{provisioningState === 'failed'}}" class="error-icon"></view>
            <view wx:else class="waiting-icon"></view>
        </view>
        
        <view class="status-text">
            <text class="main-status">{{statusText}}</text>
            <text class="sub-status">{{subStatusText}}</text>
        </view>
    </view>
    
    <view class="steps-section">
        <view class="step {{step >= 1 ? 'completed' : step === 0 ? 'active' : ''}}">
            <view class="step-icon">1</view>
            <text>发送WiFi名称</text>
        </view>
        
        <view class="step {{step >= 2 ? 'completed' : step === 1 ? 'active' : ''}}">
            <view class="step-icon">2</view>
            <text>发送WiFi密码</text>
        </view>
        
        <view class="step {{step >= 3 ? 'completed' : step === 2 ? 'active' : ''}}">
            <view class="step-icon">3</view>
            <text>连接WiFi网络</text>
        </view>
        
        <view class="step {{step >= 4 ? 'completed' : step === 3 ? 'active' : ''}}">
            <view class="step-icon">4</view>
            <text>验证网络连接</text>
        </view>
    </view>
    
    <view class="actions-section">
        <button wx:if="{{provisioningState === 'success'}}" class="success-btn" bindtap="goHome">
            配网完成
        </button>
        
        <view wx:elif="{{provisioningState === 'failed'}}" class="failed-actions">
            <button class="retry-btn" bindtap="retryProvisioning">重新配网</button>
            <button class="back-btn" bindtap="goBack">返回修改</button>
        </view>
        
        <button wx:elif="{{provisioningState === 'provisioning'}}" class="cancel-btn" bindtap="cancelProvisioning">
            取消配网
        </button>
    </view>
    
    <!-- 错误信息显示 -->
    <view wx:if="{{errorMessage}}" class="error-section">
        <text class="error-title">错误信息:</text>
        <text class="error-message">{{errorMessage}}</text>
    </view>
</view>
// pages/status/status.js
Page({
    data: {
        ssid: '',
        password: '',
        provisioningState: 'waiting', // waiting, provisioning, success, failed
        statusText: '准备开始配网',
        subStatusText: '请稍候...',
        step: 0,
        errorMessage: ''
    },
    
    onLoad(options) {
        this.setData({
            ssid: decodeURIComponent(options.ssid || ''),
            password: decodeURIComponent(options.password || '')
        });
        
        // 获取全局的BluFi实例
        const app = getApp();
        this.blufi = app.globalData.blufi;
        
        if (!this.blufi || !this.blufi.isConnected) {
            wx.showToast({
                title: '设备未连接',
                icon: 'error'
            });
            wx.navigateBack();
            return;
        }
        
        // 设置配网回调
        this.blufi.setProvisioningCallbacks({
            onSuccess: () => this.onProvisioningSuccess(),
            onFailed: (reason) => this.onProvisioningFailed(reason),
            onProgress: (step, message) => this.onProvisioningProgress(step, message)
        });
        
        // 开始配网
        this.startProvisioning();
    },
    
    async startProvisioning() {
        try {
            this.setData({
                provisioningState: 'provisioning',
                statusText: '正在配网',
                subStatusText: '发送WiFi信息到设备...',
                step: 0,
                errorMessage: ''
            });
            
            // 步骤1发送SSID
            this.updateProgress(1, '发送WiFi名称...');
            await this.delay(1000);
            
            // 步骤2发送密码
            this.updateProgress(2, '发送WiFi密码...');
            await this.delay(1000);
            
            // 步骤3连接WiFi
            this.updateProgress(3, '设备连接WiFi网络...');
            
            // 执行实际配网
            await this.blufi.startProvisioning(this.data.ssid, this.data.password);
            
            // 等待连接结果
            this.updateProgress(4, '等待连接结果...');
            
            // 设置超时检查
            this.timeoutTimer = setTimeout(() => {
                if (this.data.provisioningState === 'provisioning') {
                    this.onProvisioningFailed('连接超时');
                }
            }, 30000); // 30秒超时
            
        } catch (error) {
             console.error('配网过程异常:', error);
             this.onProvisioningFailed(error.message || '配网失败');
         }
     },
     
     updateProgress(step, message) {
         this.setData({
             step: step,
             subStatusText: message
         });
     },
     
     onProvisioningSuccess() {
         console.log('配网成功');
         
         if (this.timeoutTimer) {
             clearTimeout(this.timeoutTimer);
             this.timeoutTimer = null;
         }
         
         this.setData({
             provisioningState: 'success',
             statusText: '配网成功',
             subStatusText: '设备已成功连接到WiFi网络',
             step: 4
         });
         
         wx.showToast({
             title: '配网成功',
             icon: 'success'
         });
     },
     
     onProvisioningFailed(reason) {
         console.error('配网失败:', reason);
         
         if (this.timeoutTimer) {
             clearTimeout(this.timeoutTimer);
             this.timeoutTimer = null;
         }
         
         this.setData({
             provisioningState: 'failed',
             statusText: '配网失败',
             subStatusText: '设备连接WiFi失败',
             errorMessage: reason || '未知错误'
         });
         
         wx.showToast({
             title: '配网失败',
             icon: 'error'
         });
     },
     
     onProvisioningProgress(step, message) {
         this.updateProgress(step, message);
     },
     
     retryProvisioning() {
         this.startProvisioning();
     },
     
     goBack() {
         wx.navigateBack();
     },
     
     goHome() {
         wx.reLaunch({
             url: '/pages/index/index'
         });
     },
     
     cancelProvisioning() {
         if (this.timeoutTimer) {
             clearTimeout(this.timeoutTimer);
             this.timeoutTimer = null;
         }
         
         this.setData({
             provisioningState: 'waiting',
             statusText: '配网已取消',
             subStatusText: '用户取消了配网操作',
             step: 0
         });
     },
     
     delay(ms) {
         return new Promise(resolve => setTimeout(resolve, ms));
     },
     
     onUnload() {
         if (this.timeoutTimer) {
             clearTimeout(this.timeoutTimer);
         }
     }
 });

5. 小程序项目结构

5.1 目录结构

blufi-miniprogram/
├── app.js                 // 小程序入口文件
├── app.json              // 小程序配置文件
├── app.wxss              // 全局样式文件
├── project.config.json   // 项目配置文件
├── pages/                // 页面目录
│   ├── index/            // 首页
│   │   ├── index.js
│   │   ├── index.wxml
│   │   └── index.wxss
│   ├── scan/             // 设备扫描页面
│   │   ├── scan.js
│   │   ├── scan.wxml
│   │   └── scan.wxss
│   ├── connect/          // 设备连接页面
│   │   ├── connect.js
│   │   ├── connect.wxml
│   │   └── connect.wxss
│   ├── config/           // WiFi配置页面
│   │   ├── config.js
│   │   ├── config.wxml
│   │   └── config.wxss
│   └── status/           // 配网状态页面
│       ├── status.js
│       ├── status.wxml
│       └── status.wxss
├── utils/                // 工具类目录
│   ├── blufi.js          // BluFi协议实现
│   ├── bluetooth.js      // 蓝牙工具类
│   └── util.js           // 通用工具函数
└── components/           // 组件目录
    ├── loading/          // 加载组件
    └── device-item/      // 设备列表项组件

5.2 小程序配置文件

5.2.1 app.json

{
  "pages": [
    "pages/index/index",
    "pages/scan/scan",
    "pages/connect/connect",
    "pages/config/config",
    "pages/status/status"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "BluFi配网",
    "navigationBarTextStyle": "black",
    "backgroundColor": "#f8f8f8"
  },
  "permission": {
    "scope.bluetooth": {
      "desc": "用于连接BluFi设备进行WiFi配网"
    }
  },
  "requiredBackgroundModes": ["bluetooth-central"],
  "style": "v2",
  "sitemapLocation": "sitemap.json"
}

5.2.2 project.config.json

{
  "description": "BluFi蓝牙配网小程序",
  "packOptions": {
    "ignore": []
  },
  "setting": {
    "urlCheck": false,
    "es6": true,
    "enhance": true,
    "postcss": true,
    "preloadBackgroundData": false,
    "minified": true,
    "newFeature": false,
    "coverView": true,
    "nodeModules": false,
    "autoAudits": false,
    "showShadowRootInWxmlPanel": true,
    "scopeDataCheck": false,
    "uglifyFileName": false,
    "checkInvalidKey": true,
    "checkSiteMap": true,
    "uploadWithSourceMap": true,
    "compileHotReLoad": false,
    "lazyloadPlaceholderEnable": false,
    "useMultiFrameRuntime": true,
    "useApiHook": true,
    "useApiHostProcess": true,
    "babelSetting": {
      "ignore": [],
      "disablePlugins": [],
      "outputPath": ""
    },
    "enableEngineNative": false,
    "useIsolateContext": true,
    "userConfirmedBundleSwitch": false,
    "packNpmManually": false,
    "packNpmRelationList": [],
    "minifyWXSS": true,
    "disableUseStrict": false,
    "minifyWXML": true,
    "showES6CompileOption": false,
    "useCompilerPlugins": false
  },
  "compileType": "miniprogram",
  "libVersion": "2.19.4",
  "appid": "your_app_id",
  "projectname": "blufi-provisioning",
  "debugOptions": {
    "hidedInDevtools": []
  },
  "scripts": {},
  "staticServerOptions": {
    "baseURL": "",
    "servePath": ""
  },
  "isGameTourist": false,
  "condition": {
    "search": {
      "list": []
    },
    "conversation": {
      "list": []
    },
    "game": {
      "list": []
    },
    "plugin": {
      "list": []
    },
    "gamePlugin": {
      "list": []
    },
    "miniprogram": {
      "list": []
    }
  }
}

5.3 全局应用文件

5.3.1 app.js

// app.js
App({
  globalData: {
    blufi: null,  // 全局BluFi实例
    deviceInfo: null,  // 当前连接的设备信息
    wifiConfig: null   // WiFi配置信息
  },
  
  onLaunch() {
    console.log('BluFi配网小程序启动');
    
    // 检查蓝牙支持
    this.checkBluetoothSupport();
    
    // 初始化全局数据
    this.initGlobalData();
  },
  
  checkBluetoothSupport() {
    wx.getSystemInfo({
      success: (res) => {
        console.log('系统信息:', res);
        
        // 检查是否支持蓝牙
        if (!wx.openBluetoothAdapter) {
          wx.showModal({
            title: '提示',
            content: '当前微信版本过低,无法使用蓝牙功能,请升级到最新微信版本后重试。',
            showCancel: false
          });
        }
      }
    });
  },
  
  initGlobalData() {
    // 初始化BluFi实例
    const BluFiProvisioning = require('./utils/blufi.js');
    this.globalData.blufi = new BluFiProvisioning();
  },
  
  onShow() {
    console.log('小程序显示');
  },
  
  onHide() {
    console.log('小程序隐藏');
  },
  
  onError(error) {
    console.error('小程序错误:', error);
  }
});

6. 错误处理和重试机制

6.1 错误码定义

// utils/error-codes.js
const BluFiErrorCodes = {
  // 蓝牙相关错误
  BLUETOOTH_NOT_AVAILABLE: {
    code: 1001,
    message: '蓝牙不可用,请检查蓝牙是否开启'
  },
  BLUETOOTH_ADAPTER_INIT_FAILED: {
    code: 1002,
    message: '蓝牙适配器初始化失败'
  },
  DEVICE_SCAN_FAILED: {
    code: 1003,
    message: '设备扫描失败'
  },
  DEVICE_NOT_FOUND: {
    code: 1004,
    message: '未发现BluFi设备'
  },
  
  // 连接相关错误
  CONNECTION_FAILED: {
    code: 2001,
    message: '设备连接失败'
  },
  CONNECTION_TIMEOUT: {
    code: 2002,
    message: '设备连接超时'
  },
  SERVICE_NOT_FOUND: {
    code: 2003,
    message: '未找到BluFi服务'
  },
  CHARACTERISTIC_NOT_FOUND: {
    code: 2004,
    message: '未找到BluFi特征值'
  },
  NOTIFICATION_ENABLE_FAILED: {
    code: 2005,
    message: '启用通知失败'
  },
  
  // 配网相关错误
  WIFI_SCAN_FAILED: {
    code: 3001,
    message: 'WiFi扫描失败'
  },
  WIFI_SCAN_TIMEOUT: {
    code: 3002,
    message: 'WiFi扫描超时'
  },
  WIFI_CREDENTIALS_INVALID: {
    code: 3003,
    message: 'WiFi凭据无效'
  },
  WIFI_CONNECTION_FAILED: {
    code: 3004,
    message: 'WiFi连接失败'
  },
  WIFI_CONNECTION_TIMEOUT: {
    code: 3005,
    message: 'WiFi连接超时'
  },
  PROVISIONING_TIMEOUT: {
    code: 3006,
    message: '配网超时'
  },
  
  // 数据传输错误
  DATA_SEND_FAILED: {
    code: 4001,
    message: '数据发送失败'
  },
  DATA_PARSE_FAILED: {
    code: 4002,
    message: '数据解析失败'
  },
  CHECKSUM_ERROR: {
    code: 4003,
    message: '数据校验失败'
  },
  
  // 通用错误
  UNKNOWN_ERROR: {
    code: 9999,
    message: '未知错误'
  }
};

module.exports = BluFiErrorCodes;

6.2 重试机制实现

// utils/retry.js
class RetryManager {
  constructor(options = {}) {
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 2000;
    this.backoffMultiplier = options.backoffMultiplier || 1.5;
    this.maxDelay = options.maxDelay || 10000;
  }
  
  async execute(operation, context = '') {
    let lastError;
    let currentDelay = this.retryDelay;
    
    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        console.log(`${context} - 尝试 ${attempt + 1}/${this.maxRetries + 1}`);
        const result = await operation();
        
        if (attempt > 0) {
          console.log(`${context} - 重试成功`);
        }
        
        return result;
        
      } catch (error) {
        lastError = error;
        console.error(`${context} - 尝试 ${attempt + 1} 失败:`, error);
        
        // 如果是最后一次尝试,直接抛出错误
        if (attempt === this.maxRetries) {
          break;
        }
        
        // 等待后重试
        console.log(`${context} - ${currentDelay}ms 后重试`);
        await this.delay(currentDelay);
        
        // 增加延迟时间(指数退避)
        currentDelay = Math.min(
          currentDelay * this.backoffMultiplier,
          this.maxDelay
        );
      }
    }
    
    console.error(`${context} - 所有重试都失败了`);
    throw lastError;
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

module.exports = RetryManager;

6.3 错误处理工具类

// utils/error-handler.js
const BluFiErrorCodes = require('./error-codes.js');

class ErrorHandler {
  static handleError(error, context = '') {
    console.error(`错误处理 [${context}]:`, error);
    
    let errorInfo = {
      code: BluFiErrorCodes.UNKNOWN_ERROR.code,
      message: BluFiErrorCodes.UNKNOWN_ERROR.message,
      detail: error.message || error.errMsg || '未知错误'
    };
    
    // 根据错误信息匹配错误码
    if (error.errMsg) {
      errorInfo = this.mapWxErrorToCode(error.errMsg);
    } else if (error.code) {
      errorInfo = this.findErrorByCode(error.code);
    } else if (error.message) {
      errorInfo = this.mapMessageToCode(error.message);
    }
    
    return errorInfo;
  }
  
  static mapWxErrorToCode(errMsg) {
    const errorMappings = {
      'bluetooth not available': BluFiErrorCodes.BLUETOOTH_NOT_AVAILABLE,
      'bluetooth adapter init fail': BluFiErrorCodes.BLUETOOTH_ADAPTER_INIT_FAILED,
      'createBLEConnection:fail': BluFiErrorCodes.CONNECTION_FAILED,
      'getBLEDeviceServices:fail': BluFiErrorCodes.SERVICE_NOT_FOUND,
      'getBLEDeviceCharacteristics:fail': BluFiErrorCodes.CHARACTERISTIC_NOT_FOUND,
      'notifyBLECharacteristicValueChange:fail': BluFiErrorCodes.NOTIFICATION_ENABLE_FAILED,
      'writeBLECharacteristicValue:fail': BluFiErrorCodes.DATA_SEND_FAILED
    };
    
    for (const [key, errorCode] of Object.entries(errorMappings)) {
      if (errMsg.includes(key)) {
        return {
          code: errorCode.code,
          message: errorCode.message,
          detail: errMsg
        };
      }
    }
    
    return {
      code: BluFiErrorCodes.UNKNOWN_ERROR.code,
      message: BluFiErrorCodes.UNKNOWN_ERROR.message,
      detail: errMsg
    };
  }
  
  static findErrorByCode(code) {
    for (const errorCode of Object.values(BluFiErrorCodes)) {
      if (errorCode.code === code) {
        return {
          code: errorCode.code,
          message: errorCode.message,
          detail: ''
        };
      }
    }
    
    return {
      code: BluFiErrorCodes.UNKNOWN_ERROR.code,
      message: BluFiErrorCodes.UNKNOWN_ERROR.message,
      detail: `错误码: ${code}`
    };
  }
  
  static mapMessageToCode(message) {
    const messageMappings = {
      '蓝牙不可用': BluFiErrorCodes.BLUETOOTH_NOT_AVAILABLE,
      '设备未连接': BluFiErrorCodes.CONNECTION_FAILED,
      '连接超时': BluFiErrorCodes.CONNECTION_TIMEOUT,
      '配网超时': BluFiErrorCodes.PROVISIONING_TIMEOUT,
      'WiFi连接失败': BluFiErrorCodes.WIFI_CONNECTION_FAILED
    };
    
    for (const [key, errorCode] of Object.entries(messageMappings)) {
      if (message.includes(key)) {
        return {
          code: errorCode.code,
          message: errorCode.message,
          detail: message
        };
      }
    }
    
    return {
      code: BluFiErrorCodes.UNKNOWN_ERROR.code,
      message: BluFiErrorCodes.UNKNOWN_ERROR.message,
      detail: message
    };
  }
  
  static showError(errorInfo, options = {}) {
    const title = options.title || '错误';
    const showDetail = options.showDetail !== false;
    
    let content = errorInfo.message;
    if (showDetail && errorInfo.detail) {
      content += `\n\n详细信息: ${errorInfo.detail}`;
    }
    
    wx.showModal({
      title: title,
      content: content,
      showCancel: false,
      confirmText: '确定'
    });
  }
  
  static showToast(errorInfo) {
    wx.showToast({
      title: errorInfo.message,
      icon: 'error',
      duration: 3000
    });
  }
}

module.exports = ErrorHandler;

7. 安全机制和数据保护

7.1 数据加密(可选)

// utils/encryption.js
class BluFiEncryption {
  constructor(options = {}) {
    this.enabled = options.enabled || false;
    this.algorithm = options.algorithm || 'AES-128-CFB';
    this.key = options.key || null;
    this.iv = options.iv || null;
  }
  
  // 设置加密密钥
  setKey(key) {
    this.key = key;
  }
  
  // 设置初始化向量
  setIV(iv) {
    this.iv = iv;
  }
  
  // 加密数据
  encrypt(data) {
    if (!this.enabled || !this.key) {
      return data;
    }
    
    try {
      // 这里应该实现实际的AES加密
      // 由于微信小程序环境限制,可能需要使用第三方加密库
      console.log('数据加密(模拟)');
      return data; // 返回加密后的数据
    } catch (error) {
      console.error('数据加密失败:', error);
      throw error;
    }
  }
  
  // 解密数据
  decrypt(encryptedData) {
    if (!this.enabled || !this.key) {
      return encryptedData;
    }
    
    try {
      // 这里应该实现实际的AES解密
      console.log('数据解密(模拟)');
      return encryptedData; // 返回解密后的数据
    } catch (error) {
      console.error('数据解密失败:', error);
      throw error;
    }
  }
}

module.exports = BluFiEncryption;

7.2 数据校验

// utils/checksum.js
class ChecksumCalculator {
  // 计算简单校验和
  static calculateSimpleChecksum(data) {
    let checksum = 0;
    const view = new Uint8Array(data);
    
    for (let i = 0; i < view.length; i++) {
      checksum += view[i];
    }
    
    return checksum & 0xFFFF;
  }
  
  // 验证校验和
  static verifyChecksum(data, expectedChecksum) {
    const calculatedChecksum = this.calculateSimpleChecksum(data);
    return calculatedChecksum === expectedChecksum;
  }
  
  // CRC16校验可选
  static calculateCRC16(data) {
    let crc = 0xFFFF;
    const polynomial = 0x1021;
    
    const view = new Uint8Array(data);
    
    for (let i = 0; i < view.length; i++) {
      crc ^= (view[i] << 8);
      
      for (let j = 0; j < 8; j++) {
        if (crc & 0x8000) {
          crc = (crc << 1) ^ polynomial;
        } else {
          crc <<= 1;
        }
        crc &= 0xFFFF;
      }
    }
    
    return crc;
  }
}

module.exports = ChecksumCalculator;

7.3 敏感数据处理

// utils/security.js
class SecurityManager {
  // 清理敏感数据
  static clearSensitiveData() {
    // 清理WiFi密码等敏感信息
    const app = getApp();
    if (app.globalData.wifiConfig) {
      app.globalData.wifiConfig.password = null;
    }
    
    // 清理本地存储中的敏感数据
    try {
      wx.removeStorageSync('wifi_password');
      wx.removeStorageSync('encryption_key');
    } catch (error) {
      console.warn('清理本地存储失败:', error);
    }
  }
  
  // 验证WiFi凭据格式
  static validateWiFiCredentials(ssid, password) {
    const errors = [];
    
    // SSID验证
    if (!ssid || ssid.trim().length === 0) {
      errors.push('WiFi名称不能为空');
    } else if (ssid.length > 32) {
      errors.push('WiFi名称长度不能超过32个字符');
    }
    
    // 密码验证(可选)
    if (password && password.length > 64) {
      errors.push('WiFi密码长度不能超过64个字符');
    }
    
    return {
      valid: errors.length === 0,
      errors: errors
    };
  }
  
  // 生成随机序列号
  static generateSequenceNumber() {
    return Math.floor(Math.random() * 65536);
  }
}

module.exports = SecurityManager;

8. 测试和调试

8.1 调试工具

// utils/debug.js
class DebugManager {
  constructor() {
    this.enabled = true; // 生产环境应设为false
    this.logLevel = 'DEBUG'; // DEBUG, INFO, WARN, ERROR
    this.logs = [];
    this.maxLogs = 1000;
  }
  
  log(level, message, data = null) {
    if (!this.enabled) return;
    
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      data
    };
    
    // 添加到日志数组
    this.logs.push(logEntry);
    
    // 限制日志数量
    if (this.logs.length > this.maxLogs) {
      this.logs.shift();
    }
    
    // 控制台输出
    const logMessage = `[${timestamp}] ${level}: ${message}`;
    switch (level) {
      case 'DEBUG':
        console.log(logMessage, data);
        break;
      case 'INFO':
        console.info(logMessage, data);
        break;
      case 'WARN':
        console.warn(logMessage, data);
        break;
      case 'ERROR':
        console.error(logMessage, data);
        break;
    }
  }
  
  debug(message, data) {
    this.log('DEBUG', message, data);
  }
  
  info(message, data) {
    this.log('INFO', message, data);
  }
  
  warn(message, data) {
    this.log('WARN', message, data);
  }
  
  error(message, data) {
    this.log('ERROR', message, data);
  }
  
  // 导出日志
  exportLogs() {
    return JSON.stringify(this.logs, null, 2);
  }
  
  // 清空日志
  clearLogs() {
    this.logs = [];
  }
  
  // 获取最近的错误日志
  getRecentErrors(count = 10) {
    return this.logs
      .filter(log => log.level === 'ERROR')
      .slice(-count);
  }
}

// 创建全局调试实例
const debugManager = new DebugManager();

module.exports = debugManager;

8.2 测试用例

// test/blufi-test.js
class BluFiTest {
  constructor() {
    this.testResults = [];
  }
  
  // 运行所有测试
  async runAllTests() {
    console.log('开始BluFi测试...');
    
    await this.testBluetoothInit();
    await this.testDeviceScan();
    await this.testPacketParsing();
    await this.testChecksumCalculation();
    
    this.printTestResults();
  }
  
  // 测试蓝牙初始化
  async testBluetoothInit() {
    try {
      const blufi = new BluFiProvisioning();
      await blufi.initBluetooth();
      
      this.addTestResult('蓝牙初始化', true, '成功');
    } catch (error) {
      this.addTestResult('蓝牙初始化', false, error.message);
    }
  }
  
  // 测试设备扫描
  async testDeviceScan() {
    try {
      const blufi = new BluFiProvisioning();
      
      let deviceFound = false;
      await blufi.startScan((device) => {
        if (blufi.isValidBluFiDevice(device)) {
          deviceFound = true;
        }
      });
      
      // 等待5秒
      await new Promise(resolve => setTimeout(resolve, 5000));
      await blufi.stopScan();
      
      this.addTestResult('设备扫描', deviceFound, deviceFound ? '发现设备' : '未发现设备');
    } catch (error) {
      this.addTestResult('设备扫描', false, error.message);
    }
  }
  
  // 测试数据包解析
  testPacketParsing() {
    try {
      const packet = new BluFiPacket();
      
      // 构建测试数据包
      const testData = new TextEncoder().encode('test');
      const buffer = packet.build(0x01, 0x02, testData);
      
      // 解析数据包
      const parsed = packet.parse(buffer);
      
      const success = parsed.type === 0x01 && 
                     parsed.subtype === 0x02 && 
                     new TextDecoder().decode(parsed.data) === 'test';
      
      this.addTestResult('数据包解析', success, success ? '解析正确' : '解析错误');
    } catch (error) {
      this.addTestResult('数据包解析', false, error.message);
    }
  }
  
  // 测试校验和计算
  testChecksumCalculation() {
    try {
      const testData = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
      const checksum = ChecksumCalculator.calculateSimpleChecksum(testData.buffer);
      
      // 预期校验和: 1+2+3+4 = 10
      const expected = 10;
      const success = checksum === expected;
      
      this.addTestResult('校验和计算', success, 
        success ? `校验和正确: ${checksum}` : `校验和错误: 期望${expected}, 实际${checksum}`);
    } catch (error) {
      this.addTestResult('校验和计算', false, error.message);
    }
  }
  
  addTestResult(testName, success, message) {
    this.testResults.push({
      name: testName,
      success: success,
      message: message,
      timestamp: new Date().toISOString()
    });
  }
  
  printTestResults() {
    console.log('\n=== BluFi测试结果 ===');
    
    let passCount = 0;
    let totalCount = this.testResults.length;
    
    this.testResults.forEach(result => {
      const status = result.success ? '✓ 通过' : '✗ 失败';
      console.log(`${status} ${result.name}: ${result.message}`);
      
      if (result.success) {
        passCount++;
      }
    });
    
    console.log(`\n总计: ${passCount}/${totalCount} 个测试通过`);
    
    if (passCount === totalCount) {
      console.log('🎉 所有测试都通过了!');
    } else {
      console.log('❌ 部分测试失败,请检查相关功能');
    }
  }
}

module.exports = BluFiTest;

9. 性能优化

9.1 内存管理

// utils/memory-manager.js
class MemoryManager {
  static clearUnusedData() {
    // 清理不再使用的设备列表
    const app = getApp();
    if (app.globalData.deviceList) {
      app.globalData.deviceList = [];
    }
    
    // 清理WiFi列表缓存
    if (app.globalData.wifiList) {
      app.globalData.wifiList = [];
    }
    
    // 强制垃圾回收(如果支持)
    if (typeof wx.triggerGC === 'function') {
      wx.triggerGC();
    }
  }
  
  static monitorMemoryUsage() {
    if (typeof wx.getPerformance === 'function') {
      const performance = wx.getPerformance();
      if (performance.memory) {
        console.log('内存使用情况:', {
          used: performance.memory.usedJSHeapSize,
          total: performance.memory.totalJSHeapSize,
          limit: performance.memory.jsHeapSizeLimit
        });
      }
    }
  }
}

module.exports = MemoryManager;

9.2 连接优化

// utils/connection-optimizer.js
class ConnectionOptimizer {
  constructor() {
    this.connectionPool = new Map();
    this.maxConnections = 1; // BluFi通常只需要一个连接
  }
  
  // 优化连接参数
  getOptimalConnectionParams() {
    return {
      timeout: 15000,        // 15秒连接超时
      interval: 100,         // 100ms扫描间隔
      allowDuplicatesKey: false,
      services: [BLUFI_SERVICE_UUID]
    };
  }
  
  // 连接重用
  reuseConnection(deviceId) {
    if (this.connectionPool.has(deviceId)) {
      const connection = this.connectionPool.get(deviceId);
      if (connection.isValid) {
        return connection;
      } else {
        this.connectionPool.delete(deviceId);
      }
    }
    return null;
  }
  
  // 添加连接到池
  addConnection(deviceId, connection) {
    // 如果池满了,移除最旧的连接
    if (this.connectionPool.size >= this.maxConnections) {
      const firstKey = this.connectionPool.keys().next().value;
      this.connectionPool.delete(firstKey);
    }
    
    this.connectionPool.set(deviceId, connection);
  }
  
  // 清理连接池
  clearPool() {
    this.connectionPool.clear();
  }
}

module.exports = ConnectionOptimizer;

10. 部署和发布

10.1 发布前检查清单

  • 所有功能测试通过
  • 与ESP官方espblufi应用对比测试完成
  • 错误处理机制完善
  • 用户界面友好性检查
  • 性能测试通过
  • 安全性检查完成
  • 代码审查完成
  • 文档更新完成

10.2 版本管理

// utils/version.js
const VERSION_INFO = {
  version: '1.0.0',
  buildNumber: '20240101',
  releaseDate: '2024-01-01',
  features: [
    'BluFi设备扫描和连接',
    'WiFi网络配置',
    '配网状态监控',
    '错误处理和重试机制'
  ],
  compatibility: {
    minWechatVersion: '7.0.0',
    minSystemVersion: {
      ios: '10.0',
      android: '6.0'
    },
    espIdfVersion: '4.4+'
  }
};

module.exports = VERSION_INFO;

10.3 用户手册

10.3.1 使用步骤

  1. 准备工作

    • 确保手机蓝牙已开启
    • 确保ESP32设备处于配网模式
    • 确保手机已连接到互联网
  2. 开始配网

    • 打开BluFi配网小程序
    • 点击"开始扫描"按钮
    • 从设备列表中选择要配网的设备
  3. 连接设备

    • 等待设备连接完成
    • 连接成功后会显示"设备连接成功"
  4. 配置WiFi

    • 选择要连接的WiFi网络或手动输入WiFi信息
    • 输入WiFi密码
    • 点击"开始配网"按钮
  5. 等待配网完成

    • 等待设备连接到WiFi网络
    • 配网成功后会显示"配网完成"

10.3.2 常见问题解决

Q: 扫描不到设备怎么办? A:

  • 检查手机蓝牙是否开启
  • 确认设备是否处于配网模式
  • 尝试重新启动设备
  • 检查设备距离是否过远

Q: 连接设备失败怎么办? A:

  • 确认设备未被其他应用占用
  • 尝试重启手机蓝牙
  • 重新扫描设备

Q: WiFi配网失败怎么办 A:

  • 检查WiFi密码是否正确
  • 确认WiFi信号强度是否足够
  • 检查WiFi网络是否正常
  • 尝试重新配网

11. 附录

11.1 参考文档

11.2 技术支持

  • 开发团队:[联系方式]
  • 问题反馈:[反馈渠道]
  • 更新通知:[通知方式]

11.3 更新日志

v1.0.0 (2024-01-01)

  • 初始版本发布
  • 实现基础BluFi配网功能
  • 支持设备扫描、连接、WiFi配置
  • 完善错误处理和重试机制

文档版本: 2.0
创建日期: 2025年8月
最后更新: 2025年8月
文档状态: 已完成