概况:手机APP发送16进制指令至蓝牙犬牌,犬牌响应返回结果,功能为计步,犬牌电量,版本号,系统时间,灯光颜色等。
步骤
- 打开蓝牙
- 连接设备
- 扩大传输MTU
- 获取蓝牙服务
- 获取蓝牙设备某个服务中所有特征值(characteristic)
- 当找到同时有读 写 订阅 权限的特征值时启用低功耗蓝牙设备特征值变化时的 notify功能
- 订阅特征值开启成功后写入指令
- 写入成功后读取二进制数据值(此时开启的notify功能就会监听并返回设备响应数据)
- 接收设备响应数据
注意事项
16进制指令严格按照蓝牙协议数据包结构发送,此项目关联的包结构为帧头、协议头、版本、命令、数据区长度、数据、校验crc、帧尾。各自转成相应的十六进制,通过arraybuff数组的形式发送。
此项目crc校验规则为从帧头到校验前,字节累加。打开电脑计算器16进制累加即可。
如下图,数据长度则表示为0x01, 0x00,0x01,0x01 最后一个0x01 为占位。
关于发送一次指令 uni.onBLECharacteristicValueChange监听两次的问题,api bug官方暂未解决,解决方法,加一个变量,发送指令前置空变量,api回调中赋值,判断第二次return掉。
checkBluetooth() {
let that = this
uni.openBluetoothAdapter({
success(res) { //蓝牙已打开
uni.startBluetoothDevicesDiscovery({ //开始搜寻附近的蓝牙外围设备
services: [],
success(res) {
setTimeout(() => {
uni.onBluetoothDeviceFound(function(ret) {
that.list = that.list.concat(ret.devices).filter(item=>item.name!="")
// 对搜索到的蓝牙设备去重
for(let i=0;i<that.list.length;i++) {
for(let j=i+1;j<that.list.length;j++) {
if(that.list[i].name==that.list[j].name) {
that.list.splice(j,1)
}
}
}
that.isLoading = false
if (that.inputValue == "") {
that.equipmentList = that.list
} else {
that.equipmentList = that.list.filter(item =>
item.name.indexOf(that.inputValue) > -1
)
}
})
},2000)
}
})
},
fail(err) {
if (err.errCode == "10001") {
// 蓝牙未打开 提示
that.openPhoneBluetooth()
}
}
})
},
// 连接设备
connect(item) {
let that = this
console.log('--item--',item)
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: item.deviceId,
success(res) {
that.deviceId = item.deviceId
that.isLink = true
that.updateBtnStatus()
// 扩大传输MTU
that.setMaxMTU()
//获取蓝牙服务 不加延时返回结果立即为空
that.getBLEDeviceServices(async slist => {
for (let s of slist) {
let bdc = await that.getBLEDeviceCharacteristics(s.uuid);
// 找到特征值
if (bdc.writeId && bdc.notifyId && bdc.readId) {
that.writeUUid = bdc.writeId
that.notifyUUid = bdc.notifyId
that.readUUid = bdc.readId
uni.notifyBLECharacteristicValueChange({ //启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值
deviceId:that.deviceId,
serviceId: s.uuid,
characteristicId: bdc.notifyId,
state: true,
success: (res) => {
that.onBLECharacteristicValueChange();
console.log("订阅特征值开启成功")
setTimeout(()=>{
that.orderType = 'vision'
that.writeBLECharacteristicValue(ORDERS.VisionInfo);
setTimeout(()=>{
that.orderType = 'systemtime'
that.writeBLECharacteristicValue(ORDERS.SystemTime)
},1500)
},3000)
},
fail: (err) => {
console.error(err)
}
})
// setTimeout(()=>{
// that.writeBLECharacteristicValue()
// },2000)
break;
}
}
});
},
fail(err) { //失败断开连接
that.closeBLEConnect(item.deviceId)
}
})
},
其他部分代码:
setMaxMTU() {
let _this = this
uni.setBLEMTU({
deviceId: _this.deviceId,
mtu: 200,
success(res) {
console.log("设置最大值成功")
}
})
},
//获取蓝牙设备所有服务(service)
getBLEDeviceServices(callback) {
let _serviceList = [];
let deviceId = this.deviceId || uni.getStorageSync("deviceId").toString();
setTimeout(() => { //解决app不能得到服务的bug,500-1000
uni.getBLEDeviceServices({
deviceId,
success: res => {
for (let service of res.services) {
// console.log(service)
if (service.isPrimary) {
_serviceList.push(service);
}
}
console.log("_serviceList: ",_serviceList);
if (typeof callback == "function") callback(_serviceList)
},
fail: err => {
console.log(err)
}
});
}, 1500);
},
//获取蓝牙设备某个服务中所有特征值(characteristic)
getBLEDeviceCharacteristics(serviceId) {
let deviceId = this.deviceId;
this.serviceUUID = serviceId;
// console.log('serviceId', serviceId);
//缓存服务Id
uni.setStorageSync('serviceId', serviceId);
let readId="", writeId="", notifyId="", indicateId = ""
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId:deviceId,
serviceId:serviceId,
success: res => {
for (let _obj of res.characteristics) {
//获取readId
if (_obj.properties.read&&readId=="") {
readId = _obj.uuid;
}
if (_obj.properties.write&&writeId=="") {
writeId = _obj.uuid;
}
uni.setStorageSync('writeId', writeId);
//获取notifyId
if (_obj.properties.notify&¬ifyId=="") {
notifyId = _obj.uuid;
}
//获取indicateId
if (_obj.properties.indicate&&indicateId=="") {
indicateId = _obj.uuid;
}
uni.setStorageSync('indicateId', indicateId);
}
let result = {
'readId': readId,
'writeId': writeId,
'notifyId': notifyId,
'indicateId': indicateId
};
console.log('获取AAAA:', JSON.stringify(result));
resolve(result)
},
fail: err => {
reject(err);
}
})
});
},
hexStringToArrayBuffer(str) {
if (!str) {
return new ArrayBuffer(0);
}
var buffer = new ArrayBuffer(str.length);
let dataView = new DataView(buffer)
let ind = 0;
for (var i = 0, len = str.length; i < len; i += 2) {
let code = parseInt(str.substr(i, 2), 16)
dataView.setUint8(ind, code)
ind++
}
return buffer;
},
//写入指令
writeBLECharacteristicValue(arrayBuffer) {
let that = this
let deviceId = that.deviceId;
let serviceId = that.serviceUUID;
// let serviceId = '00010203-0405-0607-0809-0A0B0C0D1910';
let characteristicId = that.writeUUid;
const buffer = new ArrayBuffer(arrayBuffer.length) //ArrayBuffer是字节数组
const dataView = new DataView(buffer) //通过DataView对象来操作字节数组
for (let i=0;i<arrayBuffer.length;i++){
dataView.setUint8(i, arrayBuffer[i]);
}
uni.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value: buffer,
success(res) {
console.log('发送指令成功:' + arrayBuffer);
that.bugServiceId = ''
that.readBLECharacteristicValue();
},
fail(err) {
console.log('发送指令失败', JSON.stringify(err));
//self.showToast("Sending failure", 800);
}
});
},
//读取设备的特征值的二进制数据值
readBLECharacteristicValue(){
let that = this
uni.readBLECharacteristicValue({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:that.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId:that.serviceUUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId:that.readUUid,
success(res) {
console.log('----读取----:', res)
}
})
},
// 监听低功耗蓝牙设备的特征值变化事件 接收到设备推送的 notification
onBLECharacteristicValueChange() {
let that = this
uni.onBLECharacteristicValueChange(res => {
// 去除每个指令二次空监听,此为api bug
if(that.bugServiceId) return false
that.bugServiceId = res.serviceId
if(that.orderType== 'quanpaidianliang'){ //犬牌电量
let data = that.ab2hex(res.value);
let num = data.slice(-6,-4)
let dianliang = that.hex2int(num)
that.$set(that.btnList,1,{
btn_name: '犬牌电量' + dianliang + '%',
isDisabled: false
})
console.log('返回',dianliang);
}else if(that.orderType == 'vision'){ //版本号
that.ab2str(res.value,'vision')
}else if(that.orderType == 'systemtime'){ //系统时间同步
let sTime = that.ab2hex(res.value,'systemtime')
console.log('返回',sTime);
if(sTime){
plus.nativeUI.toast("时间同步成功");
}
}else if (that.orderType == 'StepCounting'){
let walks = that.ab2hex(res.value,'StepCounting')
console.log('返回计步',walks);
let useNums = walks.toString().slice(12,-4)
let oneDay = useNums.slice(0,8)/1000000 //今天
let twoDay = useNums.slice(8,16)/1000000 //昨天
let threeDay = useNums.slice(16,24)/1000000 //前天
console.log('66',oneDay,twoDay,threeDay);
uni.$emit('walks', {
'oneDay':oneDay,
'twoDay':twoDay,
'threeDay':threeDay,
});
}
})
},
// 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('')
},
hex2int(hex) { //16进制转10进制
var len = hex.length, a = new Array(len), code;
for (var i = 0; i < len; i++) {
code = hex.charCodeAt(i);
if (48<=code && code < 58) {
code -= 48;
} else {
code = (code & 0xdf) - 65 + 10;
}
a[i] = code;
}
return a.reduce(function(acc, c) {
acc = 16 * acc + c;
return acc;
}, 0);
},