在物联网应用场景中,低功耗蓝牙(BLE)凭借其低能耗、连接便捷的特点,成为设备间数据交互的重要方式。Uniapp 作为一款跨平台开发框架,提供了丰富的 API 支持,使得在多个端实现低功耗蓝牙功能变得轻松高效。本文将结合示例代码,详细讲解如何在 Uniapp 中实现低功耗蓝牙的连接、数据读取与写入操作。
一、开发准备
在开始编码前,需确保开发环境已配置好 Uniapp 开发工具,同时要了解低功耗蓝牙的基本概念,如设备、服务、特征值等。设备是蓝牙连接的主体,服务是设备提供功能的集合,特征值则是具体的数据交互点,包含可读、可写等属性 。
二、初始化蓝牙模块
在 Uniapp 中,使用uni.openBluetoothAdapter方法初始化蓝牙模块,同时通过监听相关事件获取蓝牙状态变化和搜索到的设备信息。
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停止搜索,避免资源浪费。
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方法连接设备,并监听连接状态变化。
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获取指定服务的特征值列表,区分出可读和可写的特征值。
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读取数据。
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写入。
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关闭蓝牙模块。
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);
}
});
}
八、完整代码
<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 中实现低功耗蓝牙连接并读写数据的完整流程。若你在实践中有优化需求或遇到问题,欢迎随时分享,我们一起探讨解决。