前言:该项目流程是通过扫设备上的二维码(获取设备deviceId和蓝牙锁设备信息)建立蓝牙连接后再开锁(与设备通信)。注意:①与蓝牙的通信遵循第三方通信协议(数据写入和接受这块)②发送/接受的蓝牙数据需要进行加解密处理(aes-128加密和解密)③数据类型之间的转化
参考文章:uniapp微信小程序蓝牙连接与设备数据对接_uniapp小程序蓝牙配网-CSDN博客
一:蓝牙通信板块(首先通过扫码获取到信息,然后调用蓝牙相关api进行蓝牙的通信)
1:初始化蓝牙并连接
/**
* 获取手机蓝牙是否打开(初始化蓝牙)
*/
getBluetoothState() {
let that = this
// 主机模式
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
mode: 'central',
success: (r) => {
console.log("蓝牙初始化成功");
// 获取蓝牙的匹配状态
uni.getBluetoothAdapterState({
success: function(row) {
console.log('蓝牙状态:', row.available);
if (row.available) {
that.bluetoothOpen = true;
resolve();
} else {
// 请开启蓝牙
that.bluetoothOpen = false;
reject();
}
},
fail: function(err) {
// 请开启蓝牙
that.bluetoothOpen = false;
reject();
}
})
},
fail: (err) => {
// 请开启蓝牙
if (err.errCode === 10001) {
uni.showModal({
title: '提示',
content: '请先打开蓝牙',
showCancel: false,
success: function(res) {}
});
// 监听蓝牙适配器状态变化事件
uni.onBluetoothAdapterStateChange(res => {
console.log('监听蓝牙适配器状态变化事件', res);
if (res.available) {
// 重新连接扫码的设备
that.bluetoothOpen = true
that.connectBluetooth()
} else {
that.bluetoothOpen = false;
}
});
}
reject();
}
});
});
},
/**
* 连接蓝牙
* deviceId 蓝牙设备id
*/
connectBluetooth() {
uni.showLoading({
title: '正在连接蓝牙...'
});
let that = this
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId: this.device_st_id, // 设备id
success() {
console.log("蓝牙连接成功");
that.bluetoothOpen = true
that.bluetoothConnect = true;
resolve();
// 获取服务id
that.getServiceId();
},
fail(err) {
that.bluetoothConnect = false;
console.log("蓝牙连接失败", err);
reject();
},
complete() {
uni.hideLoading()
}
});
});
},
2:获取蓝牙的serviceId后调用蓝牙监听和写入功能
// 获取服务id
getServiceId() {
let that = this
uni.getBLEDeviceServices({
deviceId: this.device_st_id,
success(res) {
console.log("获取服务Id", res)
let model = res.services[0];
that.serviceId = model.uuid;
// 调用蓝牙监听和写入功能
that.getCharacteId();
}
})
},
// 获取蓝牙低功耗设备某个服务中所有特征
getCharacteId() {
let that = this
uni.getBLEDeviceCharacteristics({
deviceId: this.device_st_id, // 蓝牙设备id
serviceId: this.serviceId, // 蓝牙服务UUID
success(res) {
console.log('数据监听', res);
res.characteristics.forEach(item => {
// 003
if (item.properties.notify === true) {
// 监听
let notify = item.uuid;
that.startNotice(notify);
}
// 002
if (item.properties.write === true) {
// 写入
let writeId = item.uuid;
uni.setStorageSync("writeId", item.uuid);
}
});
},
fail(err) {
console.log("数据监听失败", err)
}
})
},
// 启用低功耗蓝牙设备特征值变化时的notify功能
startNotice(notify) {
let that = this
uni.notifyBLECharacteristicValueChange({
characteristicId: notify,
deviceId: this.device_st_id,
serviceId: this.serviceId,
state: true,
success(res) {
console.log("启用低功耗蓝牙设备特征值变化时的notify功能", res);
// 监听低功耗蓝牙设备的特征值变化
uni.onBLECharacteristicValueChange(result => {
console.log("监听低功耗蓝牙设备的特征值变化", result);
//返回的result.value为arrayBuffer格式
if (result.value) {
var arrint82 = new Int8Array(result.value); //将返回的结果转化为一个 8 位有符号整数的数组(int8数组)
var edata2 = bluetooth.aesDecrypt(arrint82); // 解密后的int8数组
// bluetooth.ab2hex()将数组转为16进制字符串
var ab2hexSubStr = bluetooth.ab2hex(edata2).substring(
6, 12)
if (ab2hexSubStr == '000000') {
uni.showToast({
title: '成功',
icon: 'success',
duration: 2000
});
setTimeout(() => {
uni.switchTab({
url: "/pages/index/index"
})
}, 2000)
} else if (ab2hexSubStr == '100000') {
uni.showToast({
title: '开锁异常',
icon: 'error',
duration: 2000
});
setTimeout(() => {
uni.navigateBack()
}, 2000)
}
//unlockingKey为用于开蓝牙锁的通信帧(用于开蓝牙锁),结构如下
// 固定 + 出厂默认密码 + 锁位 + token (058109 PWD[6] CHM[3] token[4])
var unlockingKey = '058109' + '0000000' + that.lockNum + bluetooth.ab2hex(edata2).substring(
6, 14)const buffer = bluetooth.processData(unlockingKey);
setTimeout(function() {
uni.hideLoading();
}, 1000);
that.writeData(buffer) //开锁
}
})
}
});
},
// 向蓝牙发送数据
writeData(buffer) {
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
characteristicId: uni.getStorageSync("writeId"),
deviceId: this.device_st_id,
serviceId: this.serviceId,
value: buffer, //蓝牙设备特征值对应的二进制值
success(res) {
console.log("writeBLECharacteristicValue success", res);
resolve();
},
fail(err) {
console.log("报错了", err);
reject();
}
});
});
},
// 开锁
writeBlueData() {
if ((this.bluetoothOpen == false && this.bluetoothConnect == false) || this.bluetoothConnect == false) {
uni.showToast({
title: '开锁异常,请重新扫码',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack()
}, 1500)
return
} else {
uni.showLoading({
title: '加载中...'
});
}
bluetooth.setBleKeyVariables(this.aesKey);
const buffer = bluetooth.processData(this.hexStr);
this.writeData(buffer)
},
二:加解密处理板块aes-128 (aes.js)
可以参考该文章:uniappAES加密方法_uniapp aes解密-CSDN博客
三:数据类型转化以及加解密使用(封装好的js)
let fun_aes = require('../utils/aes.js');
let keyBv = null;
let keyWA = null;
// 加密后转换为二进制类型通信帧
const processData = (hexStr) => {
var typedArray = hexStrToUint8Array(hexStr)
var arrint8 = new Int8Array(typedArray.buffer);
var edata = aesEncrypt(arrint8);
// const buffer = Buffer.from(ab2hex(edata), 'hex').buffer;//小程序中不适用该方式获取buffer对象
var hexString=ab2hex(edata)
const buffer = hexStrToUint8Array(hexString).buffer
return buffer;
};
// 加解密/数据处理 用到的相关函数start
// 将一个十六进制字符串 hexStr 转换为一个 Uint8Array(8 位无符号整数类型的数组)
const hexStrToUint8Array = (hexStr) => {
var typedArray = new Uint8Array(hexStr.match(/[\da-f]{2}/gi).map(function(h) {
return parseInt(h, 16);
}))
return typedArray
};
// 十六进制字符串转成十进制数组
const hexStringToDecimalArray = (hexString) => {
return hexString.match(/.{2}/g).map(function(hexVal) {
return parseInt(hexVal, 16);
});
};
// buffer 数据转换为十六进制字符串。
const ab2hex = (buffer) => {
const hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
};
// 设置加解密所需的keyWA值
const setBleKeyVariables = (akey) => {
keyBv = new Int8Array(hexStringToDecimalArray(akey));
keyWA = fun_aes.CryptoJS.enc.int8array.parse(keyBv);
};
// 解密
const aesDecrypt = function(array) {
var that = this;
var acontent = array;
// 将密文转换成WordArray
var contentWA = fun_aes.CryptoJS.enc.int8array.parse(acontent);
// console.log('Decrypt contentWA is ' + contentWA);
// 插件要求密文是base64格式
var dcBase64String = contentWA.toString(fun_aes.CryptoJS.enc.Base64);
// console.log('Decrypt dcBase64String is ' + dcBase64String);
// 解密 选定mode是CFB类型,无偏移量
var decrypted = fun_aes.CryptoJS.AES.decrypt(dcBase64String, keyWA, {
iv: [],
mode: fun_aes.CryptoJS.mode.ECB,
padding: fun_aes.CryptoJS.pad.NoPadding
});
// console.log('Decrypt decrypted is ' + decrypted);
// 将解密后的明文转回int8数组
var bv = fun_aes.CryptoJS.enc.int8array.stringify(decrypted);
return bv;
};
// 加密
const aesEncrypt = function(array) {
var that = this;
var acontent = array;
// 将明文转换成WordArray
var contentWA = fun_aes.CryptoJS.enc.int8array.parse(acontent);
// console.log('Encrypt contentWA is ' + contentWA);
// 插件要求明文是base64格式
var dcBase64String = contentWA.toString(fun_aes.CryptoJS.enc.Base64);
// console.log('Encrypt dcBase64String is ' + dcBase64String);
// 加密 选定mode是ECB类型,无偏移量,还有其他的mode与pad类型
var encrypted = fun_aes.CryptoJS.AES.encrypt(contentWA, keyWA, {
iv: [],
mode: fun_aes.CryptoJS.mode.ECB,
padding: fun_aes.CryptoJS.pad.NoPadding
});
// console.log('Encrypt encrypted is ' + encrypted);
// 将密文转回int8数组
var bv = fun_aes.CryptoJS.enc.int8array.stringify(encrypted.ciphertext);
return bv;
};
export default {
processData,
setBleKeyVariables,
hexStrToUint8Array,
hexStringToDecimalArray,
ab2hex,
aesDecrypt,
aesEncrypt,
};
蓝牙连接注意事项:
1、请确保你的小程序的蓝牙权限已开通
2、若你的项目是通过搜索附近设备来匹配设备id,需要注意苹果和安卓系统的兼容处理(可以参考文章:解决uniapp微信小程序Android与iOS系统获取蓝牙广播包中deviceid不同的办法)