<template>
<view class="content">
<button type="default" @click="initialize">初始化</button>
<button type="default" @click="searchBlue">搜索</button>
<view class="list">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item, index) in dataList" :key="item.deviceId">
<view>{{item.name}}</view>---
<view>{{item.deviceId}}</view>---
<view>{{item.RSSI}}</view>
<button type="default" @click="lianjie(item.deviceId)" v-if="lanya.deviceId!==item.deviceId">连接</button>
<button type="warn" @click="closelanya(item.deviceId)" v-if="lanya.deviceId==item.deviceId">断开</button>
</view>
</view>
<button type="default" @click="communication">通讯</button>
<h3>服务值</h3>
<view class="list1">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item1, index1) in service" :key="item1.uuid">
<view>{{item1.uuid}}</view>
<view>是否主要{{item1.isPrimary}}</view>
<button type="default" @click="choose(item1.uuid)" v-if="serviceuuid!==item1.uuid">选择</button>
<button type="warn" @click="choose()" v-if="serviceuuid==item1.uuid">清空</button>
</view>
</view>
<h3>特征值</h3>
<view class="list1">
<!-- 注意事项: 不能使用 index 作为 key 的唯一标识 -->
<view v-for="(item1, index1) in characteristic" :key="item1.uuid">
<view>{{item1.uuid}}</view>
<view>可监听{{item1.properties.notify}}</view>
<view>可写{{item1.properties.write}}</view>
<view>可读{{item1.properties.read}}</view>
<button type="default" @click="monitor(item1.uuid)"
v-if="characteristicId!==item1.uuid&&item1.properties.notify==true">监听</button>
<button type="default" v-if="item1.properties.read==true" @click="readData(item1.uuid)">读取数据</button>
<button type="default" v-if="item1.properties.write==true" @click="open(item1.uuid)">写入数据</button>
</view>
</view>
<view v-for="(item, index) in stringArr" :key="index"
style="display:flex;padding: 10rpx;box-sizing: border-box;width: 100%;border-bottom: 1rpx solid #000;justify-content: center;">
<text
style="width:90%;display:inline-block;white-space: pre-wrap; word-wrap: break-word;height: auto;">{{item}}</text>
</view>
<u-popup :show="show" @close="close" mode="center">
<view style="padding: 20rpx;">
<view>{{characteristicId1}}</view>
<input placeholder="请输入数据" border="surround" v-model="writeDataValue"></input>
<button @click="writeData()">确定</button>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
dataList: [],
lanya: {},
service: [],
characteristic: [],
stringArr: [],
serviceuuid: undefined,
characteristicId: undefined,
characteristicId1: undefined,
show: false,
writeDataValue: "BB9AA90CEE",
dataObject: {}
}
},
onLoad() {
this.initialize();
},
methods: {
open(e) {
this.show = true;
this.characteristicId1 = e;
// console.log('open');
},
close() {
this.show = false;
this.characteristicId1 = undefined;
// this.writeDataValue = undefined
// console.log('close');
},
//初始化
initialize() {
uni.openBluetoothAdapter({
// 蓝牙初始化成功执行
success: (res) => {
// 这里成功之后就不用管了,直接执行就行
uni.showToast({
title: "初始化成功!",
icon: "none",
duration: 2000,
});
},
// 蓝牙初始化失败执行
fail: (err) => {
// 初始化失败之后有需求的话可以进行一些处理
// 没有需求的也不用管
// 一般情况下,还是需要分析一下原因的,这用用户和自己就知道为什么失败了
if (err.errCode == 10001) {
// 这种情况是未打开蓝牙 就需要提示用户检测蓝牙并打开
uni.showToast({
title: "请检查蓝牙是否开启!",
icon: "none",
duration: 2000,
});
}
// 我这里只演示这一个,其他的状态可进入官网进行查看
// uni-app https://uniapp.dcloud.io/api/system/bluetooth.html
// 微信原生 https://developers.weixin.qq.com/miniprogram/dev/api/device/bluetooth/wx.openBluetoothAdapter.html
}
})
},
//搜索蓝牙
searchBlue() {
uni.startBluetoothDevicesDiscovery({
success: () => {
// 调用成功之后就开始查询附近蓝牙了
// 成功之后可调用这个函数,每次查询到新的蓝牙设备就会调用
// 这个函数使用不使用都可以,不影响查询的结果
uni.onBluetoothDeviceFound((devices) => {
console.log('蓝牙', devices) // 蓝牙设备信息
// 返回的数据是ArrayBuffer格式的需要进行转换,不然咱也看不懂都是些啥
// ArrayBuffer 的转换后面会详细写出来
})
setTimeout(() => {
uni.getBluetoothDevices({
success: (res) => {
// res.devices 为 查询到的蓝牙设备列表
// 拿到结果之后,进行处理
// 首先进行过滤一下查询的列表,会出现很多未知设备这类的,这里就直接把它们给排除掉了,只留下有完整名称的设备
var arr = []
res.devices.forEach((element) => {
if (element.name !== "位置设备") {
arr.push(element);
}
})
// 筛选之后在进行判断是否有正常的蓝牙设备
// 当然你也可以查看一下被筛选掉的设备是否有你所使用的设备,如果有你可以去掉筛选 或者自己定义筛选条件
if (arr.length == 0) {
uni.showToast({
title: "未查询到可用设备,请重新扫描",
duration: 1000,
icon: "none",
});
}
// 最后这个arr就是所查到的列表
let arr1 = []
arr.map((i) => {
if (i.name) {
arr1.push(i);
}
})
this.dataList = arr1
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
// 不管查没查到一定要使用 stopBluetoothDevicesDiscovery 停止查询
}
})
}, 2000)
}
})
},
// 关闭蓝牙搜索
stopBluetoothDevicesDiscovery() {
uni.stopBluetoothDevicesDiscovery({
success: e => {
console.log('停止搜索蓝牙设备:' + e);
},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e);
}
});
},
//连接蓝牙
lianjie(e) {
console.log('连接', e);
// 为了能否顺利的连接蓝牙 可以先查询一下是否有设备已经连接蓝牙了
// 手环这一类的设备 可能会对程序造成干扰 会一直显示设备已经连接
uni.getConnectedBluetoothDevices({
success: (res) => {
// 因为我为了蓝牙的连接的稳定性,就做了这一步
// 大家使用的过程中可以省略这一步直接进行蓝牙设备
// 但是不确定是否可以正常进行蓝牙连接, 大家可以尝试一下
// 如果返回的列表不等于空,就代表已经有设备连接
if (res.devices.length !== 0) {
// 这里就需要提示用户蓝牙已连接
uni.showModal({
title: "提示!",
content: "当前蓝牙已于id为" + res.devices[0].deviceId + "的设备连接,是否断开连接",
success: (row) => {
// 用户点击确定执行
if (row.confirm) {
// 用户点击确定之后把当前连接的蓝牙断开
uni.closeBLEConnection({
// 一定要传入当前连接蓝牙的deviceId
deviceId: res.devices[0].deviceId,
success: () => {
// 到这里就已经断开成功了,再次点击就可以进行连接了
uni.showToast({
title: "连接已断开!",
icon: "none",
duration: 2000,
});
this.lanya = {}
this.service = []
this.characteristic = []
this.serviceuuid = undefined;
this.characteristicId = undefined;
this.stringArr = []
},
fail: (err) => {
// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
uni.showToast({
title: err.errMsg,
icon: "none",
duration: 2000,
});
}
})
} else {
// 用户取消之后不需要做任何操作
console.log('用户点击了取消')
}
}
});
} else {
// 当前未处于已连接状态就开始进行连接,没有连接的情况下就可以直接连接蓝牙
uni.createBLEConnection({
// 连接的时候一定传入要连接蓝牙的deviceId
deviceId: e,
// 这里可以选择设置一个延迟,如果延迟时间过了没有连接成功就直接提示报错
timeout: 5000,
success: (res) => {
// 连接成功之后可以再次进行查询一下当前连接的蓝牙信息,保存下载,后面方便使用
uni.getConnectedBluetoothDevices({
success: (devicess) => {
// devicess就是当前已经连接的蓝牙列表
// 在这判断一下有没有蓝牙,如果有就证明确实已经连接成功
if (devicess.devices[0]) {
// 到这里就可以提示用户成功,并且吧蓝牙信息保存下来方便后面使用
console.log('连接成功', devicess.devices[
0]) // 蓝牙信息
uni.showToast({
title: "连接成功!",
icon: "none",
duration: 2000,
});
this.lanya = devicess.devices[
0]
this.stopBluetoothDevicesDiscovery();
} else {
// 如果走这里面就是没有蓝牙设备,就要查看一下,看看是否真的连接成功了
}
}
})
},
fail: (err) => {
// 在这里查看连接失败的信息,判断为什么连接失败
console.log(err)
}
})
}
}
})
},
//断开蓝牙
closelanya() {
uni.closeBLEConnection({
// 一定要传入当前连接蓝牙的deviceId
deviceId: this.lanya.deviceId,
success: () => {
// 到这里就已经断开成功了,再次点击就可以进行连接了
uni.showToast({
title: "连接已断开!",
icon: "none",
duration: 2000,
});
this.lanya = {}
this.service = []
this.characteristic = []
this.serviceuuid = undefined;
this.characteristicId = undefined;
this.stringArr = []
},
fail: (err) => {
// 走到这就是断开失败了,可以进行操作提示用户或者自己查看
uni.showToast({
title: err.errMsg,
icon: "none",
duration: 2000,
});
}
})
},
//服务值获取
communication() {
// 蓝牙接收数据主要使用的api是开启监听(uni.notifyBLECharacteristicValueChange)
// 但是开启监听是需要几个特殊的值才能开启
// 所以开启之前我们需要获取这个值
// deviceId 蓝牙deviceId,蓝牙信息中包含的有
// serviceId 蓝牙服务值,根据蓝牙deviceId获取
// characteristicId 蓝牙特征值 根据serviceId 获取
// 首先根据deviceId 获取到服务值 serviceId
uni.getBLEDeviceServices({
deviceId: this.lanya.deviceId, // 获取服务值,需要传入deviceId
success: (res) => {
// res.services 返回一个数组,里面包含着支持不同的通讯方式的serviceId 一般为三个左右,也有可能更多
console.log('服务值', res.services)
this.service = res.services
// 拿到之后根据自己所需要的去保存serviceId,在后面使用
// 这里建议多试试,说不定那个可以用,又或者某个不能用
if (res.services.length <= 0) {
this.communication();
} else {
// this.choose(res.services[res.services.length - 2].uuid);
// this.choose(res.services[0].uuid);
}
},
fial: (err) => {
// 一般来说只要 deviceId 对,就不会报错
}
})
},
//选择服务值
choose(e) {
this.characteristic = []
this.stringArr = []
this.characteristicId = undefined
this.serviceuuid = e;
this.characteristicget();
},
//特征值获取
characteristicget() {
//选第4个
// 获取到之后就可以去拿着获取到的serviceId和deviceId去获取特征值
uni.getBLEDeviceCharacteristics({
deviceId: this.lanya.deviceId, // 传入获取到的deviceId
serviceId: this.serviceuuid, // 传入获取到的serviceuuid
success: (ress) => {
// ress里面就是获取到的蓝牙特征值
// 注意:根据传入serviceuuid的不同获取到的特征值也是不一样的,
// 特征值分为,可读、可写、可通知等几个类型的,根据自己想要的操作选择特征值
console.log('特征值', ress)
this.characteristic = ress.characteristics
// for (var i = 0; i < ress.characteristics.length; i++) {
// var model = ress.characteristics[i];
// if (model.properties.notify == true) {
// this.monitor(model.uuid);
// }
// }
},
fial: (err) => {
// 一般来说只要参数对,就不会报错
}
})
},
//监听
monitor(e) {
//选第1个
// 这里所声明介绍一下所用到的东西
// deviceId 就是上面蓝牙设备的deviceId
// serviceuuid 就是上面根据蓝牙设备获取到的serviceuuid
// characteristics 就是上面根据 deviceId 和 serviceuuid 获取到的
// characteristics 是一个数组里面包含着多个特征值, 根据使用去拿响应的特征值
// 我这里没有直接使用,而是进行一个循环判断,判断这么多的特征值中那个是符合要求的
// var characteristicId;
// var i = 0
// then.characteristiclist.forEach((element) => {
// if (element.properties.notify == true) {
// if (i == 0) {
// characteristicId = element.uuid;
// i++;
// }
// }
// });
// 为什么循环
// 因为开启uni.notifyBLECharacteristicValueChange需要特征值是需要固定的,我就直接判断写入了
let that = this
uni.notifyBLECharacteristicValueChange({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: e,
state: true,
success: () => {
that.characteristicId = e
console.log('监听启动成功');
this.stringArr = []
// 启用成功之后就可以在uni.onBLECharacteristicValueChange 中获取到蓝牙设备发送的数据
that.rxd()
},
});
},
//数据接受
rxd() {
console.log('监听返回');
let that = this
let str1 = '';
uni.onBLECharacteristicValueChange((res) => {
console.log('接收数据', res.value)
// 这就是蓝牙发送的数据 但是现在的数据,还不能直观的看出来是什么,还需要进行一些列转换才能直观查看
// ArrayBufer转16进制
console.log('ArrayBufer转16进制', that.buf2hex(res.value))
let str = that.buf2hex(res.value);
if (str.substr(0, 2) == 'bb' && str.endsWith('ee')) {
this.strToObj(that.strAddSpace(str))
that.stringArr.push(JSON.stringify(this.dataObject));
console.log(this.dataObject);
} else {
if (str.substr(0, 2) == 'bb') {
str1 = str;
} else {
str1 += str;
if (str1.substr(0, 2) == 'bb' && str1.endsWith('ee')) {
this.strToObj(that.strAddSpace(str1))
that.stringArr.push(JSON.stringify(this.dataObject));
str1 = '';
}
}
}
//16进制转字符串
// console.log('ArrayBufer转字符串',that.hexToString(that.buf2hex(res.value)))
// if (that.hexToString(that.buf2hex(res.value))) {
// that.stringArr.push(that.hexToString(that.buf2hex(res.value)))
// }
//16进制转字符串处理中文乱码
// console.log('ArrayBufer转字符串处理乱码',that.utf8to16(that.hexToString(that.buf2hex(res.value))))
// if (that.utf8to16(that.hexToString(that.buf2hex(res.value)))) {
// that.stringArr.push(that.utf8to16(that.hexToString(that.buf2hex(res.value))))
// }
//ArrayBufer直接转字符串
// console.log('ArrayBufer直接转字符串',that.utf8to16(that.buf2str(res.value)));
// if (that.utf8to16(that.buf2str(res.value))) {
// that.stringArr.push(that.utf8to16(that.buf2str(res.value)))
// }
// 经过 this.buf2str 转换之后就可以直观查看 蓝牙返回的信息
});
},
// 读取数据
readData(e) {
//选第3个
let that = this
uni.readBLECharacteristicValue({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: e,
success(res) {
console.log('读取数据:', res.errCode)
}
})
},
//写入数据
writeData() {
//选第2个
// 所用到的就是上面获取到的
// 但是特征值跟上面不一样,需要支持可写
// var characteristicId;
// var i = 0;
// then.characteristiclist.forEach((element) => {
// if (element.properties.write == true) {
// if (i == 0) {
// characteristicId = element.uuid;
// i++;
// }
// }
// });
let that = this
// console.log(that.writeDataValue);
// console.log(that.hexToString(that.writeDataValue));
// console.log(that.strToHexCharCode(that.writeDataValue));
// console.log(that.hex2buf(that.strToHexCharCode(that.writeDataValue)));
console.log('12进制转arraybuffer', that.hex2buf(that.writeDataValue));
uni.writeBLECharacteristicValue({
deviceId: that.lanya.deviceId,
serviceId: that.serviceuuid,
characteristicId: that.characteristicId1,
value: that.hex2buf(that.writeDataValue),
// value: that.hex2buf(that.strToHexCharCode(that.writeDataValue)),
// writeType: "write",
success: (res) => {
console.log('指令写入成功');
console.log(res);
that.show = false
that.characteristicId1 = undefined;
// that.writeDataValue = undefined
uni.showToast({
title: "指令写入成功",
icon: "none",
duration: 2000,
});
// 写入成功之后在onBLECharacteristicValueChange里面应该是有反馈的
that.rxd()
},
fail(err) {
console.log(err);
uni.showToast({
title: err.message,
icon: "none",
duration: 2000,
});
}
})
// this.string2buffer 把16进制字符串转换成buffer之后进行写入,我也忘了直接传入字符串可不可以使用,应该是不行,可以尝试一下,不行就按着我的来也行
},
//蓝牙状态变化处理
statesChange() {
uni.onBLEConnectionStateChange(res => {
// 该方法回调中可以用于处理连接意外断开等异常情况
console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`);
});
},
// arraybuffer类型转16进制字符串
buf2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
console.log('arraybuffer类型转16进制', hexArr.join(''));
return hexArr.join('')
},
// 16进制转arraybuffer类型
hex2buf(e) {
var hex = e
var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16)
}))
var buffer = typedArray.buffer
return buffer;
},
// 16进制转字符串
hexToString(hexCharCodeStr) {
var trimedStr = hexCharCodeStr.trim();
var rawStr =
trimedStr.substr(0, 2).toLowerCase() === "0x" ?
trimedStr.substr(2) :
trimedStr;
var len = rawStr.length;
if (len % 2 !== 0) {
console.log("非法格式ASCII码!");
return "";
}
var curCharCode;
var resultStr = [];
for (var i = 0; i < len; i = i + 2) {
curCharCode = parseInt(rawStr.substr(i, 2), 16); // ASCII Code Value
resultStr.push(String.fromCharCode(curCharCode));
}
return resultStr.join("");
},
//字符串转16进制
strToHexCharCode(str) {
if (str === "")
return "";
var hexCharCode = [];
hexCharCode.push("0x");
for (var i = 0; i < str.length; i++) {
hexCharCode.push((str.charCodeAt(i)).toString(16));
}
return hexCharCode.join("");
},
//处理中文乱码问题
utf8to16(str) {
// console.log(str);
var out, i, len, c;
var char2, char3;
out = "";
len = str.length;
i = 0;
while (i < len) {
c = str.charCodeAt(i++);
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += str.charAt(i - 1);
break;
case 12:
case 13:
char2 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
char2 = str.charCodeAt(i++);
char3 = str.charCodeAt(i++);
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
console.log(out, 'out')
return out;
},
// 返回数据加分隔符
strAddSpace(e) {
var data = e;
var result = "";
for (let i = 0; i < data.length; i++) {
result += data[i];
if (i % 2 === 1) result += ',';
}
let str = result.substring(0, result.lastIndexOf(','));
console.log('返回数据加分隔符', str); // 12,54,85,63,32,11,11,22,kl,kl,hh
console.log(this.checkEnd(str));
if (this.checkEnd(str)) {
return str
}
},
//低位校验
checkEnd(str) {
let str1 = str.split(',').slice(0, -2)
let itotal = 0
for (var i = 0; i < str1.length; i++) {
//将数据源转化为十六进制
itotal += Number(parseInt(str1[i], 16).toString(10))
}
if ((itotal % 100) === Number(str.split(',').slice(-2, -1).toString())) {
return true
} else {
return false
}
},
//数据字符串转对象
strToObj(e) {
let str = e.split(',').slice(1, -2);
let arr = [];
let arr1 = [];
for (let i = str.length - 1; i >= 0; i--) {
// 判断是否为数值
if (!isNaN(parseFloat(str[i])) && isFinite(str[i])) {
arr1.push(str[i]);
if (i <= 0) {
arr.push(arr1);
} else {
if (!(!isNaN(parseFloat(str[i - 1])) && isFinite(str[i] - 1))) {
arr.push(arr1);
arr1 = [];
}
}
} else {
arr1.unshift(str[i]);
}
}
let obj = {};
arr.map((m) => {
let data = m.slice(1).reverse().toString().replace(",", "");
obj[m[0]] = data;
})
if (JSON.stringify(this.dataObject) == "{}") {
this.dataObject = obj
} else {
this.dataObject = {
...this.dataObject,
...obj
}
}
// for (var i in obj) {
// this.dataObject[i] = obj[i]
// }
},
},
}
</script>
<style scoped lang="scss">
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.list {
padding: 20rpx;
view {
display: flex;
justify-content: space-around;
}
}
</style>
各种品牌的手机可能服务值与通讯值有差别,app上线后遇到过好几次连接上没有数据的问题,还有几台vivo手机连接不上,因为手上没有比照,也不知道是什么原因