import { defineStore } from 'pinia' import { sm4 } from 'sm-crypto' import { buildNumber, configs, version } from '@/starCloud/env' import { addCustomPasswordRequest, bindLockRequest, deletePasswordRequest, getLockDetailRequest, getLockNetTokenRequest, getOfflinePasswordRequest, getServerDatetimeRequest, getStarCloudToken, getUserNoListRequest, starCloudCreateUser, updateLockUserNoRequest, updatePasswordRequest } from '@/starCloud/api' import { getStorage, removeStorage, setStorage } from '@/starCloud/storage' import { closeBLEConnection, closeBluetoothAdapter, createBLEConnection, getBluetoothDevices, offBluetoothAdapterStateChange, onBLECharacteristicValueChange, Result, searchAndConnectDevice, startBluetoothDevicesDiscovery, stopBluetoothDevicesDiscovery, writeBLECharacteristicValue } from '@/starCloud/basic' import { arrayToTimestamp, convertWeekdaysToNumber, createPackageEnd, md5Encrypt, timestampToArray, uint8ArrayToString } from '@/starCloud/format' /** * 离线密码 * 该功能无需蓝牙交互直接请求服务端,详细参数说明请看星云接口文档 * @typedef {Object} OfflinePassword * @property {String} keyboardPwdName - 密码名称 * @property {Number} keyboardPwdType - 密码类型 * @property {Number} isCoerced - 胁迫 1:胁迫 2:非胁迫 * @property {Number} startDate - 开始时间 * @property {Number} endDate - 结束时间 * @property {Number} hoursStart - 开始小时 * @property {Number} hoursEnd - 结束小时 */ /** * 自定义密码 * @typedef {Object} CustomPassword * @property {String} keyboardPwdName - 密码名称 * @property {Number} keyboardPwdType - 密码类型 * @property {Number} isCoerced - 胁迫 1:胁迫 2:非胁迫 * @property {Number} startDate - 开始时间 * @property {Number} endDate - 结束时间 * @property {Number} keyboardPwd - 密码 * @property {Number} addType - 添加方式,当前仅支持1 1:蓝牙 2:网关 * @property {Number} operate - 操作类型,0:注册 1:修改 * @property {Number} pwdRight - 是否是管理员密码,0:否 1:是 */ /** * 账户信息信息 * @typedef {Object} AccountInfo * @property {Number} uid 用户ID * @property {String} username 用户名 * @property {String} password 密码 * @property {String} token token 非必填 */ /** * 锁信息 * @typedef {Object} lockInfo * @property {Number} keyId - 钥匙ID * @property {Number} lockId - 锁ID * @property {String} lockName - 锁名称 * @property {String} lockAlias - 锁别名 * @property {Number} electricQuantity - 电量 * @property {Number} electricQuantityStandby - 备用电量 * @property {Number} electricQuantityDate - 电量更新时间 * @property {String} fwVersion - 固件版本 * @property {String} hwVersion - 硬件版本 * @property {Number} keyType - 钥匙类型 * @property {Number} passageMode - 常开模式 * @property {Number} userType - 用户类型 * @property {Number} startDate - 有效期开始时间 * @property {Number} endDate - 有效期结束时间 * @property {Object} weekDays - 循环周期 * @property {Number} remoteEnable - 是否支持远程开锁:0-未知,1-是,2-否 * @property {Number} faceAuthentication - 是否实名认证:0-未知,1-是,2-否 * @property {Number} lastFaceValidateTime - 最后一次人脸验证时间 * @property {Number} nextFaceValidateTime - 下次人脸验证时间 * @property {Number} keyRight - 是否授权管理员钥匙: 0-未知,1-是,2-否 * @property {Number} keyStatus - 钥匙状态 * @property {Object} bluetooth - 蓝牙 * @property {Number} sendDate - 发送时间 * @property {Number} isLockOwner - 是否是锁主 * @property {Object} lockFeature - 锁特征 * @property {Object} lockSetting - 锁设置 * @property {Number} lockUserNo - 用户编号 * @property {Number} senderUserId - 发送者用户ID * @property {Number} isOnlyManageSelf - 如果是授权管理员此字段区分是否仅管理自己发的钥匙 * @property {Number} restoreCount - 重置次数 * @property {String} model - 模式 */ /** * 锁蓝牙信息 * @typedef {Object} bluetooth * @property {String} bluetoothDeviceId - 设备ID * @property {String} bluetoothDeviceName - 设备名称 * @property {String} publicKey - 公钥 * @property {String} privateKey - 私钥 * @property {String} signKey - 签名密钥 * @property {String} passwordKey - 密码密钥 */ /** * 锁设置信息 * @typedef {Object} lockSetting * @property {Number} appUnlockOnline - 开门是否需要联网 */ /** * 请求返回数据 * @typedef {Object} requestData * @property {String} userNos - 设备ID */ // 命令ID const cmdIds = { // 获取公钥 getPublicKey: 0x3090, // 获取私钥 getCommKey: 0x3091, // 获取锁状态 getLockStatus: 0x3040, // 新增用户 addUser: 0x3001, // 开门 openDoor: 0x3005, // 重置设备 resetDevice: 0x3004, // 清理用户 cleanUser: 0x300c, // 扩展命令 expandCmd: 0x3030 } // 子命令ID const subCmdIds = { // 设置开锁密码 setLockPassword: 3, // 重置开锁密码 resetLockPassword: 19 } // 特性值回调 let characteristicValueCallback = null // 完整数据 let completeArray // 完整内容数据长度 let length // 请求参数 let requestParams = null // 计时器 let timer export const useStarCloudStore = defineStore('starCloud', { state() { return { // 环境 env: null, // 客户端Id clientId: null, // 客户端密码 clientSecret: null, // 小程序环境 envVersion: '', // 锁信息 lockInfo: null, // 服务器时间 serverTimestamp: 0, // 时间差 timeDifference: 0, // 搜索设备列表 searchDeviceList: [], // 星云用户信息 userInfo: null, // 账户信息 accountInfo: null, // 消息序号 messageCount: 1 } }, actions: { /** * 初始化星云 * @param params * @param {String} params.clientId 客户端Id * @param {String} params.clientSecret 客户端密码 * @param {String} params.env 环境 */ initStarCloud(params) { const { clientId, clientSecret, env } = params const appInfo = uni.getAccountInfoSync() this.envVersion = appInfo.miniProgram.envVersion this.env = env || 'XHJ' this.clientId = clientId this.clientSecret = clientSecret // 监听特性值变化 onBLECharacteristicValueChange(this.listenCharacteristicValue) }, /** * 注册星云 * @returns {Promise} */ async register() { const { code, data, message } = await starCloudCreateUser({ clientId: this.clientId, clientSecret: this.clientSecret }) return new Result(code, data, message) }, /** * 退出登录 * @param params * @param {Number} params.uid 用户ID */ logout(params) { const { uid } = params if (this.accountInfo?.uid === uid) { this.userInfo = null this.accountInfo = null this.lockInfo = null removeStorage('starCloudToken') } }, /** * 选择锁 * @param params * @param {AccountInfo} params.accountInfo 账号信息 * @param {Number} params.lockId 锁ID * @returns {Promise} */ async selectLock(params) { const { accountInfo, lockId } = params const result = await this.login(accountInfo) if (result.code !== Result.Success.code) { return result } const { code, data, message } = await getLockDetailRequest({ lockId }) if (code === Result.Success.code) { let lockList = getStorage('lockList') if (!lockList) { lockList = {} } if (lockList[accountInfo.uid]) { const index = lockList[accountInfo.uid].findIndex(item => item.lockId === lockId) if (index === -1) { lockList[accountInfo.uid].push(data) } else { lockList[accountInfo.uid][index] = data } setStorage('lockList', lockList) } else { lockList[accountInfo.uid] = [data] setStorage('lockList', lockList) } this.lockInfo = data } else { const lockList = getStorage('lockList') if (lockList[accountInfo.uid]) { const index = lockList[accountInfo.uid].findIndex(item => item.lockId === lockId) if (index !== -1) { this.lockInfo = lockList[accountInfo.uid][index] return new Result(Result.Success.code, this.lockInfo) } } } return new Result(code, data, message) }, /** * 开门 * @param params * @param {AccountInfo} params.accountInfo 账号信息 * @param {String} params.type 开门方式 close: 关门 open: 开门 * @param {Boolean} params.disconnect 操作后是否断开连接 * @returns {Promise} */ async openDoor(params) { const { accountInfo, type } = params // 设置执行账号 const result = await this.login(accountInfo) if (result.code !== Result.Success.code) { return result } // 确认设备连接正常 const searchResult = await searchAndConnectDevice(this.lockInfo.bluetooth.bluetoothDeviceName) if (searchResult.code !== Result.Success.code) { return searchResult } this.updateLockInfo(searchResult.data) // 检查是否已添加为用户 const checkResult = await this.checkLockUser() if (checkResult.code !== Result.Success.code) { return checkResult } // 是否需要联网 let onlineToken = '' if (this.lockInfo.lockSetting.appUnlockOnline) { const result = await this.getNetToken() if (result.code === Result.Success.code) { onlineToken = result.data.token } else { return result } } // 开门方式 let openMode if (type === 'close') { openMode = this.lockInfo.lockSetting.appUnlockOnline ? 33 : 32 } else { openMode = this.lockInfo.lockSetting.appUnlockOnline ? 1 : 0 } const name = this.lockInfo.bluetooth.bluetoothDeviceName const uid = this.accountInfo.uid.toString() const openTime = Math.ceil(new Date().getTime() / 1000) + this.timeDifference const length = 2 + 40 + 20 + 1 + 4 + 4 + 1 + 16 + 16 const headArray = this.createPackageHeader(3, length) const contentArray = new Uint8Array(length) contentArray[0] = cmdIds.openDoor / 256 contentArray[1] = cmdIds.openDoor % 256 for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } for (let i = 0; i < uid.length; i++) { contentArray[i + 42] = uid.charCodeAt(i) } contentArray[62] = openMode contentArray.set(timestampToArray(openTime), 63) console.log('开门时token', this.lockInfo.token) contentArray.set(this.lockInfo.token || timestampToArray(openTime), 67) contentArray[71] = 16 const md5Array = md5Encrypt( name + uid, this.lockInfo.token || timestampToArray(openTime), this.lockInfo.bluetooth.signKey ) contentArray.set(md5Array, 72) for (let i = 0; i < onlineToken.length; i++) { contentArray[i + 88] = onlineToken.charCodeAt(i) } const cebArray = sm4.encrypt(contentArray, this.lockInfo.bluetooth.privateKey, { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.openDoor, params) }, /** * 获取离线密码 * @param {AccountInfo} accountInfo 账号信息 * @param {OfflinePassword} password 密码信息 * @returns {Promise} */ async getOfflinePassword(accountInfo, password) { // 设置执行账号 const result = await this.login(accountInfo) if (result.code !== Result.Success.code) { return result } return await getOfflinePasswordRequest({ ...password, lockId: this.lockInfo.lockId }) }, /** * 自定义密码 * @param params * @param {AccountInfo} params.accountInfo 账号信息 * @param {CustomPassword} params.password 密码信息 * @param {Boolean} params.disconnect 操作后是否断开连接 * @returns {Promise} */ async customPassword(params) { const { accountInfo, password } = params // 设置执行账号 const result = await this.login(accountInfo) if (result.code !== Result.Success.code) { return result } // 确认设备连接正常 const searchResult = await searchAndConnectDevice(this.lockInfo.bluetooth.bluetoothDeviceName) if (searchResult.code !== Result.Success.code) { return searchResult } this.updateLockInfo(searchResult.data) // 检查是否已添加为用户 const checkResult = await this.checkLockUser() if (checkResult.code !== Result.Success.code) { return checkResult } requestParams = password let { pwdNo, operate, keyboardPwd, startDate, endDate, pwdRight } = password const uid = this.accountInfo.uid.toString() const keyId = this.lockInfo.keyId.toString() const isAdmin = pwdRight const userCountLimit = 0xffff startDate = Math.floor(startDate / 1000) endDate = Math.floor(endDate / 1000) keyboardPwd = keyboardPwd.toString() if (!pwdNo) { pwdNo = 0 } const length = 2 + 1 + 1 + 40 + 20 + 2 + 1 + 1 + 20 + 2 + 4 + 4 + 4 + 1 + 16 const headArray = this.createPackageHeader(3, length) const contentArray = new Uint8Array(length) contentArray[0] = cmdIds.expandCmd / 256 contentArray[1] = cmdIds.expandCmd % 256 // 子命令 contentArray[2] = subCmdIds.setLockPassword contentArray[3] = length - 3 for (let i = 0; i < keyId.length; i++) { contentArray[i + 4] = keyId.charCodeAt(i) } for (let i = 0; i < uid.length; i++) { contentArray[i + 44] = uid.charCodeAt(i) } contentArray[64] = pwdNo / 256 contentArray[65] = pwdNo % 256 contentArray[66] = operate contentArray[67] = isAdmin for (let i = 0; i < keyboardPwd.length; i++) { contentArray[i + 68] = keyboardPwd.charCodeAt(i) } contentArray[88] = userCountLimit / 256 contentArray[89] = userCountLimit % 256 contentArray.set(this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), 90) contentArray.set(timestampToArray(startDate), 94) contentArray.set(timestampToArray(endDate), 98) contentArray[102] = 16 const md5Array = md5Encrypt( keyId + uid, this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), this.lockInfo.bluetooth.signKey ) contentArray.set(md5Array, 103) const cebArray = sm4.encrypt(contentArray, this.lockInfo.bluetooth.privateKey, { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.customPassword, params) }, // 搜索设备 async searchDevice(callback) { const result = await startBluetoothDevicesDiscovery() if (result.code === Result.Success.code) { const timestamp = new Date().getTime() let queryFlag = false timer = setInterval(async () => { const queryResult = await getBluetoothDevices() if (queryResult.code === Result.Success.code) { const deviceList = queryResult.data if (queryFlag === false && deviceList.length > 0) { queryFlag = true } if (new Date().getTime() - timestamp > 10000 && !queryFlag) { if (timer) { clearInterval(timer) } callback(Result.NotAvailableWeChatNearbyDevicesEmpty) } const list = [] 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') { list.push(deviceList[i]) } } } this.searchDeviceList = list callback( new Result(Result.Success.code, { list }) ) } else { callback(queryResult) } }, 1000) } else { callback(result) } }, // 停止搜索 async stopSearchDevice() { console.log('停止搜索') if (timer) { clearInterval(timer) } return await stopBluetoothDevicesDiscovery() }, /** * 绑定设备 * @param params * @param {AccountInfo} params.accountInfo 账号信息 * @param {String} params.name 设备名称 * @returns {Promise} */ async bindDevice(params) { const { accountInfo, name } = params // 设置执行账号 const result = await this.login(accountInfo) if (result.code !== Result.Success.code) { return result } const device = this.searchDeviceList.find(item => item.name === name) const connectResult = await createBLEConnection(device.deviceId) if (connectResult.code === Result.Success.code) { this.updateLockInfo({ ...connectResult.data, bluetooth: { bluetoothDeviceId: device.deviceId, bluetoothDeviceName: device.name } }) const publicKeyResult = await this.getPublicKey() if (publicKeyResult.code !== Result.Success.code) { return publicKeyResult } const commKeyResult = await this.getCommKey() if (commKeyResult.code !== Result.Success.code) { return commKeyResult } const lockStatusResult = await this.getLockStatus() if (lockStatusResult.code !== Result.Success.code) { return lockStatusResult } const timestamp = Math.ceil(new Date().getTime() / 1000) const password = (Math.floor(Math.random() * 900000) + 100000).toString() const addUserResult = await this.addLockUser({ params: { name: this.lockInfo.bluetooth.bluetoothDeviceName, keyId: '0', authUid: this.accountInfo.uid.toString(), uid: this.accountInfo.uid.toString(), openMode: 1, keyType: 0, startDate: timestamp, expireDate: 0xffffffff, useCountLimit: 0xffff, isRound: 0, weekRound: 0, startHour: 0, startMin: 0, endHour: 0, endMin: 0, role: 0xff, password }, disconnect: true }) if (addUserResult.code !== Result.Success.code) { return addUserResult } offBluetoothAdapterStateChange() closeBluetoothAdapter() const params = { lockAlias: this.lockInfo.bluetooth.bluetoothDeviceName, lockInfo: { ...this.lockInfo.lockConfig, adminPwd: password }, bluetooth: this.lockInfo.bluetooth, lockUserNo: this.lockInfo.lockUserNo, pwdTimestamp: this.lockInfo.pwdTimestamp, featureValue: this.lockInfo.featureValue, featureSettingValue: this.lockInfo.featureSettingValue, featureSettingParams: this.lockInfo.featureSettingParams } const bindLockResult = await bindLockRequest(params) if (bindLockResult.code === Result.Success.code) { this.updateLockInfo({ lockId: bindLockResult.data.lockId, keyId: bindLockResult.data.keyId, adminPwd: password }) } return new Result( bindLockResult.code, { lock: this.lockInfo }, bindLockResult.message ) } return connectResult }, /** * 清理用户 * @returns {Promise} */ async cleanLockUser() { // 确认设备连接正常 const searchResult = await searchAndConnectDevice(this.lockInfo.bluetooth.bluetoothDeviceName) if (searchResult.code !== Result.Success.code) { return searchResult } this.updateLockInfo(searchResult.data) // 获取并处理锁信息 let { uid: authUid, keyId, token, bluetooth } = this.lockInfo let { uid } = this.userInfo authUid = authUid.toString() uid = uid.toString() keyId = keyId.toString() const name = bluetooth.bluetoothDeviceName // 获取用户列表 const { code: requestCode, data: requestData } = await getUserNoListRequest({ lockId: this.lockInfo.lockId }) console.log('获取用户列表请求结果', requestCode, requestData) if (requestCode !== 0) return Result.Fail const userNoList = requestData.userNos // 组装发送数据 const length = 2 + 40 + 20 + 40 + 20 + 2 + userNoList.length + 4 + 1 + 16 const headArray = this.createPackageHeader(3, length) const contentArray = new Uint8Array(length) contentArray[0] = cmdIds.cleanUser / 256 contentArray[1] = cmdIds.cleanUser % 256 for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } for (let i = 0; i < authUid.length; i++) { contentArray[i + 42] = authUid.charCodeAt(i) } for (let i = 0; i < keyId.length; i++) { contentArray[i + 62] = keyId.charCodeAt(i) } for (let i = 0; i < uid.length; i++) { contentArray[i + 102] = uid.charCodeAt(i) } contentArray[122] = userNoList.length / 256 contentArray[123] = userNoList.length % 256 for (let i = 0; i < userNoList.length; i++) { contentArray[i + 124] = userNoList[i] } contentArray.set(token || new Uint8Array([0, 0, 0, 0]), 124 + userNoList.length) contentArray[128 + userNoList.length] = 16 const md5Array = md5Encrypt( authUid + keyId, token || new Uint8Array([0, 0, 0, 0]), bluetooth.publicKey ) contentArray.set(md5Array, 129 + userNoList.length) const cebArray = sm4.encrypt(contentArray, bluetooth.privateKey, { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.cleanLockUser, { disconnect: false }) }, /** * 登录星云 * @param {Object} accountInfo * @returns {Promise} */ async login(accountInfo) { let accounts = getStorage('starCloudAccount') let userInfos = getStorage('starCloudUser') if (!accounts) { accounts = {} } if (!userInfos) { userInfos = {} } this.accountInfo = accounts[accountInfo.uid] if (this.accountInfo && this.accountInfo.token) { this.userInfo = userInfos[accountInfo.uid] setStorage('starCloudToken', this.accountInfo.token) this.getServerTimestamp().then(() => {}) return Result.Success } const { code, data: userInfo, message } = await getStarCloudToken({ username: accountInfo.username, password: accountInfo.password, clientId: this.clientId, clientSecret: this.clientSecret }) if (code === Result.Success.code) { this.userInfo = userInfo this.accountInfo = { username: accountInfo.username, password: accountInfo.password, token: userInfo.access_token, uid: userInfo.uid } setStorage('starCloudToken', userInfo.access_token) accounts[userInfo.uid] = { uid: userInfo.uid, username: accountInfo.username, password: accountInfo.password, token: userInfo.access_token } setStorage('starCloudAccount', accounts) userInfos[userInfo.uid] = userInfo setStorage('starCloudUser', userInfo) // 获取服务器时间 this.getServerTimestamp().then(() => {}) } return new Result(code, {}, message) }, // 获取公钥 async getPublicKey() { const headArray = this.createPackageHeader(0, 42) const contentArray = new Uint8Array(42) contentArray[0] = cmdIds.getPublicKey / 256 contentArray[1] = cmdIds.getPublicKey % 256 const name = this.lockInfo.bluetooth.bluetoothDeviceName for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } const packageArray = createPackageEnd(headArray, contentArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.getPublicKey, { disconnect: false }) }, // 获取私钥 async getCommKey() { const length = 2 + 40 + 40 + 20 + 4 + 1 + 16 const headArray = this.createPackageHeader(2, length) const contentArray = new Uint8Array(length) contentArray[0] = cmdIds.getCommKey / 256 contentArray[1] = cmdIds.getCommKey % 256 const name = this.lockInfo.bluetooth.bluetoothDeviceName const keyId = '0' const authUid = this.accountInfo.uid.toString() await this.getServerTimestamp() const nowTime = this.serverTimestamp for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } for (let i = 0; i < keyId.length; i++) { contentArray[i + 42] = keyId.charCodeAt(i) } for (let i = 0; i < authUid.length; i++) { contentArray[i + 82] = authUid.charCodeAt(i) } contentArray.set(timestampToArray(nowTime), 102) contentArray[106] = 16 const md5Array = md5Encrypt( authUid + keyId, contentArray.slice(102, 106), this.lockInfo.bluetooth.publicKey ) contentArray.set(md5Array, 107) const cebArray = sm4.encrypt(contentArray, contentArray.slice(2, 18), { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.getCommKey, { disconnect: false }) }, // 获取锁状态 async getLockStatus() { const length = 2 + 40 + 20 + 4 + 4 const headArray = this.createPackageHeader(3, length) const contentArray = new Uint8Array(length) contentArray[0] = cmdIds.getLockStatus / 256 contentArray[1] = cmdIds.getLockStatus % 256 const name = this.lockInfo.bluetooth.bluetoothDeviceName const uid = this.accountInfo.uid.toString() await this.getServerTimestamp() const nowTime = this.serverTimestamp const date = new Date() const localTime = this.serverTimestamp - date.getTimezoneOffset() * 60 for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } for (let i = 0; i < uid.length; i++) { contentArray[i + 42] = uid.charCodeAt(i) } contentArray.set(timestampToArray(nowTime), 62) contentArray.set(timestampToArray(localTime), 66) const cebArray = sm4.encrypt(contentArray, this.lockInfo.bluetooth.privateKey, { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.getLockStatus, { disconnect: false }) }, // 获取联网token async getNetToken() { const { code, data, message } = await getLockNetTokenRequest({ lockId: this.lockInfo.lockId }) return new Result(code, data, message) }, // 获取服务器时间 async getServerTimestamp() { const { code, data, message } = await getServerDatetimeRequest({}) if (code === Result.Success.code) { this.serverTimestamp = Math.ceil(data.date / 1000) this.timeDifference = Math.ceil((data.date - new Date().getTime()) / 1000) } return new Result(code, data, message) }, // 添加用户 async addLockUser(params) { const { params: data } = params // 确认设备连接正常 const searchResult = await searchAndConnectDevice( this.lockInfo.bluetooth.bluetoothDeviceName, data.role !== 0xff ) if (searchResult.code !== Result.Success.code) { return searchResult } this.updateLockInfo(searchResult.data) const { name, authUid, uid, keyId, openMode, keyType, startDate, expireDate, useCountLimit, isRound, weekRound, startHour, startMin, endHour, endMin, role, password } = 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 contentArray = new Uint8Array(length) contentArray[0] = cmdIds.addUser / 256 contentArray[1] = cmdIds.addUser % 256 for (let i = 0; i < name.length; i++) { contentArray[i + 2] = name.charCodeAt(i) } for (let i = 0; i < authUid.length; i++) { contentArray[i + 42] = authUid.charCodeAt(i) } for (let i = 0; i < keyId.length; i++) { contentArray[i + 62] = keyId.charCodeAt(i) } for (let i = 0; i < uid.length; i++) { contentArray[i + 102] = uid.charCodeAt(i) } contentArray[122] = openMode contentArray[123] = keyType contentArray.set(timestampToArray(startDate), 124) contentArray.set(timestampToArray(expireDate), 128) contentArray[132] = useCountLimit / 256 contentArray[133] = useCountLimit % 256 contentArray[134] = isRound contentArray[135] = weekRound contentArray[136] = startHour contentArray[137] = startMin contentArray[138] = endHour contentArray[139] = endMin contentArray[140] = role for (let i = 0; i < password.length; i++) { contentArray[i + 141] = password.charCodeAt(i) } contentArray.set(this.lockInfo.token || timestampToArray(startDate), 161) contentArray[165] = 16 const md5Array = md5Encrypt( authUid + keyId, this.lockInfo.token || timestampToArray(startDate), this.lockInfo.bluetooth.publicKey ) contentArray.set(md5Array, 166) const cebArray = sm4.encrypt(contentArray, this.lockInfo.bluetooth.privateKey, { mode: 'ecb', output: 'array' }) const packageArray = createPackageEnd(headArray, cebArray) const writeResult = await writeBLECharacteristicValue( this.lockInfo.deviceId, this.lockInfo.serviceId, this.lockInfo.writeCharacteristicId, packageArray ) if (writeResult.code !== Result.Success.code) { return writeResult } return this.getWriteResult(this.addLockUser, params) }, // 获取写入结果 getWriteResult(request, params) { return new Promise(resolve => { const getWriteResultTimer = setTimeout(() => { resolve(Result.Fail) }, 20000) characteristicValueCallback = async data => { // code 6 token过期,重新获取 if (data.code === Result.NotTokenLock.code) { resolve(await request(params)) } else if (data.code === Result.NotRegisteredLock.code) { const checkResult = await this.checkLockUser(true) if (checkResult.code === Result.Success.code) { resolve(await request(params)) } else { clearTimeout(getWriteResultTimer) resolve(checkResult) } } else { clearTimeout(getWriteResultTimer) if (params.disconnect) { await this.disconnectDevice() } console.log('写入结果', data, request, params) resolve(data) } } }) }, // 检查是否已添加为用户 async checkLockUser(forceAdd = false) { if (this.lockInfo.lockUserNo === 0 || forceAdd) { const timestamp = Math.floor(new Date().getTime() / 1000) const password = (Math.floor(Math.random() * 900000) + 100000).toString() console.log('用户未添加,开始添加用户') const addUserParams = { name: this.lockInfo.bluetooth.bluetoothDeviceName, keyId: this.lockInfo.keyId.toString(), authUid: this.lockInfo.uid.toString(), uid: this.accountInfo.uid.toString(), openMode: 1, keyType: 0, startDate: this.lockInfo.startDate === 0 ? timestamp : Math.floor(this.lockInfo.startDate / 1000), expireDate: this.lockInfo.endDate === 0 ? 0xffffffff : Math.floor(this.lockInfo.endDate / 1000), useCountLimit: this.lockInfo.keyType === 3 ? 1 : 0xffff, isRound: this.lockInfo.keyType === 4 ? 1 : 0, weekRound: this.lockInfo.keyType === 4 ? convertWeekdaysToNumber(this.lockInfo.weekDays) : 0, startHour: this.lockInfo.keyType === 4 ? new Date(this.lockInfo.startDate).getHours() : 0, startMin: this.lockInfo.keyType === 4 ? new Date(this.lockInfo.startDate).getMinutes() : 0, endHour: this.lockInfo.keyType === 4 ? new Date(this.lockInfo.endDate).getHours() : 0, endMin: this.lockInfo.keyType === 4 ? new Date(this.lockInfo.endDate).getMinutes() : 0, role: 0, password } const addUserResult = await this.addLockUser({ params: addUserParams, disconnect: false }) console.log('添加用户蓝牙结果', addUserResult) if (addUserResult.code === Result.Success.code) { const { code } = await updateLockUserNoRequest({ keyId: this.lockInfo.keyId, lockUserNo: this.lockInfo.lockUserNo }) console.log('添加用户请求结果', code) return Result.Success } if (addUserResult.code === Result.NotMoreKeyLock.code) { console.log('用户达上限,开始清理用户') const { code: cleanCode } = await this.cleanLockUser() console.log('清理用户蓝牙结果', cleanCode) if (cleanCode === Result.Success.code) { return await this.checkLockUser() } return Result.Fail } if (addUserResult.code === Result.ReadyHasKeyLock.code) { return Result.Success } return Result.Fail } return Result.Success }, // 更新锁信息 updateLockInfo(lockInfo) { this.lockInfo = { ...this.lockInfo, ...lockInfo } }, // 特征值变化回调 listenCharacteristicValue(res) { if (res.deviceId === this.lockInfo.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 { this.parsingCharacteristicValue(binaryData).then(() => {}) } } 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) { this.parsingCharacteristicValue(completeArray).then(() => {}) completeArray = null } } } }, // 解析特征值 async parsingCharacteristicValue(binaryData) { // 0x20 明文 0x22 SM4(事先约定密钥) 0x23 SM4(设备指定密钥) if (binaryData[7] === 0x20) { if (binaryData[12] * 256 + binaryData[13] === cmdIds.getPublicKey) { if (binaryData[14] === Result.Success.code) { this.updateLockInfo({ bluetooth: { ...this.lockInfo.bluetooth, publicKey: [...binaryData.slice(15, 31)] } }) } characteristicValueCallback(new Result(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 < this.lockInfo.bluetooth.bluetoothDeviceName.length; i++) { key[i] = this.lockInfo.bluetooth.bluetoothDeviceName.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] === Result.Success.code) { this.updateLockInfo({ bluetooth: { ...this.lockInfo.bluetooth, privateKey: decrypted.slice(3, 19), signKey: decrypted.slice(19, 35) }, pwdTimestamp: arrayToTimestamp(decrypted.slice(35, 39)) * 1000 }) console.log('privateKey', Array.from(this.lockInfo.bluetooth.privateKey)) console.log('signKey', Array.from(this.lockInfo.bluetooth.signKey)) } characteristicValueCallback(new Result(decrypted[2])) } } else { const cebBinaryData = binaryData.slice(12, binaryData.length - 2) const decrypted = sm4.decrypt(cebBinaryData, this.lockInfo.bluetooth.privateKey, { mode: 'ecb', output: 'array' }) console.log('ecb解密后的数据', decrypted) const cmdId = decrypted[0] * 256 + decrypted[1] switch (cmdId) { case cmdIds.getLockStatus: if (decrypted[2] === Result.Success.code) { const lockConfig = { vendor: uint8ArrayToString(decrypted.slice(3, 23)), product: decrypted[23], model: uint8ArrayToString(decrypted.slice(24, 44)), fwVersion: uint8ArrayToString(decrypted.slice(44, 64)), hwVersion: uint8ArrayToString(decrypted.slice(64, 84)), serialNum0: uint8ArrayToString(decrypted.slice(84, 100)), serialNum1: uint8ArrayToString(decrypted.slice(100, 116)), btDeviceName: uint8ArrayToString(decrypted.slice(116, 132)), electricQuantity: decrypted[132], electricQuantityStandby: decrypted[133], restoreCount: decrypted[134] * 256 + decrypted[135], restoreDate: arrayToTimestamp(decrypted.slice(136, 140)), icPartNo: uint8ArrayToString(decrypted.slice(140, 150)), indate: arrayToTimestamp(decrypted.slice(150, 154)), mac: uint8ArrayToString(decrypted.slice(154, 174)), timezoneOffset: new Date().getTimezoneOffset() * 60 } this.updateLockInfo({ featureValue: uint8ArrayToString(decrypted.slice(175, 175 + decrypted[174])), featureSettingValue: uint8ArrayToString( decrypted.slice( 176 + decrypted[174], 176 + decrypted[174] + decrypted[175 + decrypted[174]] ) ), featureSettingParams: Array.from( decrypted.slice(176 + decrypted[174] + decrypted[175 + decrypted[174]]) ), lockConfig }) console.log('获取锁状态成功', this.lockInfo.lockConfig) } characteristicValueCallback(new Result(decrypted[2])) break case cmdIds.addUser: this.updateLockInfo({ token: decrypted.slice(42, 46) }) if (decrypted[46] === Result.Success.code) { this.updateLockInfo({ lockUserNo: decrypted[47] * 256 + decrypted[48] }) } console.log('添加用户结果', decrypted[46], this.lockInfo.lockUserNo) characteristicValueCallback(new Result(decrypted[46])) break case cmdIds.expandCmd: const subCmdId = decrypted[3] switch (subCmdId) { case subCmdIds.resetLockPassword: this.updateLockInfo({ token: decrypted.slice(5, 9) }) characteristicValueCallback(new Result(decrypted[4])) break case subCmdIds.setLockPassword: this.updateLockInfo({ token: decrypted.slice(5, 9) }) if (decrypted[2] === Result.Success.code) { if (decrypted[11] === Result.Success.code) { const pwdNo = decrypted[9] * 256 + decrypted[10] if (requestParams.operate === 0) { const addResult = await addCustomPasswordRequest({ ...requestParams, pwdUserNo: pwdNo, lockId: this.lockInfo.lockId }) if (addResult.code === Result.Success.code) { characteristicValueCallback( new Result(addResult.code, { pwdNo, keyboardPwdId: addResult.data.keyboardPwdId, keyboardPwd: addResult.data.keyboardPwd, keyboardPwdStatus: addResult.data.keyboardPwdStatus, pwdUserNo: pwdNo }) ) } else { characteristicValueCallback( new Result(addResult.code, addResult.data, addResult.message) ) } } else if (requestParams.operate === 1) { const updateResult = await updatePasswordRequest(requestParams) if (updateResult.code === Result.Success.code) { characteristicValueCallback(new Result(updateResult.code)) } else { characteristicValueCallback( new Result(updateResult.code, updateResult.data, updateResult.message) ) } } else if (requestParams.operate === 3) { const deleteResult = await deletePasswordRequest(requestParams) if (deleteResult.code === Result.Success.code) { characteristicValueCallback(new Result(deleteResult.code)) } else { characteristicValueCallback( new Result(deleteResult.code, deleteResult.data, deleteResult.message) ) } } } else { characteristicValueCallback(new Result(decrypted[11])) } } else { characteristicValueCallback(new Result(decrypted[2])) } break default: break } break case cmdIds.openDoor: this.updateLockInfo({ token: decrypted.slice(2, 6) }) console.log('开门', decrypted[6], this.lockInfo.token) characteristicValueCallback(new Result(decrypted[6])) break default: this.updateLockInfo({ token: decrypted.slice(2, 6) }) console.log('默认结果', decrypted[6], this.lockInfo.token) characteristicValueCallback(new Result(decrypted[6])) break } } }, // 获取配置 getConfig() { let config = configs[this.env] return { ...config, version, buildNumber } }, /* * 生成包头 * 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 }, // 断开与设备的连接 async disconnectDevice() { return await closeBLEConnection(this.lockInfo.deviceId) } } })