uniapp 实现低功耗蓝牙连接并读写数据实战指南

在物联网应用场景中,低功耗蓝牙(BLE)凭借其低能耗、连接便捷的特点,成为设备间数据交互的重要方式。Uniapp 作为一款跨平台开发框架,提供了丰富的 API 支持,使得在多个端实现低功耗蓝牙功能变得轻松高效。本文将结合示例代码,详细讲解如何在 Uniapp 中实现低功耗蓝牙的连接、数据读取与写入操作。

一、开发准备

在开始编码前,需确保开发环境已配置好 Uniapp 开发工具,同时要了解低功耗蓝牙的基本概念,如设备、服务、特征值等。设备是蓝牙连接的主体,服务是设备提供功能的集合,特征值则是具体的数据交互点,包含可读、可写等属性 。

二、初始化蓝牙模块

在 Uniapp 中,使用uni.openBluetoothAdapter方法初始化蓝牙模块,同时通过监听相关事件获取蓝牙状态变化和搜索到的设备信息。

javascript 复制代码
const openBluetooth = () => {

uni.openBluetoothAdapter({

success: (e) => {

console.log('蓝牙适配器打开成功');

// 监听蓝牙适配器状态变化

uni.onBluetoothAdapterStateChange((res) => {

console.log('蓝牙适配器状态变化:', res);

});

// 监听搜索到新设备

uni.onBluetoothDeviceFound((res) => {

const devices = res.devices;

console.log('搜索到新设备数量:', devices.length);

// 处理搜索到的设备数据

});

},

fail: (e) => {

console.log('蓝牙适配器打开失败:', e);

}

});

}

三、搜索蓝牙设备

调用uni.startBluetoothDevicesDiscovery方法开始搜索周围的蓝牙设备,可通过传入参数筛选特定设备。搜索完成后,及时调用uni.stopBluetoothDevicesDiscovery停止搜索,避免资源浪费。

javascript 复制代码
const startDiscovery = () => {

uni.startBluetoothDevicesDiscovery({

success: (e) => {

console.log('开始搜索蓝牙设备成功');

},

fail: (e) => {

console.log('开始搜索蓝牙设备失败:', e);

}

});

}

const stopDiscovery = () => {

uni.stopBluetoothDevicesDiscovery({

success: (e) => {

console.log('停止搜索蓝牙设备成功');

},

fail: (e) => {

console.log('停止搜索蓝牙设备失败:', e);

}

});

}

四、连接蓝牙设备

在获取到目标设备的deviceId后,使用uni.createBLEConnection方法连接设备,并监听连接状态变化。

javascript 复制代码
const connectDevice = (deviceId) => {

uni.createBLEConnection({

deviceId: deviceId,

success: (e) => {

console.log('连接蓝牙设备成功');

},

fail: (e) => {

console.log('连接蓝牙设备失败:', e);

}

});

// 监听连接状态变化

uni.onBLEConnectionStateChange((res) => {

if (res.connected) {

console.log('蓝牙设备已连接');

} else {

console.log('蓝牙设备已断开');

}

});

}

五、获取设备服务与特征值

连接设备后,通过uni.getBLEDeviceServices获取设备服务列表,再利用uni.getBLEDeviceCharacteristics获取指定服务的特征值列表,区分出可读和可写的特征值。

javascript 复制代码
const getServices = (deviceId) => {

uni.getBLEDeviceServices({

deviceId: deviceId,

success: (res) => {

const services = res.services;

console.log('获取服务成功,服务数量:', services.length);

// 处理服务数据

},

fail: (e) => {

console.log('获取服务失败:', e);

}

});

}

const getCharacteristics = (deviceId, serviceId) => {

uni.getBLEDeviceCharacteristics({

deviceId: deviceId,

serviceId: serviceId,

success: (res) => {

const characteristics = res.characteristics;

console.log('获取特征值成功,特征值数量:', characteristics.length);

// 处理特征值数据,区分可读可写

},

fail: (e) => {

console.log('获取特征值失败:', e);

}

});

}

六、数据读取与写入

6.1 读取数据

获取到可读特征值的characteristicId后,调用uni.readBLECharacteristicValue读取数据。

javascript 复制代码
const readValue = (deviceId, serviceId, characteristicId) => {

uni.readBLECharacteristicValue({

deviceId: deviceId,

serviceId: serviceId,

characteristicId: characteristicId,

success: (res) => {

const data = res.value;

console.log('读取数据成功:', data);

},

fail: (e) => {

console.log('读取数据失败:', e);

}

});

}

6.2 写入数据

对于可写特征值,将数据转换为合适格式后,使用uni.writeBLECharacteristicValue写入。

javascript 复制代码
const writeValue = (deviceId, serviceId, characteristicId, data) => {

// 数据格式转换,如将字符串转换为ArrayBuffer

const buffer = new TextEncoder('utf - 8').encode(data);

uni.writeBLECharacteristicValue({

deviceId: deviceId,

serviceId: serviceId,

characteristicId: characteristicId,

value: buffer,

success: (res) => {

console.log('写入数据成功');

},

fail: (e) => {

console.log('写入数据失败:', e);

}

});

}

七、断开连接与关闭蓝牙

使用uni.closeBLEConnection断开与设备的连接,通过uni.closeBluetoothAdapter关闭蓝牙模块。

javascript 复制代码
const disconnectDevice = (deviceId) => {

uni.closeBLEConnection({

deviceId: deviceId,

success: (e) => {

console.log('断开蓝牙设备连接成功');

},

fail: (e) => {

console.log('断开蓝牙设备连接失败:', e);

}

});

}

const closeBluetooth = () => {

uni.closeBluetoothAdapter({

success: (e) => {

console.log('关闭蓝牙适配器成功');

},

fail: (e) => {

console.log('关闭蓝牙适配器失败:', e);

}

});

}

八、完整代码

html 复制代码
<template>
	<view class="container">
		<button @click="openBluetooth">初始化蓝牙模块</button>
		<button @click="startDiscovery">开始搜索蓝牙设备</button>
		<button @click="stopDiscovery">停止搜索蓝牙设备</button>

		<view class="input-group">
			<text>设备:</text>
			<input v-model="selectedDeviceName" disabled />
			<button @click="selectDevice">选择设备</button>
		</view>

		<button @click="connectDevice">连接蓝牙设备</button>
		<button @click="getServices">获取设备服务</button>

		<view class="input-group">
			<text>服务:</text>
			<input v-model="selectedServiceId" disabled />
			<button @click="selectService">选择服务</button>
		</view>

		<button @click="getCharacteristics">获取服务的特征值</button>

		<view class="input-group">
			<text>读取特征值:</text>
			<input v-model="selectedCharacteristicId" disabled />
			<button @click="selectCharacteristic">选择</button>
		</view>

		<button @click="readValue">读取特征值数据</button>

		<view class="input-group">
			<text>读取数据:</text>
			<input v-model="readValueData" disabled style="width:60%" />
		</view>

		<hr />

		<view class="input-group">
			<text>写入特征值:</text>
			<input v-model="selectedWCharacteristicId" disabled />
			<button @click="selectwCharacteristic">选择</button>
		</view>

		<button @click="writeValue">写入特征值数据</button>

		<view class="input-group">
			<text>写入数据:</text>
			<input v-model="writeValueData" style="width:60%" />
		</view>

		<button @click="disconnectDevice">断开蓝牙设备</button>
		<button @click="closeBluetooth">关闭蓝牙模块</button>

		<view class="output">
			<text>{{ outputText }}</text>
		</view>
	</view>
</template>

<script setup>
	import {
		ref
	} from 'vue'
	import {
		onLoad,
		onUnload
	} from '@dcloudio/uni-app'
	// 数据状态
	const bds = ref([]) // 可连接设备列表
	const deviceId = ref(null)
	const bconnect = ref(false)
	const bss = ref([]) // 连接设备服务列表
	const serviceId = ref(null)
	const bscs = ref([]) // 连接设备服务对应的特征值列表
	const characteristicId = ref(null)
	const bscws = ref([]) // 可写特征值列表
	const wcharacteristicId = ref(null)

	// UI绑定数据
	const selectedDeviceName = ref('')
	const selectedServiceId = ref('')
	const selectedCharacteristicId = ref('')
	const selectedWCharacteristicId = ref('')
	const readValueData = ref('')
	const writeValueData = ref('test')
	const outputText = ref('Bluetooth用于管理蓝牙设备,搜索附近蓝牙设备、连接实现数据通信等。')

	// 工具函数
	const buffer2hex = (value) => {
		let t = ''
		if (value) {
			const v = new Uint8Array(value)
			for (const i in v) {
				t += '0x' + v[i].toString(16) + ' '
			}
		} else {
			t = '无效值'
		}
		return t
	}

	const str2ArrayBuffer = (s, f) => {
		const b = new Blob([s], {
			type: 'text/plain'
		})
		const r = new FileReader()
		r.readAsArrayBuffer(b)
		r.onload = () => {
			if (f) f.call(null, r.result)
		}
	}

	// 重设数据
	const resetDevices = (d, s) => {
		if (!d) {
			bds.value = []
			deviceId.value = null
			selectedDeviceName.value = ''
		}
		if (!s) {
			bss.value = []
			serviceId.value = null
			selectedServiceId.value = ''
		}
		bscs.value = []
		bscws.value = []
		characteristicId.value = null
		wcharacteristicId.value = null
		selectedCharacteristicId.value = ''
		selectedWCharacteristicId.value = ''
	}

	// 输出日志
	const outLine = (text) => {
		outputText.value += '\n' + text
	}

	const outSet = (text) => {
		outputText.value = text
	}

	// 蓝牙操作函数
	const openBluetooth = () => {
		outSet('打开蓝牙适配器:')
		uni.openBluetoothAdapter({
			success: (e) => {
				outLine('打开成功!')

				// 监听蓝牙适配器状态变化
				uni.onBluetoothAdapterStateChange((res) => {
					outLine('onBluetoothAdapterStateChange: ' + JSON.stringify(res))
				})

				// 监听搜索到新设备
				uni.onBluetoothDeviceFound((res) => {
					const devices = res.devices
					outLine('onBluetoothDeviceFound: ' + devices.length)
					for (const i in devices) {
						outLine(JSON.stringify(devices[i]))
						const device = devices[i]
						if (device.deviceId) {
							bds.value.push(device)
						}
					}
					if (!bconnect.value && bds.value.length > 0) {
						const n = bds.value[bds.value.length - 1].name
						selectedDeviceName.value = n || bds.value[bds.value.length - 1].deviceId
						deviceId.value = bds.value[bds.value.length - 1].deviceId
					}
				})

				// 监听低功耗蓝牙设备连接状态变化
				uni.onBLEConnectionStateChange((res) => {
					outLine('onBLEConnectionStateChange: ' + JSON.stringify(res))
					if (deviceId.value === res.deviceId) {
						bconnect.value = res.connected
					}
				})

				// 监听低功耗蓝牙设备的特征值变化
				uni.onBLECharacteristicValueChange((res) => {
					outLine('onBLECharacteristicValueChange: ' + JSON.stringify(res))
					const value = buffer2hex(res.value)
					console.log(value)
					outLine('value(hex) = ' + value)
					if (characteristicId.value === res.characteristicId) {
						readValueData.value = value
					} else if (wcharacteristicId.value === res.characteristicId) {
						uni.showToast({
							title: value,
							icon: 'none'
						})
					}
				})
			},
			fail: (e) => {
				outLine('打开失败! ' + JSON.stringify(e))
			}
		})
	}

	const startDiscovery = () => {
		outSet('开始搜索蓝牙设备:')
		resetDevices()
		uni.startBluetoothDevicesDiscovery({
			success: (e) => {
				outLine('开始搜索成功!')
			},
			fail: (e) => {
				outLine('开始搜索失败! ' + JSON.stringify(e))
			}
		})
	}

	const stopDiscovery = () => {
		outSet('停止搜索蓝牙设备:')
		uni.stopBluetoothDevicesDiscovery({
			success: (e) => {
				outLine('停止搜索成功!')
			},
			fail: (e) => {
				outLine('停止搜索失败! ' + JSON.stringify(e))
			}
		})
	}

	const selectDevice = () => {
		if (bds.value.length <= 0) {
			uni.showToast({
				title: '未搜索到有效蓝牙设备!',
				icon: 'none'
			})
			return
		}

		const buttons = bds.value.map(device => {
			return device.name || device.deviceId
		})

		uni.showActionSheet({
			title: '选择蓝牙设备',
			itemList: buttons,
			success: (res) => {
				selectedDeviceName.value = bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceId
				deviceId.value = bds.value[res.tapIndex].deviceId
				outLine('选择了"' + (bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceId) + '"')
			}
		})
	}

	const connectDevice = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		outSet('连接设备: ' + deviceId.value)
		uni.createBLEConnection({
			deviceId: deviceId.value,
			success: (e) => {
				outLine('连接成功!')
			},
			fail: (e) => {
				outLine('连接失败! ' + JSON.stringify(e))
			}
		})
	}

	const getServices = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		if (!bconnect.value) {
			uni.showToast({
				title: '未连接蓝牙设备!',
				icon: 'none'
			})
			return
		}
		resetDevices(true)
		outSet('获取蓝牙设备服务:')
		uni.getBLEDeviceServices({
			deviceId: deviceId.value,
			success: (e) => {
				const services = e.services
				outLine('获取服务成功! ' + services.length)
				if (services.length > 0) {
					bss.value = services
					for (const i in services) {
						outLine(JSON.stringify(services[i]))
					}
					if (bss.value.length > 0) {
						selectedServiceId.value = serviceId.value = bss.value[bss.value.length - 1].uuid
					}
				} else {
					outLine('获取服务列表为空?')
				}
			},
			fail: (e) => {
				outLine('获取服务失败! ' + JSON.stringify(e))
			}
		})
	}

	const selectService = () => {
		if (bss.value.length <= 0) {
			uni.showToast({
				title: '未获取到有效蓝牙服务!',
				icon: 'none'
			})
			return
		}

		const buttons = bss.value.map(service => service.uuid)

		uni.showActionSheet({
			title: '选择服务',
			itemList: buttons,
			success: (res) => {
				selectedServiceId.value = serviceId.value = bss.value[res.tapIndex].uuid
				outLine('选择了服务: "' + serviceId.value + '"')
			}
		})
	}

	const getCharacteristics = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		if (!bconnect.value) {
			uni.showToast({
				title: '未连接蓝牙设备!',
				icon: 'none'
			})
			return
		}
		if (!serviceId.value) {
			uni.showToast({
				title: '未选择服务!',
				icon: 'none'
			})
			return
		}
		resetDevices(true, true)
		outSet('获取蓝牙设备指定服务的特征值:')
		uni.getBLEDeviceCharacteristics({
			deviceId: deviceId.value,
			serviceId: serviceId.value,
			success: (e) => {
				const characteristics = e.characteristics
				outLine('获取特征值成功! ' + characteristics.length)
				if (characteristics.length > 0) {
					bscs.value = []
					bscws.value = []
					for (const i in characteristics) {
						const characteristic = characteristics[i]
						outLine(JSON.stringify(characteristic))
						if (characteristic.properties) {
							if (characteristic.properties.read) {
								bscs.value.push(characteristic)
							}
							if (characteristic.properties.write) {
								bscws.value.push(characteristic)
								if (characteristic.properties.notify || characteristic.properties
									.indicate) {
									uni.notifyBLECharacteristicValueChange({
										deviceId: deviceId.value,
										serviceId: serviceId.value,
										characteristicId: characteristic.uuid,
										state: true,
										success: (e) => {
											outLine('notifyBLECharacteristicValueChange ' +
												characteristic.uuid + ' success.')
										},
										fail: (e) => {
											outLine('notifyBLECharacteristicValueChange ' +
												characteristic.uuid + ' failed! ' + JSON
												.stringify(e))
										}
									})
								}
							}
						}
					}
					if (bscs.value.length > 0) {
						selectedCharacteristicId.value = characteristicId.value = bscs.value[bscs.value
							.length - 1].uuid
					}
					if (bscws.value.length > 0) {
						selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[bscws.value
							.length - 1].uuid
					}
				} else {
					outLine('获取特征值列表为空?')
				}
			},
			fail: (e) => {
				outLine('获取特征值失败! ' + JSON.stringify(e))
			}
		})
	}

	const selectCharacteristic = () => {
		if (bscs.value.length <= 0) {
			uni.showToast({
				title: '未获取到有效可读特征值!',
				icon: 'none'
			})
			return
		}

		const buttons = bscs.value.map(char => char.uuid)

		uni.showActionSheet({
			title: '选择特征值',
			itemList: buttons,
			success: (res) => {
				selectedCharacteristicId.value = characteristicId.value = bscs.value[res.tapIndex].uuid
				outLine('选择了特征值: "' + characteristicId.value + '"')
			}
		})
	}

	let readInterval = null

	const readValue = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		if (!bconnect.value) {
			uni.showToast({
				title: '未连接蓝牙设备!',
				icon: 'none'
			})
			return
		}
		if (!serviceId.value) {
			uni.showToast({
				title: '未选择服务!',
				icon: 'none'
			})
			return
		}
		if (!characteristicId.value) {
			uni.showToast({
				title: '未选择读取的特征值!',
				icon: 'none'
			})
			return
		}


		outSet('读取蓝牙设备的特征值数据: ')
		uni.readBLECharacteristicValue({
			deviceId: deviceId.value,
			serviceId: serviceId.value,
			characteristicId: characteristicId.value,
			success: (e) => {
				outLine('读取数据成功!')
			},
			fail: (e) => {
				outLine('读取数据失败! ' + JSON.stringify(e))
			}
		})
	}

	const selectwCharacteristic = () => {
		if (bscws.value.length <= 0) {
			uni.showToast({
				title: '未获取到有效可写特征值!',
				icon: 'none'
			})
			return
		}

		const buttons = bscws.value.map(char => char.uuid)

		uni.showActionSheet({
			title: '选择特征值',
			itemList: buttons,
			success: (res) => {
				selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[res.tapIndex].uuid
				outLine('选择了特征值: "' + wcharacteristicId.value + '"')
			}
		})
	}

	const writeValue = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		if (!bconnect.value) {
			uni.showToast({
				title: '未连接蓝牙设备!',
				icon: 'none'
			})
			return
		}
		if (!serviceId.value) {
			uni.showToast({
				title: '未选择服务!',
				icon: 'none'
			})
			return
		}
		if (!wcharacteristicId.value) {
			uni.showToast({
				title: '未选择写入的特征值!',
				icon: 'none'
			})
			return
		}
		if (!writeValueData.value || writeValueData.value === '') {
			uni.showToast({
				title: '请输入需要写入的数据',
				icon: 'none'
			})
			return
		}

		str2ArrayBuffer(writeValueData.value, (buffer) => {
			outSet('写入蓝牙设备的特征值数据: ')
			uni.writeBLECharacteristicValue({
				deviceId: deviceId.value,
				serviceId: serviceId.value,
				characteristicId: wcharacteristicId.value,
				value: buffer,
				success: (e) => {
					outLine('写入数据成功!')
				},
				fail: (e) => {
					outLine('写入数据失败! ' + JSON.stringify(e))
				}
			})
		})
	}

	const disconnectDevice = () => {
		if (!deviceId.value) {
			uni.showToast({
				title: '未选择设备!',
				icon: 'none'
			})
			return
		}
		resetDevices(true)
		outSet('断开蓝牙设备连接:')
		uni.closeBLEConnection({
			deviceId: deviceId.value,
			success: (e) => {
				outLine('断开连接成功!')
			},
			fail: (e) => {
				outLine('断开连接失败! ' + JSON.stringify(e))
			}
		})
	}

	const closeBluetooth = () => {
		outSet('关闭蓝牙适配器:')
		resetDevices()
		uni.closeBluetoothAdapter({
			success: (e) => {
				outLine('关闭成功!')
				bconnect.value = false
			},
			fail: (e) => {
				outLine('关闭失败! ' + JSON.stringify(e))
			}
		})
	}
</script>

<style>
	.container {
		padding: 20px;
	}

	button {
		margin: 10px 0;
		padding: 10px;
		background-color: #007aff;
		color: white;
		border-radius: 5px;
		border: none;
	}

	.input-group {
		display: flex;
		align-items: center;
		margin: 10px 0;
	}

	.input-group input {
		flex: 1;
		border: 1px solid #ccc;
		padding: 5px;
		margin: 0 5px;
	}

	.input-group button {
		margin: 0;
		padding: 5px 10px;
	}

	.output {
		margin-top: 20px;
		padding: 10px;
		background-color: #f5f5f5;
		border: 1px solid #ddd;
		white-space: pre-wrap;
		max-height: 200px;
		overflow-y: auto;
	}
</style>

九、注意事项

1、权限问题:在不同平台上,需确保应用已获取蓝牙相关权限。例如在 Android 平台,需在AndroidManifest.xml中添加蓝牙权限声明。

2、兼容性:不同设备的蓝牙服务和特征值 UUID 可能不同,需根据实际设备文档进行适配。

3、错误处理:完善各 API 调用的错误处理逻辑,及时向用户反馈操作结果。

以上就是在 Uniapp 中实现低功耗蓝牙连接并读写数据的完整流程。若你在实践中有优化需求或遇到问题,欢迎随时分享,我们一起探讨解决。

相关推荐
kooboo china.9 分钟前
Tailwind CSS实战技巧:从核心类到高效开发
前端·javascript·css·编辑器·html
卓怡学长9 分钟前
w317汽车维修预约服务系统设计与实现
java·前端·spring boot·spring·汽车
lilye6641 分钟前
精益数据分析(38/126):SaaS模式的流失率计算优化与定价策略案例
前端·人工智能·数据分析
5:001 小时前
Qt:(创建项目)
java·前端·qt
green_pine_1 小时前
CSS学习笔记12——CSS3新增特性
前端·css·笔记·学习
努力的搬砖人.1 小时前
Spring Boot 使用 WebMagic 爬虫框架入门
java·spring boot·爬虫
Code哈哈笑1 小时前
【SpringBoot】Spring中事务的实现:声明式事务@Transactional、编程式事务
java·spring boot·后端·spring·mybatis
Wenhao.1 小时前
Go-web开发之帖子功能
开发语言·前端·golang
蓝婷儿1 小时前
前端面试每日三题 - Day 22
前端·面试·职场和发展
java1234_小锋2 小时前
如何配置NGINX作为反向代理服务器来缓存后端服务的响应?
前端·nginx·缓存