Uniapp 实现 ESP32-S3 蓝牙配网功能(完整代码 + 原理详解)

该文章已生成可运行项目,

📱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
本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值