toy-Kapi_Rtc/BluFi蓝牙配网小程序开发需求说明书.md
2026-01-20 16:55:17 +08:00

2623 lines
75 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BluFi蓝牙配网小程序开发需求说明书
## 1. 项目概述
### 1.1 项目背景
本项目需要开发一个微信小程序用于与ESP32设备进行BluFi蓝牙配网。该小程序需要完全兼容ESP官方的espblufi应用程序功能能够成功进行WiFi配网并返回配网成功报告。
### 1.2 项目目标
- 开发微信小程序实现BluFi蓝牙配网功能
- 与ESP32设备建立稳定的蓝牙连接
- 完成WiFi网络配置和连接验证
- 提供用户友好的配网界面和状态反馈
- 确保与官方espblufi应用程序的完全兼容性
### 1.3 设备端配置信息
基于项目代码分析,设备端配置如下:
```javascript
// 设备端配置参数来自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.h``bluetooth_provisioning.cc`分析:
```javascript
// 设备端状态枚举对应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标准
```javascript
// 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 设备发现和命名规则
```javascript
// 设备名称识别(基于项目配置)
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数据包结构
```javascript
// 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 数据包类型定义
```javascript
// 控制包类型基于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 小程序端实现
```javascript
// 蓝牙配网管理类
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 设备扫描页面实现
```xml
<!-- 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>
```
```javascript
// 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 连接设备实现
```javascript
// 在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 连接页面实现
```xml
<!-- 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>
```
```javascript
// 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扫描实现
```javascript
// 在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配置页面实现
```xml
<!-- 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>
```
```javascript
// 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凭据发送实现
```javascript
// 在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 配网状态页面实现
```xml
<!-- 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>
```
```javascript
// 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
```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
```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
```javascript
// 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 错误码定义
```javascript
// 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 重试机制实现
```javascript
// 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 错误处理工具类
```javascript
// 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 数据加密(可选)
```javascript
// 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 数据校验
```javascript
// 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 敏感数据处理
```javascript
// 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 调试工具
```javascript
// 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 测试用例
```javascript
// 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 内存管理
```javascript
// 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 连接优化
```javascript
// 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 版本管理
```javascript
// 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 参考文档
- [ESP-IDF BluFi官方文档](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/blufi.html)
- [微信小程序蓝牙API文档](https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html)
- [BluFi协议规范](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/blufi)
### 11.2 技术支持
- 开发团队:[联系方式]
- 问题反馈:[反馈渠道]
- 更新通知:[通知方式]
### 11.3 更新日志
#### v1.0.0 (2024-01-01)
- 初始版本发布
- 实现基础BluFi配网功能
- 支持设备扫描、连接、WiFi配置
- 完善错误处理和重试机制
---
**文档版本**: 2.0
**创建日期**: 2025年8月
**最后更新**: 2025年8月
**文档状态**: 已完成