前言:
前段时间 在公司开发一款APP 其中有一个需求对接某厂蓝牙电子秤(类似地磅秤、多蓝牙),第一次对接这种 开发中遇到好多问题、比如将plus.bluetooth.writeBLECharacteristicValue 指定指令字符串转二进制 ArrayBuffer、uni.notifyBLECharacteristicValueChange蓝牙返回指令值十六进制转换字符串等。需要的小伙伴 可以点个关注哈!
整理需求及应用场景:
其实我们做开发的 只要业务逻辑、需求理解等清楚了 剩下的就是码code的事情了、主要就是按照以下几步 基本这个需求就能完成了。
JS部分
data() {
return {
a:'',
va:'',
b:'',
vb:'',
lastValue:'', //最终棒棒称重量
temdeviceIds:[], //选择已连接设备 临时
deviceIds:[], //选择已连接设备
arrbuffer: undefined,
Loading: false,
bluetoothList: []
};
},
onLoad() {
this.InitBle()
},
onUnload() {
if (this.timer1) {
clearTimeout(this.timer1);
this.timer1 = null;
}
if (this.timer2) {
clearTimeout(this.timer2);
this.timer2 = null;
}
if (this.timer3) {
clearInterval(this.timer3);
this.timer3 = null;
}
},
1.首先初始化蓝牙
InitBle() {
let _this = this
uni.openBluetoothAdapter({ //初始化蓝牙模块
success(res) {
console.log('初始化蓝牙', res)
_this.SearchBle(); //2.0
},
fail(err) {
uni.showToast({
title: '请检查是否已开启蓝牙',
icon: 'none',
duration: 1500
})
}
})
},
2.搜索蓝牙设备
GetBleList() {
var _this = this;
uni.getBluetoothDevices({
success: function(res) {
var bluetoothArr = res.devices.filter(function(obj) {
return obj.name.indexOf('LEAP')!=-1 //此处是过滤自己想要连接的蓝牙设备
})
_this.bluetoothList = bluetoothArr //将蓝牙列表展示在页面中
},
fail: function() {
console.log("搜索蓝牙设备失败");
uni.showToast({
title: '搜索蓝牙设备失败或附件暂无开启的蓝牙设备',
icon: 'none',
duration: 2000
})
},
complete: function() {
uni.hideLoading()
_this.Loading = false
_this.StopSearchBle() //不管是否有搜索到蓝牙设备 都要在complete中关闭蓝牙 优化性能
}
})
},
3.停止蓝牙搜索
StopSearchBle() {
uni.stopBluetoothDevicesDiscovery({
success(res) {
console.log('停止搜索蓝牙', res)
}
})
},
4.找到蓝牙设备后 页面点击选择某个蓝牙设备(此需求是可以连接多个蓝牙设备)
goConnectBle(e, index) {
console.log('开始连接蓝牙', e, index)
this.Loading = true
let item = {
deviceId: e.deviceId,
name: e.name,
};
this.temdeviceIds.push(item); //临时已连接的蓝牙设备
this.deviceIds = this.uniqueFunc(this.temdeviceIds,'deviceId') //去重后的蓝牙设备
// 将点击蓝牙设备 存放数组遍历 用户设备可链接多蓝牙
uni.showLoading({
title:'正在连接中...'
})
//此处为重点 蓝牙连接 根据某一id连接设备,4.0
this.deviceIds.map(item=>{
this.ConnectBle(item)
})
},
5.去重方法
uniqueFunc(arr, uniId){
const res = new Map();
return arr.filter((item) => !res.has(item[uniId]) && res.set(item[uniId], 1));
}
6.选择连接蓝牙设备后相关特征信息
ConnectBle(item) {
var _this = this;
uni.createBLEConnection({
deviceId: item.deviceId, //设备id
success: (res) => {
uni.hideLoading()
_this.NoticeConnection(); //连接成功后,开始监听异常
_this.timer2 = setTimeout(() => { //加个延迟、目的是为了确保连接成功后,再获取服务列表--可以显示正在连接效果
_this.Loading = false
_this.timer3 = setInterval(()=>{
_this.GetServiceId(item.deviceId); //5.0 获取蓝牙uuid 会存在多个 具体根据某厂商文档告知
_this.writeTest(item) //写入指令 告知电子秤设备(一般某厂商在开发文档、说明文档告知)
},10000) //此处为模拟轮询 第一次会出现获取不到蓝牙特性信息、将值调整小一点即可获取
uni.showToast({
title: '连接成功',
icon: 'success',
duration: 800
})
}, 3000)
},
fail: function(err) {
if (err.errMsg == 'createBLEConnection:fail already connect') {
_this.Loading = false
uni.showToast({
title: '已有蓝牙连接',
duration: 1200
})
} else {
uni.showToast({
title: err.errMsg,
duration: 1200
})
}
},
complete: function() {
// console.log('蓝牙连接完成')
}
})
},
7.连接成功后,开始监听异常
NoticeConnection() {
var _this = this;
uni.onBLEConnectionStateChange((res) => {
console.log('开始监听蓝牙状态', res)
if (!res.connected) {
uni.showModal({
title: '提示',
content: '蓝牙已断开,请重新搜索重连!',
success(res) {}
})
}
})
},
8.获取蓝牙(5.0)设备的服务uuid(服务uuid可能有多个)
GetServiceId(deviceId) {
console.log('连接的蓝牙设备deviceId====', deviceId)
var _this = this
uni.getBLEDeviceServices({
deviceId,
success(res) {
_this.serviceid_list = res.services //蓝牙服务列表放在data里面只是备用
_this.serviceId = 'xxxxxxxxxxxxxxxxx' //这是用来监听蓝牙下发和接收的服务uuid 一般厂商提供说明文档有提到
_this.GetCharacteIdNotify(_this.serviceId,
deviceId) //6.0 获取第2个服务uuid的特征值 (关于获取第几个uuid服务,看蓝牙方面提供的协议
}
})
},
9.根据服务uuid获取蓝牙特征值,开始监听写入和接收
GetCharacteIdNotify(serviceId, deviceId) {
let _this = this
uni.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success(res) {
console.log('获取蓝牙特征值', res.characteristics)
_this.arrbuffer = res.characteristics
_this.writeId = res.characteristics[1].uuid //写入 id 此处根据实际情况写
_this.notifyId = res.characteristics[0].uuid //接收id 此处根据实际情况写
}
})
},
10.写入指令(按某厂商说明文档 给出指定指令)
writeTest(item) {
this.BleWrite('$xxxx#', item.deviceId)
}
11.向蓝牙写入数据 传递电子秤设备信息
BleWrite(instruction, deviceId) {
// 向蓝牙设备发送一个0x00的16进制数据
let _this = this
let serviceId = _this.serviceId
let characteristicId = _this.writeId
let msg = instruction
const buffer = new ArrayBuffer(msg.length)
const dataView = new DataView(buffer)
for (var i = 0; i < msg.length; i++) {
dataView.setUint8(i, msg.charAt(i).charCodeAt())
}
plus.bluetooth.writeBLECharacteristicValue({
deviceId, // 蓝牙设备 deviceId
serviceId, // 蓝牙服务uuid,即第二个uuid
characteristicId, // 蓝牙特征值的 (即 writeId)
value: buffer, // 这里的value是ArrayBuffer类型
success(res) {
_this.startNoticeBle(_this.notifyId,deviceId) //7.0,开始侦听数据
},
fail(err) {
console.log('写入数据失败', err)
uni.showToast({
icon: "none",
title: "请确保您的手机已连接设备",
duration: 3000
})
}
})
},
12.写入指令成功后,开始侦听蓝牙发送数据 处理接收
startNoticeBle(notifyId,deviceId) {
let _this = this
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
deviceId: deviceId,
serviceId: _this.serviceId,
characteristicId: notifyId,
success(res) {
setTimeout(function() {
uni.onBLECharacteristicValueChange((res) => {
_this.hexCharCodeToStr(_this.ab2hex(res.value)) // hexCharCodeToStr为蓝牙返回的是十六进制的值 此处要将十六进制值转换成字符串 ab2hex 为ArrayBuffer转16进度字符
})
}, 5) //延迟是为异步更好拿出数据
},
fail: function(err) {
console.log('开启监听失败', err)
}
})
},
13.ArrayBuffer转16进度字符串
ab2hex(buffer) {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
},
14.将十六进制转换字符串
hexCharCodeToStr(hexCharCodeStr) {
const _this = this
_this.lastValue = ''
var trimedStr = hexCharCodeStr.trim();
var reslutr = ''
var rawStr = trimedStr.substr(0, 2).toLowerCase() === "0x" ? trimedStr.substr(2) : trimedStr;
var len = rawStr.length;
if (len % 2 !== 0) {
alert("存在非法字符!");
return "";
}
var curCharCode;
var resultStr = [];
for (var i = 0; i < len; i = i + 2) {
curCharCode = parseInt(rawStr.substr(i, 2), 16);
resultStr.push(String.fromCharCode(curCharCode));
}
//设备的特征值 这里值的是设备号 和蓝牙没有关系
if(resultStr.join("").replace(',', '').indexOf('W')!=-1){
if(_this.a=='' || _this.a == resultStr.join("").replace(',', '').substring(0, 5)){
_this.a = resultStr.join("").replace(',', '').substring(0, 5)
_this.va = resultStr.join("").replace(',', '').substring(6, resultStr.length - 2)
}else if(_this.b=='' || _this.b== resultStr.join("").replace(',', '').substring(0, 5)){
_this.b = resultStr.join("").replace(',', '').substring(0, 5)
_this.vb = resultStr.join("").replace(',', '').substring(6, resultStr.length - 2)
}
}
console.log('电子秤称完整信息',resultStr.join("").replace(',',''))
_this.lastValue =Number(_this.va) + Number(_this.vb)
}
15.设备无法连接 需要重新获取
请调用 第一个方法 this.InitBle()
html部分
<template>
<view class="box">
<view class="content" v-if="Loading">
<view class="content_b" />
</view>
<view style="height: 100%;" v-else>
<view class="content" v-if="bluetoothList.length>0">
<view class="" v-if="bluetoothList && bluetoothList.lenght!=0">
<view class="ffass" v-for="(item,index) in bluetoothList" :key="index">
<view class="trycsd">
<view class="nmjer">
<image src="/static/image/xxx.png" mode="" class="mjqwex"></image>
</view>
<text>{{item.name}}</text>
</view>
<text class="fdstrd" @click="goConnectBle(item,index)">点击链接</text>
</view>
</view>
<view class="" v-show="lastValue">
当前重量:{{lastValue}} +kg
</view>
</view>
<view class="content" v-else style="margin: 300rpx auto;margin-bottom: 0;">
<view class="content_t">
<view class="common_txt" style="color: #000">
未发现附近的蓝牙设备
</view>
<view style="margin-bottom: 50rpx;" size="default" shape="circle" :hairLine="true"
class="common_txt" @click="ReSearchBle()">
重新扫描
</view>
</view>
<view class="content_b" />
</view>
</view>
</view>
</template>
css部分
<style lang="scss" scoped>
.content {
display: flex;
flex: 1;
height: 100%;
flex-direction: column;
.bluetooth_item {
border-bottom: 1rpx solid #ccc;
line-height: 100rpx;
padding: 0 50rpx;
display: flex;
justify-content: space-between;
}
}
.content_t {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
.txt {
color: red;
margin: 40rpx 0rpx 120rpx;
font-size: 34rpx;
}
}
.content_b {
flex: 1;
}
.common_txt {
color: indianred;
font-size: 34rpx;
}
.ffass{
width: 698rpx;
height: 180rpx;
background: #FFFFFF;
border-radius: 10rpx;
overflow: hidden;
margin: 30rpx auto;
display: flex;
justify-content: space-between;
align-items: center;
.nmjer{
width: 152rpx;
height: 152rpx;
background: #F9F9F9;
border-radius: 10rpx;
display: flex;
justify-content: center;
align-items: center;
margin-right: 30rpx;
margin-left: 14rpx;
.mjqwex{
width: 85rpx;
height: 85rpx;
}
}
.trycsd{
display: flex;
align-items: center;
}
.fdstrd{
display: inline-block;
height: 68rpx;
width: 196rpx;
background: #FFFFFF;
border-radius: 5rpx;
color: #5A9CFE;
font-size: 32rpx;
border: 1px solid #5A9CFE;
margin-right: 20rpx;
line-height: 68rpx;
text-align: center;
}
}
</style>