1016 lines
33 KiB
JavaScript
1016 lines
33 KiB
JavaScript
import { defineStore } from 'pinia'
|
||
import { sm4 } from 'sm-crypto'
|
||
import { buildNumber, configs, version } from '@/starCloud/env'
|
||
import {
|
||
addCustomPassword,
|
||
getLockDetail,
|
||
getLockNetTokenRequest,
|
||
getOfflinePassword,
|
||
getServerDatetime,
|
||
getStarCloudToken,
|
||
getUserNoListRequest,
|
||
starCloudCreateUser,
|
||
updateLockUserNoRequest,
|
||
updatePassword
|
||
} from '@/starCloud/api'
|
||
import { getStorage, setStorage } from '@/starCloud/storage'
|
||
import {
|
||
closeBLEConnection,
|
||
onBLECharacteristicValueChange,
|
||
Result,
|
||
searchAndConnectDevice,
|
||
writeBLECharacteristicValue
|
||
} from '@/starCloud/basic'
|
||
import {
|
||
arrayToTimestamp,
|
||
convertWeekdaysToNumber,
|
||
createPackageEnd,
|
||
md5Encrypt,
|
||
timestampToArray,
|
||
uint8ArrayToString
|
||
} from '@/starCloud/format'
|
||
|
||
/**
|
||
* 锁信息
|
||
* @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
|
||
|
||
export const useStarCloudStore = defineStore('starCloud', {
|
||
state() {
|
||
return {
|
||
// 环境
|
||
env: null,
|
||
// 客户端Id
|
||
clientId: null,
|
||
// 客户端密码
|
||
clientSecret: null,
|
||
// 星云是否登录
|
||
loginStarCloud: false,
|
||
// 星云用户信息
|
||
userInfo: null,
|
||
// 账户信息
|
||
accountInfo: null,
|
||
// 锁信息
|
||
lockInfo: null,
|
||
// 消息序号
|
||
messageCount: 1,
|
||
// 服务器时间
|
||
serverTimestamp: 0,
|
||
// 时间差
|
||
timeDifference: 0,
|
||
// 小程序环境
|
||
envVersion: ''
|
||
}
|
||
},
|
||
actions: {
|
||
// 初始化星云
|
||
initStarCloud(clientId, clientSecret, env = 'XHJ') {
|
||
const appInfo = uni.getAccountInfoSync()
|
||
this.envVersion = appInfo.miniProgram.envVersion
|
||
|
||
this.env = env
|
||
this.clientId = clientId
|
||
this.clientSecret = clientSecret
|
||
// 监听特性值变化
|
||
onBLECharacteristicValueChange(this.listenCharacteristicValue)
|
||
},
|
||
// 注册星云
|
||
async register() {
|
||
const { code, data, message } = await starCloudCreateUser({
|
||
clientId: this.clientId,
|
||
clientSecret: this.clientSecret
|
||
})
|
||
return new Result(code, data, message)
|
||
},
|
||
// 登录星云
|
||
async login(username, password) {
|
||
this.accountInfo = {
|
||
username,
|
||
password
|
||
}
|
||
|
||
const token = getStorage('starCloudToken')
|
||
if (token) {
|
||
this.userInfo = getStorage('starCloudUser')
|
||
this.loginStarCloud = true
|
||
// 获取服务器时间
|
||
this.getServerTimestamp().then(() => {})
|
||
return Result.Success
|
||
}
|
||
console.log('登录星云', username, password)
|
||
const {
|
||
code,
|
||
data: userInfo,
|
||
message
|
||
} = await getStarCloudToken({
|
||
username,
|
||
password,
|
||
clientId: this.clientId,
|
||
clientSecret: this.clientSecret
|
||
})
|
||
if (code === Result.Success.code) {
|
||
this.userInfo = userInfo
|
||
setStorage('starCloudToken', userInfo.access_token)
|
||
setStorage('starCloudUser', userInfo)
|
||
this.loginStarCloud = true
|
||
// 获取服务器时间
|
||
this.getServerTimestamp().then(() => {})
|
||
}
|
||
return new Result(code, {}, message)
|
||
},
|
||
// 选择锁
|
||
async selectLock(lockId) {
|
||
const { code, data, message } = await getLockDetail({
|
||
lockId
|
||
})
|
||
if (code === Result.Success.code) {
|
||
const lockList = getStorage('lockList')
|
||
if (lockList) {
|
||
const index = lockList.findIndex(item => item.lockId === lockId)
|
||
if (index === -1) {
|
||
lockList.push(data)
|
||
} else {
|
||
lockList[index] = data
|
||
}
|
||
setStorage('lockList', lockList)
|
||
} else {
|
||
setStorage('lockList', [data])
|
||
}
|
||
this.lockInfo = data
|
||
} else {
|
||
const lockList = getStorage('lockList')
|
||
console.log('锁列表', lockList)
|
||
if (lockList) {
|
||
const index = lockList.findIndex(item => item.lockId === lockId)
|
||
if (index !== -1) {
|
||
this.lockInfo = lockList[index]
|
||
return new Result(Result.Success.code, this.lockInfo)
|
||
}
|
||
}
|
||
}
|
||
return new Result(code, data, message)
|
||
},
|
||
// 清理用户
|
||
async cleanLockUser(disconnect = false) {
|
||
// 确认设备连接正常
|
||
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)
|
||
},
|
||
// 开门
|
||
// eslint-disable-next-line default-param-last
|
||
async openDoor(type = 'open', disconnect) {
|
||
// 确认设备连接正常
|
||
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.userInfo.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, disconnect)
|
||
},
|
||
// 获取离线密码
|
||
async getOfflinePassword(password) {
|
||
return await getOfflinePassword({
|
||
...password,
|
||
lockId: this.lockInfo.lockId
|
||
})
|
||
},
|
||
// 自定义密码
|
||
async customPassword(password, disconnect) {
|
||
// 确认设备连接正常
|
||
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.userInfo.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, disconnect, password)
|
||
},
|
||
|
||
// 获取联网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 getServerDatetime({})
|
||
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(data, disconnect = false) {
|
||
// 确认设备连接正常
|
||
const searchResult = await searchAndConnectDevice(this.lockInfo.bluetooth.bluetoothDeviceName)
|
||
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, disconnect, data)
|
||
},
|
||
// 获取写入结果
|
||
// eslint-disable-next-line default-param-last
|
||
getWriteResult(request, disconnect = false, 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 (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.userInfo.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(addUserParams)
|
||
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.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.token)
|
||
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 addCustomPassword({
|
||
...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
|
||
})
|
||
)
|
||
} else {
|
||
characteristicValueCallback(
|
||
new Result(addResult.code, addResult.data, addResult.message)
|
||
)
|
||
}
|
||
} else if (requestParams.operate === 1) {
|
||
const updateResult = await updatePassword({
|
||
...requestParams,
|
||
keyboardPwdId: this.lockInfo.keyboardPwdId
|
||
})
|
||
if (updateResult.code === Result.Success.code) {
|
||
characteristicValueCallback(new Result(updateResult.code))
|
||
} else {
|
||
characteristicValueCallback(
|
||
new Result(updateResult.code, updateResult.data, updateResult.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)
|
||
}
|
||
}
|
||
})
|