📱Uniapp与ESP32-S3蓝牙配网全解析:从原理到实战
一、🚀 项目背景与目标
在物联网应用开发中,为设备配置WiFi网络是最基础的需求。传统方式需要用户通过复杂的命令行或专业工具进行配置,对普通用户不友好。本文将介绍如何使用Uniapp开发一个可视化蓝牙配网界面,让用户通过手机App轻松完成ESP32-S3设备的WiFi配置。
二、💡 技术原理与核心概念
1. 蓝牙低功耗(BLE)通信基础
- GATT协议:基于属性的分层协议,是BLE通信的基础
- Service(服务):包含一组相关特征的集合,如"电池服务"
- Characteristic(特征):最小数据单元,有唯一UUID,如"电池电量"
- Descriptor(描述符):提供特征值的附加信息
2. Uniapp蓝牙API核心方法
| 方法名 | 作用 |
|---|---|
openBluetoothAdapter |
初始化蓝牙模块 |
startBluetoothDevicesDiscovery |
开始扫描蓝牙设备 |
createBLEConnection |
连接到蓝牙设备 |
getBLEDeviceServices |
获取设备的服务 |
getBLEDeviceCharacteristics |
获取特征值 |
writeBLECharacteristicValue |
向特征值写入数据 |
notifyBLECharacteristicValueChange |
启用特征值变化通知 |
三、🛠️ 完整代码实现
1. 核心页面代码
<template>
<view class="container">
<view class="header">
<text class="title">ESP32-S3 蓝牙配网工具</text>
</view>
<view class="form">
<input v-model="ssid" placeholder="请输入WiFi名称" class="input" />
<view class="pass-box">
<input v-model="password" placeholder="请输入WiFi密码" type="password" class="input" />
</view>
</view>
<view class="buttons">
<button @click="startScan" :disabled="scanning">{
{ scanning ? '扫描中...' : '扫描设备' }}</button>
<button @click="connectToDevice" :disabled="!selectedDeviceId || scanning || isConnecting">连接设备</button>
<button @click="sendCredentials" :disabled="!isConnected || scanning || isSending">发送配网信息</button>
<button @click="triggerWifiScan" :disabled="!isConnected || scanning || isFetchingWifi">获取WiFi列表</button>
</view>
<view class="status">状态:{
{ statusText }}</view>
<!-- WiFi列表展示 -->
<view class="wifi-list" v-if="wifiList.length > 0">
<text class="section-title">附近WiFi列表({
{ wifiList.length }}个)</text>
<view class="wifi-item" v-for="(wifi, index) in wifiList" :key="index" @click="selectWifi(wifi)">
<text :style="{ color: ssid === wifi.ssid ? '#007AFF' : '#333' }">
<text class="wifi-name">{
{ wifi.ssid||'--' }}</text>
<text class="wifi-signal">信号强度:{
{ wifi.rssi }}dBm</text>
</text>
<view class="signal-bar" :style="{ width: getSignalStrength(wifi.rssi) + '%' }"></view>
</view>
</view>
<!-- 扫描到的设备列表 -->
<view class="devices" v-if="devices.length">
<text class="section-title">可连接设备</text>
<view v-for="item in devices" :key="item.deviceId" @click="selectDevice(item)" class="device-item">
<text :style="{ color: selectedDeviceId === item.deviceId ? '#007AFF' : '#333' }">
{
{ item.name || item.localName || '未知设备' }}
({
{ item.deviceId.slice(-6) }})
</text>
<text class="rssi">信号:{
{ item.RSSI }}dBm</text>
</view>
</view>
<!-- 日志输出区域 -->
<view class="log">
<text class="section-title">操作日志</text>
<scroll-view scroll-y style="max-height: 200px;">
<view v-for="(log, index) in logs" :key="index" class="log-item">{
{ log }}</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// WiFi配网信息
ssid: '',
password: '',
// 蓝牙设备信息
devices: [],
selectedDeviceId: '',
isConnected: false,
scanning: false,
statusText: '就绪',
logs: [],
// BLE服务与特征值UUID(与ESP32端保持一致)
serviceId: '000000FF-0000-1000-8000-00805F9B34FB',
charSSID: '0000FFE2-0000-1000-8000-00805F9B34FB', // 用于发送WiFi名称
charPASS: '0000FFE3-0000-1000-8000-00805F9B34FB', // 用于发送WiFi密码
charNotify: '0000FFE1-0000-1000-8000-00805F9B34FB', // 用于接收配网结果
charWifi: '0000FFE4-0000-1000-8000-00805F9B34FB', // 用于接收WiFi列表
// 数据接收缓冲区(处理蓝牙分片传输)
dataBuffer: null,
wifiList: [],
// 内部状态变量
deviceFoundListener: null,
timeoutTimer: null,
connectRetryCount: 0,
maxConnectRetries: 3,
isDiscovering: false,
isConnecting: false,
isSending: false,
isFetchingWifi: false, // 标识是否正在等待WiFi数据
parseTimeout: null, // 数据解析超时计时器
configResultTimer: null, // 配网结果超时计时器
wifiRetryCount: 0,
maxWifiRetries: 3,
isReceivingData: false, // 标识是否正在接收分片数据
// 权限相关变量
isAppPlatform: uni.getSystemInfoSync().platform === 'android' || uni.getSystemInfoSync().platform ===
'ios',
permissionExplained: false,
locationPermissionExplained: false
};
},
onLoad() {
// 初始化蓝牙监听器
this.initBluetoothListener();
// 读取权限说明状态(避免重复弹窗)
this.permissionExplained = uni.getStorageSync('bluetoothPermissionExplained') === 'true';
this.locationPermissionExplained = uni.getStorageSync('androidLocationPermissionExplained') === 'true';
this.log('应用启动,等待操作...');
},
onUnload() {
// 页面卸载时清理蓝牙资源
this.cleanupBluetooth();
},
methods: {
// 初始化蓝牙数据监听
initBluetoothListener() {
// 监听蓝牙特征值变化(接收数据)
uni.onBLECharacteristicValueChange(res => {
// 处理WiFi列表数据(仅处理当前连接的设备)
if (res.deviceId === this.selectedDeviceId && res.characteristicId === this.charWifi) {
// 过滤空数据包(避免无效解析)
if (res.value.byteLength === 0 || res.value.byteLength === undefined) {
this.log('收到蓝牙数据(空数据包),已忽略');
return;
}
this.isReceivingData = true; // 标记正在接收数据,阻止重试
this.log(`收到蓝牙数据(${res.value.byteLength}字节)`);
// 解析数据,若返回有效列表则终止重试流程
const wifiData = this.parseBluetoothData(res.value);
if (wifiData.length > 0) {
this.wifiList = wifiData;
this.statusText = `已获取${wifiData.length}个WiFi网络`;
this.wifiRetryCount = 0; // 解析成功后重置重试计数
this.isFetchingWifi = false;
this.isReceivingData = false;
return;
}
// 若只是分片数据(未完成),重置超时计时器继续等待
if (this.parseTimeout) clearTimeout(this.parseTimeout);
this.parseTimeout = setTimeout(() => {
this.log('分片数据接收超时,判定为无效数据');
this.isReceivingData = false;
this.resetDataBuffer();
this.autoRetryWifiScan(); // 超时后才重试
}, 5000); // 分片间隔超时设为5秒
}
// 监听配网结果通知
if (res.deviceId === this.selectedDeviceId && res.characteristicId === this.charNotify) {
// 关键:使用UTF-8解码配网结果(可能含中文)
const resultStr = this.arrayBufferToString(res.value);
this.log(`收到配网结果:${resultStr}`);
this.statusText = resultStr.includes('Failed') ? '配网失败,请重试' : '配网成功';
this.handleConfigResult(resultStr);
}
});
// 监听蓝牙连接状态变化
uni.onBLEConnectionStateChange(res => {
if (res.deviceId === this.selectedDeviceId) {
this.isConnected = res.connected;
if (!res.connected) {
this.log('蓝牙连接已断开');
this.statusText = '设备已断开连接';
this.isSending = false;
this.isFetchingWifi = false;
this.isReceivingData = false;
this.clearConfigTimer();
}
}
});
},
// 处理配网结果
handleConfigResult(result) {
this.clearConfigTimer();
if (result.includes('success') || result.includes('连接成功')) {
this.statusText = '配网成功!设备正在连接WiFi...';
uni.showToast({
title: '配网成功',
icon: 'success',
duration: 3000
});
this.log('配网成功,设备将尝试连接WiFi');
setTimeout(() => this.disconnect(), 3000);
} else if (result.includes('fail') || result.includes('密码错误') || result === "Failed to Connected!") {
const errorMsg = result.includes('密码') ? 'WiFi密码错误' : '配网失败';
this.statusText = `${errorMsg},请重新尝试`;
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
});
this.log(`配网失败:${result}`);
this.isSending = false;
} else if (result.includes('timeout')) {
this.statusText = '配网超时,WiFi可能无法连接';
uni.showToast({
title: '配网超时',
icon: 'none',
duration: 3000
});
this.log('配网超时,WiFi可能不存在或信号弱');
this.isSending = false;
}
},
// 日志记录
log(message) {
const time = new Date().toLocaleTimeString();
const logText = `[${time}] ${message}`;
this.logs.push(logText);
console.log(logText);
if (this.logs.length > 50) this.logs.shift(); // 只保留最近50条日志
},
// 开始扫描蓝牙设备
startScan() {
this.scanning = true;
this.devices = [];
this.selectedDeviceId = '';
this.wifiList = [];
this.isDiscovering = false;
this.statusText = '初始化蓝牙...';
this.log('开始新的扫描');
if (this.deviceFoundListener) {
uni.offBluetoothDeviceFound(this.deviceFoundListener);
this.deviceFoundListener = null;
}
if (this.permissionExplained) {
this.proceedWithBluetoothScan();
} else {
uni.showModal({
title: '需要蓝牙权限',
content: '为了搜索并连接ESP32-S3设备,完成WiFi配网,需要获取蓝牙权限。',
confirmText: '同意',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
this.permissionExplained = true;
uni.setStorageSync('bluetoothPermissionExplained', 'true');
this.proceedWithBluetoothScan();
} else {
this.scanning = false;
this.statusText = '用户取消蓝牙权限申请';
this.log('用户拒绝授予蓝牙权限');
}
}
});
}
},
// 权限说明后继续扫描流程
proceedWithBluetoothScan() {
if (this.isAppPlatform) {
this.checkAppBluetoothPermission();
} else {
this.openBluetoothAndScan();
}
},
// 检查App平台权限
checkAppBluetoothPermission() {
const platform = uni.getSystemInfoSync().platform;
this.log(`当前平台:${platform}`);
// #ifdef APP-PLUS
if (platform === 'android') {
if (this.locationPermissionExplained) {
this.requestAndroidPermissions();
} else {
uni.showModal({
title: '补充权限说明',
content: '安卓设备需要位置权限才能精准扫描蓝牙设备,此权限仅用于设备搜索。',
showCancel: false,
confirmText: '知道了',
success: () => {
this.locationPermissionExplained = true;
uni.setStorageSync('androidLocationPermissionExplained', 'true');
this.requestAndroidPermissions();
}
});
}
} else {
this.checkIOSBluetoothPermission();
}
// #endif
},
// Android权限请求
requestAndroidPermissions() {
plus.android.requestPermissions(
[
'android.permission.BLUETOOTH',
'android.permission.BLUETOOTH_ADMIN',
'android.permission.BLUETOOTH_SCAN',
'android.permission.BLUETOOTH_CONNECT',
'android.permission.ACCESS_FINE_LOCATION'
],
() => {
this.log('安卓权限请求成功,初始化蓝牙...');
this.openBluetoothAndScan();
},
(err) => {
this.log(`安卓权限被拒绝:${JSON.stringify(err)}`);
this.statusText = '请授予蓝牙及位置权限';
this.scanning = false;
uni.showModal({
title: '权限不足',
content: '安卓设备需要蓝牙和位置权限才能扫描设备,请在设置中开启',
success: (res) => {
if (res.confirm) plus.runtime.openURL('app-settings:');
}
});
}
);
},
// iOS权限检查
checkIOSBluetoothPermission() {
const cbManager = plus.ios.importClass('CBPeripheralManager');
const authStatus = cbManager.authorizationStatus();
if (authStatus === 3) {
this.log('iOS蓝牙权限已授权,初始化蓝牙...');
this.openBluetoothAndScan();
} else {
this.log(`iOS蓝牙权限未授权(状态:${authStatus})`);
this.statusText = '请开启蓝牙权限';
this.scanning = false;
uni.showModal({
title: '权限不足',
content: 'iOS需要蓝牙权限才能连接设备,请在设置中开启"蓝牙"权限',
success: (res) => {
if (res.confirm) plus.runtime.openURL('app-settings:');
}
});
}
},
// 打开蓝牙并启动扫描
openBluetoothAndScan() {
uni.openBluetoothAdapter({
success: () => {
this.log('蓝牙适配器初始化成功');
this.startDeviceDiscovery();
},
fail: err => {
this.handleBluetoothError(err, '蓝牙初始化失败');
this.scanning = false;
}
});
},
// 开始设备发现
startDeviceDiscovery() {
if (this.timeoutTimer) clearTimeout(this.timeoutTimer);
// 设置扫描超时(5秒)
this.timeoutTimer = setTimeout(() => {
this.stopScan();
this.log(`扫描超时,共发现${this.devices.length}个设备`);
}, 5000);
// 注册设备发现监听
this.deviceFoundListener = uni.onBluetoothDeviceFound(res => {
if (!this.isDiscovering) {
this.isDiscovering = true;
this.handleDeviceFound(res);
this.isDiscovering = false;
}
});
// 启动扫描
uni.startBluetoothDevicesDiscovery({
services: [],
allowDuplicatesKey: true,
success: () => {
this.statusText = '正在扫描设备...';
this.log('设备扫描已启动');
},
fail: err => {
this.handleBluetoothError(err, '启动扫描失败');
this.scanning = false;
}
});
},
// 处理发现的设备
handleDeviceFound(res) {
const devices = res.devices || [];
devices.forEach(device => {
if (!device.deviceId || device.RSSI < -90) return; // 过滤信号过弱的设备
const deviceName = (device.name || device.localName || '').toLowerCase();
// 只保留ESP32相关设备
if (!deviceName.includes('esp32') && !deviceName.includes('ragus')) return;
// 避免重复添加设备
const isDuplicate = this.devices.some(d => d.deviceId === device.deviceId);
if (!isDuplicate) {
this.devices.push(device);
this.log(`发现设备:${device.name || '未知设备'}(信号:${device.RSSI}dBm)`);
}
});
},
// 停止扫描
stopScan() {
if (!this.scanning) return;
clearTimeout(this.timeoutTimer);
uni.stopBluetoothDevicesDiscovery({
success: () => {
this.scanning = false;
this.statusText = `扫描完成(${this.devices.length}个设备)`;
if (this.deviceFoundListener) {
uni.offBluetoothDeviceFound(this.deviceFoundListener);
this.deviceFoundListener = null;
}
},
fail: err => {
this.log(`停止扫描失败:${JSON.stringify(err)}`);
}
});
},
// 选择设备
selectDevice(device) {
if (this.scanning) return;
this.selectedDeviceId = device.deviceId;
this.statusText = `已选择:${device.name || '未知设备'}`;
this.log(`选中设备:${device.deviceId.slice(-6)}`);
},
// 选中wifi
selectWifi(wifi) {
this.ssid = wifi.ssid;
this.statusText = `已选择:${wifi.ssid || '未知设备'}`;
this.log(`选中WiFi:${wifi.ssid}`);
},
// 连接到选中的设备
connectToDevice() {
if (!this.selectedDeviceId) {
uni.showToast({
title: '请先选择设备',
icon: 'none'
});
return;
}
this.isConnecting = true;
this.statusText = '正在连接设备...';
this.log(`开始连接设备:${this.selectedDeviceId.slice(-6)}`);
this.connectRetryCount = 0;
this.attemptConnection();
},
// 尝试连接设备
attemptConnection() {
uni.createBLEConnection({
deviceId: this.selectedDeviceId,
timeout: 15000,
success: () => {
this.log('设备连接成功,正在获取服务...');
this.isConnected = true;
this.isConnecting = false;
this.statusText = '连接成功,获取服务中...';
this.discoverServices();
},
fail: err => {
this.connectRetryCount++;
if (this.connectRetryCount < this.maxConnectRetries) {
this.log(`连接失败(${this.connectRetryCount}/${this.maxConnectRetries}),重试中...`);
setTimeout(() => this.attemptConnection(), 2000);
} else {
this.handleBluetoothError(err, '连接失败(已达最大重试次数)');
this.isConnected = false;
this.isConnecting = false;
}
}
});
},
// 发现设备服务
discoverServices() {
uni.getBLEDeviceServices({
deviceId: this.selectedDeviceId,
success: res => {
this.log(`发现${res.services.length}个服务`);
// 查找目标服务
const targetService = res.services.find(s =>
s.uuid.toLowerCase() === this.serviceId.toLowerCase()
);
if (targetService) {
this.log(`找到目标服务:${this.serviceId}`);
this.enableNotifications();
} else {
this.log(`未找到目标服务:${this.serviceId}`);
this.statusText = '连接失败:服务不存在';
this.disconnect();
}
},
fail: err => {
this.handleBluetoothError(err, '获取服务失败');
this.disconnect();
}
});
},
// 启用特征值通知
enableNotifications() {
uni.getBLEDeviceCharacteristics({
deviceId: this.selectedDeviceId,
serviceId: this.serviceId,
success: res => {
this.log(`发现${res.characteristics.length}个特征值`);
this.enableSpecificNotify(this.charNotify, '配网状态通知');
this.enableSpecificNotify(this.charWifi, 'WiFi列表通知');
},
fail: err => {
this.handleBluetoothError(err, '获取特征值失败');
this.disconnect();
}
});
},
// 启用指定特征值的通知
enableSpecificNotify(characteristicId, name) {
uni.notifyBLECharacteristicValueChange({
deviceId: this.selectedDeviceId,
serviceId: this.serviceId,
characteristicId: characteristicId,
state: true,
success: () => {
this.log(`✅ ${name}已启用`);
if (characteristicId === this.charWifi) {
this.statusText = '连接就绪,可获取WiFi列表';
}
},
fail: err => {
this.log(`❌ 启用${name}失败:${JSON.stringify(err)}`);
if (characteristicId === this.charWifi) {
this.statusText = `启用WiFi通知失败:${err.errMsg}`;
}
}
});
},
// 触发设备发送WiFi列表
triggerWifiScan() {
if (!this.isConnected) {
uni.showToast({
title: '请先连接设备',
icon: 'none'
});
return;
}
// 如果正在接收数据或等待响应,不重复请求
if (this.isFetchingWifi || this.isReceivingData) {
this.log('已在获取WiFi列表中,请勿重复点击');
return;
}
// 重置状态,发送一次请求
this.isFetchingWifi = true;
this.isReceivingData = false;
this.resetDataBuffer();
this.wifiList = [];
this.statusText = `正在请求WiFi列表(${this.wifiRetryCount + 1}/${this.maxWifiRetries})`;
this.log(`发送WiFi列表请求(第${this.wifiRetryCount + 1}次尝试)`);
uni.readBLECharacteristicValue({
deviceId: this.selectedDeviceId,
serviceId: this.serviceId,
characteristicId: this.charWifi,
success: () => {
this.log('WiFi列表请求已发送,等待设备推送数据...');
// 设置总超时(10秒):如果设备完全无响应,触发重试
if (this.parseTimeout) clearTimeout(this.parseTimeout);
this.parseTimeout = setTimeout(() => {
if (!this.isReceivingData) { // 确认设备未响应
this.log('设备无任何响应,判定为超时');
this.isReceivingData = false;
this.autoRetryWifiScan();
}
}, 10000);
},
fail: err => {
this.handleBluetoothError(err, '发送WiFi请求失败');
this.isFetchingWifi = false;
this.autoRetryWifiScan();
}
});
},
// 自动重试获取WiFi列表
autoRetryWifiScan() {
// 停止当前等待状态
this.isFetchingWifi = false;
// 达到最大重试次数则停止
if (this.wifiRetryCount >= this.maxWifiRetries - 1) {
this.statusText = 'WiFi列表获取失败,请手动重试';
this.log(`已达最大重试次数(${this.maxWifiRetries}次),停止重试`);
this.wifiRetryCount = 0; // 重置计数,避免影响下一次手动尝试
return;
}
// 否则等待1.5秒后重试(给设备喘息时间)
this.wifiRetryCount++; // 仅在重试前递增,确保计数正确
this.log(`等待设备就绪,准备第${this.wifiRetryCount + 1}次重试`);
setTimeout(() => {
// 重试前检查连接状态
if (this.isConnected && !this.isFetchingWifi) {
this.triggerWifiScan();
} else {
this.log('设备已断开或状态异常,取消重试');
this.wifiRetryCount = 0; // 异常时重置
}
}, 1500);
},
// 解析蓝牙数据(处理分片传输,支持中文WiFi名称解析)
parseBluetoothData(buffer) {
try {
if (!buffer) throw new Error('接收的数据为空');
// 提取有效ArrayBuffer
let validBuffer;
if (buffer instanceof ArrayBuffer) {
validBuffer = buffer;
} else if (buffer.buffer instanceof ArrayBuffer) {
validBuffer = buffer.buffer;
} else if (typeof buffer === 'object' && 'value' in buffer) {
validBuffer = buffer.value;
} else {
throw new Error(`不支持的buffer类型: ${typeof buffer}`);
}
// 转换为Uint8Array并拼接分片数据
const chunkBytes = Array.from(new Uint8Array(validBuffer));
if (!this.dataBuffer) {
this.dataBuffer = chunkBytes;
} else {
this.dataBuffer = this.dataBuffer.concat(chunkBytes);
}
// 使用增强版的UTF-8解码方法
let str = '';
try {
// 优先使用更可靠的解码方式
str = decodeURIComponent(escape(String.fromCharCode.apply(null, this.dataBuffer)));
} catch {
// 如果字符串太长导致apply失败,用for循环转
str = this.dataBuffer.map(c => String.fromCharCode(c)).join('');
}
this.log(`UTF-8解析后的数据片段: ${str}`);
// 提取中括号中的JSON数组内容(增强版特性)
const match = str.match(/\[.*\]/s);
if (!match) {
this.log('未检测到完整的JSON数组');
return [];
}
let jsonStr = match[0];
// 修复可能的格式问题(增强版特性)
jsonStr = jsonStr
.replace(/}{/g, '},{') // 修复对象间缺少逗号
.replace(/(\w+):/g, '"$1":') // 修复key缺少引号
.replace(/:'([^']+)'/g, ':"$1"'); // 单引号转双引号
// 解析JSON
const result = JSON.parse(jsonStr);
// 验证数据结构
if (Array.isArray(result) && result.every(item =>
item && typeof item.ssid === 'string' && typeof item.rssi === 'number'
)) {
this.log(`解析成功,共${result.length}个WiFi(含中文)`);
// 清除超时计时器,避免误触发重试
if (this.parseTimeout) {
clearTimeout(this.parseTimeout);
this.parseTimeout = null;
}
return result;
} else {
throw new Error('数据结构不符合要求(需包含ssid和rssi)');
}
} catch (e) {
this.log(`解析失败: ${e.message}`);
return []; // 解析失败,等待超时后由超时逻辑触发重试
}
},
// 发送WiFi配网信息
sendCredentials() {
if (!this.ssid.trim() || !this.password) {
uni.showToast({
title: '请输入WiFi名称和密码',
icon: 'none'
});
return;
}
if (!this.isConnected) {
uni.showToast({
title: '设备已断开,请重新连接',
icon: 'none'
});
return;
}
this.isSending = true;
this.statusText = '正在发送SSID...';
this.log(`发送SSID:${this.ssid}`);
// 先发送SSID,成功后再发送密码
this.writeCharacteristic(this.charSSID, this.ssid, () => {
this.statusText = '正在发送密码...';
this.log('SSID发送成功,开始发送密码');
this.writeCharacteristic(this.charPASS, this.password, () => {
this.statusText = '配网信息发送完成,等待设备响应...';
this.log('密码发送成功,等待设备配网结果(超时15秒)');
// 设置配网超时计时器
this.configResultTimer = setTimeout(() => {
this.handleConfigResult('timeout');
}, 15000);
}, 3);
}, 3);
},
// 向特征值写入数据(支持长数据分片发送)
writeCharacteristic(characteristicId, value, callback, retryCount = 3) {
// 使用增强版的字符串转ArrayBuffer方法(支持中文和特殊字符)
const buffer = this.stringToArrayBuffer(value);
const maxChunkSize = 18; // 蓝牙每次最大发送字节数(根据设备调整)
const chunks = [];
// 分割数据为多个分片
for (let i = 0; i < buffer.byteLength; i += maxChunkSize) {
const end = Math.min(i + maxChunkSize, buffer.byteLength);
chunks.push(buffer.slice(i, end));
}
let index = 0;
const sendChunk = () => {
if (!this.isConnected) {
this.log('发送失败:设备已断开连接');
this.isSending = false;
this.statusText = '发送失败:设备已断开';
return;
}
if (index >= chunks.length) {
callback(); // 所有分片发送完成,执行回调
return;
}
const chunk = chunks[index];
uni.writeBLECharacteristicValue({
deviceId: this.selectedDeviceId,
serviceId: this.serviceId,
characteristicId: characteristicId,
value: chunk,
success: () => {
this.log(`发送第${index + 1}/${chunks.length}包数据`);
index++;
setTimeout(sendChunk, 100); // 间隔100ms发送下一分片
},
fail: err => {
this.log(`第${index + 1}包发送失败(剩余${retryCount}次重试):${err.errMsg}`);
if (retryCount > 0) {
setTimeout(sendChunk, 500); // 失败后延迟500ms重试
retryCount--;
} else {
this.handleBluetoothError(err, `发送失败(第${index + 1}包)`);
this.isSending = false;
}
}
});
};
sendChunk(); // 开始发送第一个分片
},
// 字符串转ArrayBuffer(增强版:支持中文、表情和特殊字符)
stringToArrayBuffer(str) {
// 使用unescape和encodeURIComponent确保UTF-8编码正确
const utf8 = unescape(encodeURIComponent(str));
const result = new Uint8Array(utf8.length);
for (let i = 0; i < utf8.length; i++) {
result[i] = utf8.charCodeAt(i);
}
return result.buffer;
},
// ArrayBuffer转字符串(增强版:支持中文、表情和特殊字符)
arrayBufferToString(buffer) {
const uint8Array = new Uint8Array(buffer);
return decodeURIComponent(escape(String.fromCharCode.apply(null, uint8Array)));
},
// 断开连接
disconnect() {
if (!this.selectedDeviceId) return;
uni.closeBLEConnection({
deviceId: this.selectedDeviceId,
success: () => {
this.log('设备连接已断开');
this.isConnected = false;
this.wifiList = [];
this.statusText = '已断开连接';
},
fail: err => {
this.log(`断开连接失败:${JSON.stringify(err)}`);
}
});
},
// 清理蓝牙资源
cleanupBluetooth() {
if (this.isConnected) this.disconnect();
if (this.deviceFoundListener) {
uni.offBluetoothDeviceFound(this.deviceFoundListener);
}
this.clearConfigTimer();
if (this.timeoutTimer) clearTimeout(this.timeoutTimer);
if (this.parseTimeout) clearTimeout(this.parseTimeout);
uni.closeBluetoothAdapter({
success: () => this.log('蓝牙适配器已关闭')
});
},
// 清除配网超时计时器
clearConfigTimer() {
if (this.configResultTimer) {
clearTimeout(this.configResultTimer);
this.configResultTimer = null;
}
},
// 处理蓝牙错误
handleBluetoothError(err, message) {
this.log(`${message}:${err.errMsg}`);
this.statusText = `${message}(${err.errMsg})`;
this.scanning = false;
this.isConnected = false;
this.isSending = false;
this.clearConfigTimer();
},
// 重置数据缓冲区
resetDataBuffer() {
this.dataBuffer = null;
if (this.parseTimeout) {
clearTimeout(this.parseTimeout);
this.parseTimeout = null;
}
},
// 计算WiFi信号强度百分比(-100dBm为0%,-50dBm为100%)
getSignalStrength(rssi) {
return Math.min(Math.max(100 + rssi, 0), 100);
}
}
};
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #fff;
min-height: 100vh;
}
.header {
text-align: center;
margin: 30rpx 0;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.form {
margin-bottom: 30rpx;
}
.input {
width: 100%;
height: 80rpx;
padding: 0 20rpx;
margin-bottom: 20rpx;
border: 1px solid #eee;
border-radius: 8rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.buttons {
display: flex;
flex-wrap: wrap;
gap: 15rpx;
margin-bottom: 30rpx;
}
button {
flex: 1 1 auto;
min-width: 200rpx;
height: 80rpx;
line-height: 80rpx;
font-size: 26rpx;
padding: 0;
background-color: #007AFF;
color: white;
}
button:disabled {
background-color: #CCCCCC;
}
.status {
height: 60rpx;
line-height: 60rpx;
text-align: center;
font-size: 28rpx;
color: #666;
margin-bottom: 30rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.section-title {
display: block;
font-size: 30rpx;
font-weight: bold;
color: #333;
margin: 20rpx 0 10rpx;
}
.wifi-list,
.devices {
background-color: #f9f9f9;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 30rpx;
}
.wifi-item,
.device-item {
padding: 15rpx 0;
border-bottom: 1px solid #eee;
}
.wifi-item:last-child,
.device-item:last-child {
border-bottom: none;
}
.wifi-name {
font-size: 28rpx;
}
.wifi-signal {
font-size: 24rpx;
display: block;
margin-top: 5rpx;
}
.signal-bar {
height: 8rpx;
background-color: #007AFF;
margin-top: 10rpx;
border-radius: 4rpx;
}
.rssi {
font-size: 22rpx;
color: #999;
m

最低0.47元/天 解锁文章
2009

被折叠的 条评论
为什么被折叠?



