介绍:
本文采用uni-app框架来创建一个简单的用户界面,用于搜索、连接和发送命令给蓝牙设备。
1.打开蓝牙适配器
function openBluetooth() {
uni.openBluetoothAdapter({
success() {
uni.offBluetoothDeviceFound();
// 监听新设备发现事件
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach(device => {
if (device.name && device.name.startsWith('one-parking')) {
const existingDevice = devicesInfo.value.find(d => d.deviceId === device.deviceId);
if (!existingDevice) {
devicesInfo.value.push(device);
}
}
});
});
// 开始搜索蓝牙设备
uni.startBluetoothDevicesDiscovery({
success() {
console.log('开始搜索蓝牙设备...');
// 设置一个定时器,在一定时间后停止搜索
setTimeout(stopDiscovery, 10000);
},
fail(err) {
console.error('开始搜索失败', err);
}
});
},
fail() {
uni.showToast({
title: '请打开蓝牙',
icon: 'none'
});
}
});
}
2.连接蓝牙
function connectBluetooth( data : any ) {
bluetoothInfo.value = data;
bluetoothInfo.value.deviceId = data.deviceId;
uni.createBLEConnection({
deviceId : data.deviceId,
success() {
// 判断连接的状态和断开重新连接
checkSttusOrReconnect(data);
// 获取蓝牙的服务ServiceID
uni.getBLEDeviceServices({
deviceId : data.deviceId,
success(servicesRes : any) {
if(servicesRes.services.length > 0) {
bluetoothInfo.value.serviceId = servicesRes.services[0].uuid;
// 获取蓝牙的特征值characteristicId
uni.getBLEDeviceCharacteristics({
deviceId : data.deviceId,
serviceId : servicesRes.services[0].uuid,
success(resCharacter) {
if(resCharacter.characteristics.length > 0) {
bluetoothInfo.value.characteristics = resCharacter.characteristics;
// 接受通知
notify();
let macAddress = data.name.split('-')[1];
let password = calculatePassword(macAddress);
let bondCommand = `phbond${password}`;
sendMsg(bondCommand);
}
}
})
}
}
});
}
})
}
提示:这里根据蓝牙的设备id连接后,一起获取了服务和特征id,用于方便后面蓝牙进行通信,其中的特征值是一个重点,不同的特征值id会对应不同的操作,不然就会操作不了其他的api,比如uni.notifyBLECharacteristicValueChange(OBJECT)这个就需要有notify 或者 indicate 才可以成功调用
3.监听蓝牙的通知
// 接收蓝牙端发送的通知
function notify() {
uni.notifyBLECharacteristicValueChange({
state: true,
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId : bluetoothInfo.value.deviceId,
serviceId: bluetoothInfo.value.serviceId,
characteristicId : bluetoothInfo.value.characteristics.find(item => item.properties.notify).uuid,
success() {
monitorMsg();
},
fail(res) {
console.log('不支持这个notify' + JSON.stringify(res))
}
})
}
function monitorMsg() {
uni.onBLECharacteristicValueChange((res : any) => {
console.log("发送过来的数据---------", res);
let resHex = ab2ascii(res.value)
console.log("转换后的简易数据----------",resHex);
});
}
提示: 刚刚在上面说到的点就是特征值id有多个,需要有对应的特征权限
4.发送数据
// 向蓝牙写数据
function sendMsg( msg : string ) {
uni.writeBLECharacteristicValue({
deviceId : bluetoothInfo.value.deviceId,
serviceId: bluetoothInfo.value.serviceId,
characteristicId : bluetoothInfo.value.characteristics.find(item => item.properties.write).uuid,
value: asciiToArrayBuffer(msg) as any,
writeType: 'write',
success(res) {
console.log('消息发送成功', res)
},
fail(res) {
console.log('消息发送失败', res)
}
});
}
注意:其中写数据需要向设备写的是二进制,所以需要转换一下
// 二进制转化工具
function ab2ascii(buffer : ArrayBuffer) {
var str = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit : any) {
return String.fromCharCode(bit);
}
)
return str.join('');
}
function asciiToArrayBuffer(str : string) {
if (!str) {
return new ArrayBuffer(0);
}
var buffer = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buffer);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buffer;
}
5.监听蓝牙的状态并发现断开重连
function checkSttusOrReconnect( data : any ) {
uni.onBLEConnectionStateChange(function (state) {
if (!state.connected) {
console.warn('与设备' + data.name + '的连接已断开');
if(breakCount.value < 2) {
reconnectionTimer = setTimeout(()=>{
connectBluetooth(bluetoothInfo.value);
},3000);
}
breakCount.value += 1;
} else {
console.log('与设备 ' + data.name + ' 的连接已建立');
if(reconnectionTimer) { clearTimeout(reconnectionTimer) }
breakCount.value = 0;
}
});
}
6.完整的demo代码(设备是车辆地锁)
<template>
<view class="pageBox">
<view style="font-size: 22px;text-align: center;margin-top: 32px;">
蓝牙测试用例
</view>
<view style="margin: 12px;display: flex;flex-direction: column;">
<radio-group style="margin: 12px;gap: 26px;display: flex;flex-wrap: wrap;padding: 6px;">
<radio value="phup" @click="cmdText = 'phup'">phup(升)</radio>
<radio value="phdown" @click="cmdText = 'phdown'">phdown(降)</radio>
<radio value="phstatus" @click=" cmdText = 'phstatus'">phstatus(状态)</radio>
</radio-group>
<button @click="sendControllCmd()" style="background-color: #564DD6;color: #fff;border-radius: 26px;margin: 12px;">发送指令</button>
</view>
<view style="margin: 22px;font-size: 18px;font-weight: bold;color: royalblue;">蓝牙设置信息:</view>
<view v-for="devices in devicesInfo">
<view style="justify-content: space-between;border-radius: 8px;display: flex;margin: 12px;margin:12px 22px;background-color: #fff;border: 2px dashed #564DD6;padding: 12px;">
<view style="display: flex;flex-direction: column;gap: 6px;">
<view>设备名称:{{devices.name}}</view>
<view>ID: {{devices.deviceId}}</view>
</view>
<view><button style="margin: 6px;border-radius: 6px;background-color: #9A2CD7;color: #fff;" size="mini" @click="connectBluetooth(devices)">连接</button></view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { onReady } from "@dcloudio/uni-app";
import { ref } from "vue";
onReady(()=>{
openBluetooth();
});
let devicesInfo = ref([]);
let reconnectionTimer : any;
let breakCount = ref<number>(0);
function openBluetooth() {
uni.openBluetoothAdapter({
success() {
uni.offBluetoothDeviceFound();
// 监听新设备发现事件
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach(device => {
if (device.name && device.name.startsWith('one-parking')) {
const existingDevice = devicesInfo.value.find(d => d.deviceId === device.deviceId);
if (!existingDevice) {
devicesInfo.value.push(device);
}
}
});
});
// 开始搜索蓝牙设备
uni.startBluetoothDevicesDiscovery({
success() {
console.log('开始搜索蓝牙设备...');
// 设置一个定时器,在一定时间后停止搜索
setTimeout(stopDiscovery, 10000);
},
fail(err) {
console.error('开始搜索失败', err);
}
});
},
fail() {
uni.showToast({
title: '请打开蓝牙',
icon: 'none'
});
}
});
}
function stopDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success() {
console.log('停止蓝牙嗅探成功');
},
fail(err) {
console.error('停止蓝牙嗅探失败', err);
}
});
}
// 地锁密码获取: 提取MAC地址的最后6位转换为十进制 , 加上520168 , 取结果的最后六位
function calculatePassword(mac : string) {
let lastSixHex = mac.slice(-6);
let hexToInt = parseInt(lastSixHex, 16);
let sum = hexToInt + 520168;
let finalPassword = ('000000' + sum).slice(-6);
return finalPassword;
}
let bluetoothInfo = ref({
deviceId: '',
serviceId: '',
characteristics: [],
devicesInfo: {} // 连接的蓝牙设备
});
function connectBluetooth( data : any ) {
bluetoothInfo.value = data;
bluetoothInfo.value.deviceId = data.deviceId;
uni.createBLEConnection({
deviceId : data.deviceId,
success() {
// 判断连接的状态和断开重新连接
checkSttusOrReconnect(data);
// 获取蓝牙的服务ServiceID
uni.getBLEDeviceServices({
deviceId : data.deviceId,
success(servicesRes : any) {
if(servicesRes.services.length > 0) {
bluetoothInfo.value.serviceId = servicesRes.services[0].uuid;
// 获取蓝牙的特征值characteristicId
uni.getBLEDeviceCharacteristics({
deviceId : data.deviceId,
serviceId : servicesRes.services[0].uuid,
success(resCharacter) {
if(resCharacter.characteristics.length > 0) {
bluetoothInfo.value.characteristics = resCharacter.characteristics;
// 接受通知
notify();
let macAddress = data.name.split('-')[1];
let password = calculatePassword(macAddress);
let bondCommand = `phbond${password}`;
sendMsg(bondCommand);
}
}
})
}
}
});
}
})
}
// 检查设备并重连
function checkSttusOrReconnect( data : any ) {
uni.onBLEConnectionStateChange(function (state) {
if (!state.connected) {
console.warn('与设备' + data.name + '的连接已断开');
if(breakCount.value < 2) {
reconnectionTimer = setTimeout(()=>{
connectBluetooth(bluetoothInfo.value);
},3000);
}
breakCount.value += 1;
} else {
console.log('与设备 ' + data.name + ' 的连接已建立');
if(reconnectionTimer) { clearTimeout(reconnectionTimer) }
breakCount.value = 0;
}
});
}
// 接收蓝牙端发送的通知
function notify() {
uni.notifyBLECharacteristicValueChange({
state: true,
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId : bluetoothInfo.value.deviceId,
serviceId: bluetoothInfo.value.serviceId,
characteristicId : bluetoothInfo.value.characteristics.find(item => item.properties.notify).uuid,
success() {
monitorMsg();
},
fail(res) {
console.log('不支持这个notify' + JSON.stringify(res))
}
})
}
function monitorMsg() {
uni.onBLECharacteristicValueChange((res : any) => {
console.log("发送过来的数据---------", res);
let resHex = ab2ascii(res.value)
console.log("转换后的简易数据----------",resHex);
});
}
let cmdText = ref('');
function sendControllCmd() {
console.log('发送的指令:' + cmdText.value);
sendMsg(cmdText.value);
}
// 向蓝牙写数据
function sendMsg( msg : string ) {
uni.writeBLECharacteristicValue({
deviceId : bluetoothInfo.value.deviceId,
serviceId: bluetoothInfo.value.serviceId,
characteristicId : bluetoothInfo.value.characteristics.find(item => item.properties.write).uuid,
value: asciiToArrayBuffer(msg) as any,
writeType: 'write',
success(res) {
console.log('消息发送成功', res)
},
fail(res) {
console.log('消息发送失败', res)
}
});
}
// 二进制转化工具
function ab2ascii(buffer : ArrayBuffer) {
var str = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit : any) {
return String.fromCharCode(bit);
}
)
return str.join('');
}
function asciiToArrayBuffer(str : string) {
if (!str) {
return new ArrayBuffer(0);
}
var buffer = new ArrayBuffer(str.length);
var bufView = new Uint8Array(buffer);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buffer;
}
</script>
<style scoped lang="scss">
.pageBox {
height: 100%;
width: 100%;
position: fixed;
}
</style>