/** * @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:明文,1:AES128,2:SM4(事先约定密钥),3:SM4(设备指定密钥) * 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) } }) } } })