wx-starlock/stores/bluetooth.js

690 lines
22 KiB
JavaScript
Raw Normal View History

/**
* @description 蓝牙数据持久化
*/
import { defineStore } from 'pinia'
import crc from 'crc'
import { sm4 } from 'sm-crypto'
import { md5 } from 'js-md5'
// 定时器
let timer
// 特性值回调
let characteristicValueCallback = null
// 命令ID
const cmdIds = {
// 获取公钥
getPublicKey: 0x3090,
// 获取私钥
getCommKey: 0x3091,
// 获取锁状态
getLockStatus: 0x3040,
// 新增用户
addUser: 0x3001,
// 开门
openDoor: 0x3005,
// 重置设备
resetDevice: 0x3004,
// 清理用户
cleanUser: 0x300C,
}
export const useBluetoothStore = defineStore('ble', {
state() {
return {
/*
* 蓝牙状态
* -1 未知初始状态
* 0 正常
* 1 系统蓝牙未打开
* 2 小程序蓝牙功能被禁用
* 3 系统蓝牙未打开且小程序蓝牙功能被禁用
*/
bluetoothStatus: -1,
// 设备列表
deviceList: [],
// 当前锁信息
currentLockInfo: {},
// 消息序号
messageCount: 1
}
},
actions: {
// 初始化蓝牙模块并监听蓝牙状态变化
initBluetooth() {
const that = this
// 初始化蓝牙模块
return new Promise(resolve => {
uni.openBluetoothAdapter({
success() {
that.bluetoothStatus = 0
console.log('蓝牙初始化成功')
resolve()
},
fail(err) {
console.log('蓝牙初始化失败', err)
// 系统蓝牙未打开
if(err.errCode === 10001) {
if(that.bluetoothStatus === 2) {
that.bluetoothStatus = 3
} else {
that.bluetoothStatus = 1
}
}
// 小程序蓝牙功能被禁用
if(err.errno === 103) {
if(that.bluetoothStatus === 1) {
that.bluetoothStatus = 3
} else {
that.bluetoothStatus = 2
}
}
resolve()
}
})
})
},
// 监听蓝牙状态变化
onBluetoothState() {
const that = this
uni.onBluetoothAdapterStateChange((res) => {
console.log('蓝牙状态改变', res)
if(that.bluetoothStatus === 3 && res.available) {
that.bluetoothStatus = 2
} else if (that.bluetoothStatus === 2 && !res.available) {
that.bluetoothStatus = 3
} else if (that.bluetoothStatus === 1 && res.available) {
that.bluetoothStatus = 0
} else if (that.bluetoothStatus === 0 && !res.available){
that.bluetoothStatus = 1
} else if (that.bluetoothStatus === -1) {
if(res.available) {
that.bluetoothStatus = 0
} else {
that.bluetoothStatus = 1
}
}
})
},
// 监听蓝牙设备连接状态
onBluetoothConnectStatus() {
const that = this
uni.onBLEConnectionStateChange(function (res) {
if(res.deviceId === that.currentLockInfo.deviceId) {
console.log('设备连接状态改变', res)
that.updateCurrentLockInfo({
...that.currentLockInfo,
connected: res.connected
})
}
})
},
// 解析特征值
parsingCharacteristicValue(binaryData) {
const that = this
// 0x20 明文 0x22 SM4事先约定密钥 0x23 SM4设备指定密钥
if(binaryData[7] === 0x20) {
if(binaryData[12] * 256 + binaryData[13] === cmdIds.getPublicKey) {
if(binaryData[14] === 0) {
that.updateCurrentLockInfo({
...that.currentLockInfo,
publicKey: binaryData.slice(15, 31)
})
}
characteristicValueCallback({
code: binaryData[14]
})
}
} else if(binaryData[7] === 0x22) {
// 截取入参
const cebBinaryData = binaryData.slice(12, binaryData.length - 2)
// 解密
const key = new Uint8Array(16)
for (let i = 0; i < that.currentLockInfo.name.length; i++) {
key[i] = that.currentLockInfo.name.charCodeAt(i)
}
const decrypted = sm4.decrypt(cebBinaryData, key, { mode: 'ecb', output: 'array' })
console.log('ecb解密后的数据', decrypted)
if(decrypted[0] * 256 + decrypted[1] === cmdIds.getCommKey) {
if(decrypted[2] === 0) {
that.updateCurrentLockInfo({
...that.currentLockInfo,
commKey: decrypted.slice(3, 19),
signKey: decrypted.slice(19, 35)
})
console.log('commKey', Array.from(that.currentLockInfo.commKey))
console.log('signKey', Array.from(that.currentLockInfo.signKey))
}
characteristicValueCallback({
code: decrypted[2]
})
}
} else {
const cebBinaryData = binaryData.slice(12, binaryData.length - 2)
const decrypted = sm4.decrypt(cebBinaryData, that.currentLockInfo.commKey, { mode: 'ecb', output: 'array' })
console.log('ecb解密后的数据', decrypted)
const cmdId = decrypted[0] * 256 + decrypted[1]
if(cmdId === cmdIds.getLockStatus) {
if (decrypted[2] === 0) {
console.log('获取锁状态成功', decrypted)
}
} else if(cmdId === cmdIds.openDoor) {
if (decrypted[2] === 0) {
console.log('开门成功', decrypted)
}
} else if(cmdId === cmdIds.addUser) {
if (decrypted[2] === 0) {
console.log('添加用户', decrypted)
}
}
}
},
// 监听蓝牙设备特征值改变
onBluetoothCharacteristicValueChange() {
const that = this
// 完整数据
let completeArray
// 完整内容数据长度
let length
console.log('开始监听特征值改变')
uni.onBLECharacteristicValueChange(function (res) {
if(res.deviceId === that.currentLockInfo.deviceId) {
let binaryData = new Uint8Array(res.value)
if(binaryData[0] === 0xEF && binaryData[1] === 0x01 && binaryData[2] === 0xEE && binaryData[3] === 0x02) {
length = binaryData[8] * 256 + binaryData[9]
if(length + 14 > binaryData.length) {
completeArray = binaryData
} else {
that.parsingCharacteristicValue(binaryData)
}
} else {
if(completeArray) {
const combinedArray = new Uint8Array(completeArray.length + binaryData.length)
combinedArray.set(completeArray, 0);
combinedArray.set(binaryData, completeArray.length)
completeArray = combinedArray
if(length + 14 === completeArray.length) {
that.parsingCharacteristicValue(completeArray)
completeArray = null
}
}
}
}
})
},
// 开始搜索蓝牙设备
getBluetoothDevices() {
const that = this
if(this.bluetoothStatus !== 0) {
console.log('搜索未执行', this.bluetoothStatus)
this.getBluetoothStatus()
return
}
uni.startBluetoothDevicesDiscovery({
success: function (res) {
setTimeout(() => {
that.searchBluetoothDevices()
}, 300)
},
fail: async function (res) {
console.log('开始搜索失败', res)
if(res.errCode === 10000) {
// 重新初始化蓝牙适配器
await that.initBluetooth()
that.getBluetoothDevices()
}
}
})
},
// 定时查询蓝牙设备
searchBluetoothDevices() {
const that = this
if(this.bluetoothStatus !== 0) {
console.log('搜索未执行', this.bluetoothStatus)
this.getBluetoothStatus()
return
}
timer = setInterval(() => {
uni.getBluetoothDevices({
success(res) {
const deviceList = res.devices
that.deviceList = []
for(let i = 0; i < deviceList.length; i++) {
if(deviceList[i]?.advertisServiceUUIDs) {
const uuid = deviceList[i]?.advertisServiceUUIDs[0]
if(uuid && uuid.slice(2,8)==='758824' && uuid.slice(30,32)==='00') {
that.deviceList.push(deviceList[i])
}
}
}
console.log('设备列表', that.deviceList)
},
fail: async function (res) {
console.log('获取设备列表失败', res)
if(res.errCode === 10000) {
// 重新初始化蓝牙适配器
await that.initBluetooth()
}
}
})
}, 1000)
},
// 停止搜索蓝牙设备
stopGetBluetoothDevices() {
clearInterval(timer)
uni.stopBluetoothDevicesDiscovery()
},
// 蓝牙状态提示
getBluetoothStatus() {
if(this.bluetoothStatus === 1) {
uni.showModal({
title: '提示',
content: '蓝牙尚未打开,请先打开蓝牙',
showCancel: false,
confirmText: '确定',
})
} else if(this.bluetoothStatus === 2 || this.bluetoothStatus === 3) {
uni.showModal({
title: '提示',
content: '小程序蓝牙功能被禁用,请打开小程序蓝牙权限',
showCancel: false,
confirmText: '去设置',
success(res) {
if(res.confirm) {
uni.openSetting()
}
}
})
}
},
// 检查小程序设置
checkSetting() {
const that = this
uni.getSetting({
async success(res) {
const bluetooth = res.authSetting['scope.bluetooth']
if(that.bluetoothStatus === -1) {
if(bluetooth) {
that.bluetoothStatus = 0
} else {
that.bluetoothStatus = 2
}
} else if(that.bluetoothStatus === 0 && !bluetooth) {
that.bluetoothStatus = 2
} else if(that.bluetoothStatus === 1 && !bluetooth) {
that.bluetoothStatus = 3
} else if(that.bluetoothStatus === 2 && bluetooth) {
that.bluetoothStatus = 0
await that.initBluetooth()
} else if(that.bluetoothStatus === 3 && bluetooth) {
that.bluetoothStatus = 1
await that.initBluetooth()
that.onBluetoothState()
}
console.log('蓝牙权限', bluetooth, that.bluetoothStatus)
}
})
},
// 连接蓝牙设备+获取设备服务+获取设备特征值
connectBluetoothDevice() {
const that = this
return new Promise((resolve) => {
if(that.bluetoothStatus !== 0) {
console.log('连接未执行', that.bluetoothStatus)
that.getBluetoothStatus()
resolve(false)
return
}
uni.createBLEConnection({
deviceId: that.currentLockInfo.deviceId,
success(res) {
console.log('连接成功', res)
// 获取设备服务
uni.getBLEDeviceServices({
deviceId: that.currentLockInfo.deviceId,
success (res) {
let serviceId
for(let i = 0; i < res.services.length; i++) {
if(res.services[i].isPrimary) {
serviceId = res.services[i].uuid
}
}
// 获取设备对应服务的特征值
uni.getBLEDeviceCharacteristics({
deviceId: that.currentLockInfo.deviceId,
serviceId: serviceId,
success (res) {
let notifyCharacteristicId
let writeCharacteristicId
for(let i = 0; i < res.characteristics.length; i++) {
const characteristic = res.characteristics[i]
if(characteristic.properties.notify) {
notifyCharacteristicId = characteristic.uuid
}
if(characteristic.properties.write) {
writeCharacteristicId = characteristic.uuid
}
}
that.updateCurrentLockInfo({
...that.currentLockInfo,
serviceId,
notifyCharacteristicId,
writeCharacteristicId
})
that.notifyBluetoothCharacteristicValueChange()
resolve(true)
},
fail(res) {
if(res.errCode === 10006) {
uni.showToast({
title: '连接失败,请靠近设备并保持设备处于唤醒状态',
icon: 'none'
})
}
console.log('获取设备特征值失败', res)
resolve(false)
}
})
},
fail(res) {
if(res.errCode === 10006) {
uni.showToast({
title: '连接失败,请靠近设备并保持设备处于唤醒状态',
icon: 'none'
})
}
console.log('获取设备服务失败', res)
resolve(false)
}
})
},
fail(res) {
uni.showToast({
title: '连接失败,请靠近设备并保持设备处于唤醒状态',
icon: 'none'
})
console.log('连接失败', res)
resolve(false)
}
})
})
},
// 更新当前锁信息
updateCurrentLockInfo(lockInfo) {
console.log('更新当前锁信息', lockInfo)
this.currentLockInfo = lockInfo
},
// 订阅设备特征值改变
notifyBluetoothCharacteristicValueChange() {
const that = this
uni.notifyBLECharacteristicValueChange({
state: true,
deviceId: that.currentLockInfo.deviceId,
serviceId: that.currentLockInfo.serviceId,
characteristicId: that.currentLockInfo.notifyCharacteristicId,
success(res) {
console.log('订阅成功', res)
},
fail(res) {
console.log('订阅失败', res)
}
})
},
// 断开蓝牙连接
closeBluetoothConnection() {
const that = this
uni.closeBLEConnection({
deviceId: that.currentLockInfo.deviceId,
success(res) {
console.log('断开连接成功', res)
},
fail(res) {
console.log('断开连接失败', res)
}
})
},
// 写入特征值
async writeBLECharacteristicValue(binaryData) {
const that = this
// 确认蓝牙状态正常
if(this.bluetoothStatus !== 0) {
console.log('写入未执行', this.bluetoothStatus)
this.getBluetoothStatus()
return
}
// 确认设备连接正常
if(!that.currentLockInfo.connected) {
const result = await that.connectBluetoothDevice()
if(!result) return
}
console.log('设备ID', that.currentLockInfo.deviceId)
console.log('设备名称:', that.currentLockInfo.name)
console.log('设备主服务:', that.currentLockInfo.serviceId)
console.log('设备写入特征值:', that.currentLockInfo.writeCharacteristicId)
console.log('设备写入数据:', Array.from(binaryData))
// 次数
const count = Math.ceil(binaryData.length / 20)
for(let i = 0; i < count; i++) {
const writeData = binaryData.slice(i * 20, i === (count - 1) ? binaryData.length : (i + 1) * 20)
uni.writeBLECharacteristicValue({
deviceId: that.currentLockInfo.deviceId,
serviceId: that.currentLockInfo.serviceId,
characteristicId: that.currentLockInfo.writeCharacteristicId,
value: writeData.buffer,
success() {
console.log('数据写入成功', Array.from(writeData))
},
fail(res) {
console.log('写入失败', res)
}
})
}
},
/*
* 生成包头
* encryptionType 加密类型 0明文1AES1282SM4事先约定密钥3SM4设备指定密钥
* originalLength 原始数据长度
* */
createPackageHeader(encryptionType, originalLength) {
// 头部数据
let headArray = new Uint8Array(12)
// 固定包头
headArray[0] = 0xEF
headArray[1] = 0x01
headArray[2] = 0xEE
headArray[3] = 0x02
// 包类型 发送
headArray[4] = 0x01
// 包序号
headArray[5] = this.messageCount / 256
headArray[6] = this.messageCount % 256
this.messageCount++
// 包标识
if(encryptionType === 0) {
headArray[7] = 0x20
} else if(encryptionType === 2) {
headArray[7] = 0x22
} else {
headArray[7] = 0x23
}
// 数据长度
if(encryptionType === 0) {
headArray[8] = originalLength / 256
headArray[9] = originalLength % 256
} else {
const length = Math.ceil(originalLength / 16) * 16
headArray[8] = length / 256
headArray[9] = length % 256
}
headArray[10] = originalLength / 256
headArray[11] = originalLength % 256
return headArray
},
// 生成包尾 头部数据+内容数据
createPackageEnd(headArray,conentArray) {
// 拼接头部和内容
let mergerArray = new Uint8Array(headArray.length + conentArray.length)
mergerArray.set(headArray)
mergerArray.set(conentArray, headArray.length)
// crc加密
const crcResult = crc.crc16kermit(mergerArray)
// 拼接crc
let newArray = new Uint8Array(mergerArray.length + 2)
newArray.set(mergerArray)
newArray.set([crcResult / 256, crcResult % 256], mergerArray.length)
return newArray
},
// 获取公钥
async getPublicKey(name) {
const headArray = this.createPackageHeader(0, 42)
const conentArray = new Uint8Array(42)
conentArray[0] = cmdIds.getPublicKey / 256
conentArray[1] = cmdIds.getPublicKey % 256
for(let i = 0; i < name.length; i++) {
conentArray[i + 2] = name.charCodeAt(i)
}
const packageArray = this.createPackageEnd(headArray, conentArray)
await this.writeBLECharacteristicValue(packageArray)
return this.getWriteResult()
},
// 获取私钥
async getCommKey(name, keyId, authUid, nowTime, publicKey) {
const length = 2 + 40 + 40 + 20 + 4 + 1 + 16
const headArray = this.createPackageHeader(2, length)
const conentArray = new Uint8Array(length)
conentArray[0] = cmdIds.getCommKey / 256
conentArray[1] = cmdIds.getCommKey % 256
for (let i = 0; i < name.length; i++) {
conentArray[i + 2] = name.charCodeAt(i)
}
for(let i = 0; i < keyId.length; i++) {
conentArray[i + 42] = keyId.charCodeAt(i)
}
for(let i = 0; i < authUid.length; i++) {
conentArray[i + 82] = authUid.charCodeAt(i)
}
conentArray.set(this.timestampToArray(nowTime), 102)
conentArray[106] = 16
const md5Array = this.platformMd5Encrypte(authUid, keyId, conentArray.slice(102, 106), publicKey)
conentArray.set(md5Array, 107)
const cebArray = sm4.encrypt(conentArray, conentArray.slice(2, 18), { mode: 'ecb', output: 'array' })
const packageArray = this.createPackageEnd(headArray, cebArray)
await this.writeBLECharacteristicValue(packageArray)
return this.getWriteResult()
},
/*
* 锁与平台md5加密
* authUid
* keyId
* token nowTime
* publicKey
* */
platformMd5Encrypte(authUid, keyId, token, publicKey) {
const length = authUid.length + keyId.length + 4 + 16
const md5Array = new Uint8Array(length)
for (let i = 0; i < authUid.length; i++) {
md5Array[i] = authUid.charCodeAt(i)
}
for (let i = 0; i < keyId.length; i++) {
md5Array[authUid.length + i] = keyId.charCodeAt(i)
}
md5Array.set(token, authUid.length + keyId.length)
md5Array.set(publicKey, authUid.length + keyId.length + 4)
const md5Text= md5(md5Array)
return new Uint8Array(md5Text.match(/.{1,2}/g).map(byte => parseInt(byte, 16)))
},
// 获取锁状态
async getLockStatus(name, uid, nowTime, localTime, commKey) {
const length = 2 + 40 + 20 + 4 + 4
const headArray = this.createPackageHeader(3, length)
const conentArray = new Uint8Array(length)
conentArray[0] = cmdIds.getLockStatus / 256
conentArray[1] = cmdIds.getLockStatus % 256
for (let i = 0; i < name.length; i++) {
conentArray[i + 2] = name.charCodeAt(i)
}
for (let i = 0; i < uid.length; i++) {
conentArray[i + 42] = uid.charCodeAt(i)
}
conentArray.set(this.timestampToArray(nowTime), 62)
conentArray.set(this.timestampToArray(localTime), 66)
const cebArray = sm4.encrypt(conentArray, commKey, { mode: 'ecb', output: 'array' })
const packageArray = this.createPackageEnd(headArray, cebArray)
await this.writeBLECharacteristicValue(packageArray)
},
// 时间戳转二进制
timestampToArray(timestamp) {
const array = new Uint8Array(4)
array[0] = (timestamp & 0xff000000) >> 24
array[1] = (timestamp & 0xff0000) >> 16
array[2] = (timestamp & 0xff00) >> 8
array[3] = (timestamp & 0xff)
return array
},
// 添加用户
addLockUser(data) {
const { name, authUid, uid, keyId, openMode, keyType, startDate, expireDate, useCountLimit, isRound, weekRound,
startHour, startMin, endHour, endMin, role, password, token, publicKey} = data
const length = 2 + 40 + 20 + 40 + 20 + 1 + 1 + 4 + 4 + 2 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 1 + 16
const headArray = this.createPackageHeader(3, length)
const conentArray = new Uint8Array(length)
conentArray[0] = cmdIds.addUser / 256
conentArray[1] = cmdIds.addUser % 256
// 尚未完成
},
// 获取写入结果
getWriteResult() {
return new Promise(resolve => {
const timer = setTimeout(() => {
resolve({ code: -1 })
}, 7000)
characteristicValueCallback = (data) => {
clearTimeout(timer)
resolve(data)
}
})
}
}
})