diff --git a/src/starCloud/.gitignore b/src/starCloud/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/src/starCloud/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/src/starCloud/README.md b/src/starCloud/README.md new file mode 100644 index 0000000..36e3797 --- /dev/null +++ b/src/starCloud/README.md @@ -0,0 +1,168 @@ +## 星云SDK + +### 1. 安装 + +在需要引用的项目中执行命令 + +```git +git subtree add --prefix=starCloud git@code.star-lock.cn:StarlockTeam/starcloud-sdk-uniapp.git master +``` + +更新 + +```git +git subtree pull --prefix=starCloud git@code.star-lock.cn:StarlockTeam/starcloud-sdk-uniapp.git master +``` + +推送 + +```git +git subtree push --prefix=starCloud git@code.star-lock.cn:StarlockTeam/starcloud-sdk-uniapp.git master +``` + +### 2. 需安装的npm包 + +`npm install buffer crc js-md5 pinia sm-crypto` + +pinia是全局状态管理工具,需在main.js中引入 + +```javascript +import * as Pinia from 'pinia' + +const store = Pinia.createPinia() +app.use(store) +``` + +### 3. 使用 + +```javascript +import { useStarCloudStore } from '@/starCloud/starCloud' + +const $starCloud = useStarCloudStore() + +/** + * 初始化星云 + * @param params + * @param {String} params.clientId 客户端Id + * @param {String} params.clientSecret 客户端密码 + * @param {String} params.env 环境 + * @param {Boolean} params.isReportLog 是否上报日志 + */ +$starCloud.initStarCloud(params) + +// 注册,后续所有方法调用返回值均为code, data, message +// code对应报错码有三部分组合构成,锁端报错码+星云服务端报错码+自定义报错码 +// Result类定义了所有自定义报错码,具体报错码请查看Result类 +const { code, data, message } = await $starCloud.register() +if (code === Result.Success.code) { + // 逻辑代码 +} else { + // 错误处理 +} + +/** + * 退出登录 + * @param params + * @param {Number} params.uid 用户ID + */ +const { code, data, message } = await $starCloud.logout(params) + +/** + * 选择锁 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {Number} params.lockId 锁ID + * @returns {Promise} + */ +const { code, data, message } = await $starCloud.selectLock(params) + +/** + * 开门 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {String} params.type 开门方式 close: 关门 open: 开门 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ +const { code, data, message } = await $starCloud.openDoor(params) + +/** + * 获取离线密码 + * @param {AccountInfo} accountInfo 账号信息 + * @param {OfflinePassword} password 密码信息 + * @returns {Promise} + */ +const data = await $starCloud.getOfflinePassword(accountInfo, password) + +/** + * 自定义密码 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {CustomPassword} params.password 密码信息 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ +const data = await $starCloud.customPassword(params) + +/** + * 搜索蓝牙设备 + */ +await $starCloud.searchDevice(searchDevice) +const searchDevice = async result => { +} + +/** + * 停止搜索 + */ +await $starCloud.stopSearchDevice() + +/** + * 绑定设备 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {String} params.name 设备名称 + * @returns {Promise} + */ +const data = await $starCloud.bindDevice(params) + +/** + * 移除坏锁 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {List[int]} params.lockIds 锁Id列表 + * @returns {Promise} + */ +const data = await $starCloud.removeBadLock(params) + +/** + * 删除锁 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + */ +const data = await $starCloud.deleteLock(params) + +/** + * 修改管理员密码 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {String} params.adminPwd 管理员密码 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ +const data = await $starCloud.updateAdminPassword(params) + +/** + * 同步全部开门记录 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ +const data = await $starCloud.syncAllOpenRecord(params) + +/** + * 获取服务器时间 + * @returns {Promise} + */ +const data = await $starCloud.getServerTimestamp() +``` diff --git a/src/starCloud/api.js b/src/starCloud/api.js new file mode 100644 index 0000000..4d31d43 --- /dev/null +++ b/src/starCloud/api.js @@ -0,0 +1,163 @@ +import request from '@/starCloud/request' + +// 创建账号 +export function starCloudCreateUser(data) { + return request({ + url: '/createUser', + method: 'POST', + data + }) +} + +// 获取访问令牌 +export function getStarCloudToken(data) { + return request({ + url: '/oauth2/token', + method: 'POST', + data + }) +} + +// 更新锁用户 +export function updateLockUserNoRequest(data) { + return request({ + url: '/v1/key/updateLockUserNo', + method: 'POST', + data + }) +} + +// 获取所有锁用户 +export function getUserNoListRequest(data) { + return request({ + url: '/v1/key/getUserNoList', + method: 'POST', + data + }) +} + +// 获取手机联网token +export function getLockNetTokenRequest(data) { + return request({ + url: '/v1/lock/getLockNetToken', + method: 'POST', + data + }) +} + +// 获取服务器时间 +export function getServerDatetimeRequest(data) { + return request({ + url: '/v1/lock/queryDate', + method: 'POST', + data + }) +} + +// 获取锁详情 +export function getLockDetailRequest(data) { + return request({ + url: '/v1/lock/detail', + method: 'POST', + data + }) +} + +// 获取离线密码 +export function getOfflinePasswordRequest(data) { + return request({ + url: '/v1/keyboardPwd/get', + method: 'POST', + data + }) +} + +// 添加自定义密码 +export function addCustomPasswordRequest(data) { + return request({ + url: '/v1/keyboardPwd/add', + method: 'POST', + data + }) +} + +// 更新密码 +export function updatePasswordRequest(data) { + return request({ + url: '/v1/keyboardPwd/update', + method: 'POST', + data + }) +} + +// 删除密码 +export function deletePasswordRequest(data) { + return request({ + url: '/v1/keyboardPwd/delete', + method: 'POST', + data + }) +} + +// 绑定智能锁 +export function bindLockRequest(data) { + return request({ + url: '/v1/lock/initialize', + method: 'POST', + data + }) +} + +// 移除坏锁 +export function removeBadLockRequest(data) { + return request({ + url: '/v1/lock/removeBadLock', + method: 'POST', + data + }) +} + +// 删除锁 +export function deleteLockRequest(data) { + return request({ + url: '/v1/lock/delete', + method: 'POST', + data + }) +} + +// 删除锁 +export function updateElectricQuantityRequest(data) { + return request({ + url: '/v1/lock/updateElectricQuantity', + method: 'POST', + data + }) +} + +// 修改管理员密码 +export function changeAdminKeyboardPwdRequest(data) { + return request({ + url: '/v1/lock/changeAdminKeyboardPwd', + method: 'POST', + data + }) +} + +// 获取操作记录的最后上传时间 +export function getLastRecordTimeRequest(data) { + return request({ + url: '/v1/lockRecord/getLastRecordTime', + method: 'POST', + data + }) +} + +// 上传操作记录 +export function uploadRecordRequest(data) { + return request({ + url: '/v1/lockRecord/upload', + method: 'POST', + data + }) +} diff --git a/src/starCloud/basic.js b/src/starCloud/basic.js new file mode 100644 index 0000000..37ced86 --- /dev/null +++ b/src/starCloud/basic.js @@ -0,0 +1,493 @@ +export class Result { + static codes = { + Success: 0, + Fail: -1, + + NotMoreData: -10, + + NotAvailableBluetooth: -20, + NotAvailableBluetoothPermission: -21, + NotAvailableWeChatNearbyDevicesPermission: -22, + NotAvailableWeChatLocationPermission: -23, + NotAvailableWeChatNearbyDevicesEmpty: -24, + NotAvailableWeChatBluetoothPermission: -25, + DeviceHasBeenReset: -30, + + NotRegisteredLock: 4, + NotTokenLock: 6, + NotMoreKeyLock: 12, + ReadyHasKeyLock: 15, + ReadyHasPassword: 251 + } + + static resultsMap = new Map([ + [Result.codes.Success, { message: '成功', data: {} }], + [Result.codes.Fail, { message: '失败', data: {} }], + + [Result.codes.NotMoreData, { message: '没有更多数据', data: {} }], + + [Result.codes.NotAvailableBluetooth, { message: '蓝牙尚未打开,请先打开蓝牙', data: {} }], + [ + Result.codes.NotAvailableBluetoothPermission, + { message: '小程序蓝牙功能被禁用,请打开小程序蓝牙权限', data: {} } + ], + [ + Result.codes.NotAvailableWeChatNearbyDevicesPermission, + { message: '蓝牙功能需要附近设备权限,请前往设置开启微信的附近设备权限后再试', data: {} } + ], + [ + Result.codes.NotAvailableWeChatLocationPermission, + { message: '蓝牙功能需要定位权限,请前往设置开启微信的定位权限后再试', data: {} } + ], + [ + Result.codes.NotAvailableWeChatNearbyDevicesEmpty, + { + message: '蓝牙功能需要定位服务,请前往设置开启定位服务后再试', + data: {} + } + ], + [ + Result.codes.NotAvailableWeChatBluetoothPermission, + { + message: '微信的蓝牙权限被禁用,请前往设置开启微信的蓝牙权限后再试', + data: {} + } + ], + [Result.codes.DeviceHasBeenReset, { message: '设备已被重置', data: {} }], + + [Result.codes.NotRegisteredLock, { message: '用户在锁端未注册', data: {} }], + [Result.codes.NotTokenLock, { message: '用户在锁端token失效', data: {} }], + [Result.codes.NotMoreKeyLock, { message: '锁端钥匙数量已达上限', data: {} }], + [Result.codes.ReadyHasKeyLock, { message: '用户已是锁端用户', data: {} }], + [Result.codes.ReadyHasPassword, { message: '该密码已存在', data: {} }] + ]) + + constructor(code, data, message) { + const result = Result.resultsMap.get(code) + if (result) { + this.code = code + this.message = message || result.message + this.data = data || result.data + } else { + this.code = code + this.message = message || '' + this.data = data || {} + } + } + + // 成功 + static get Success() { + return new Result(Result.codes.Success) + } + + // 失败(默认错误) + static get Fail() { + return new Result(Result.codes.Fail) + } + + // 没有更多数据 + static get NotMoreData() { + return new Result(Result.codes.NotMoreData) + } + + // 蓝牙未开启 + static get NotAvailableBluetooth() { + return new Result(Result.codes.NotAvailableBluetooth) + } + + // 小程序蓝牙权限被禁用 + static get NotAvailableBluetoothPermission() { + return new Result(Result.codes.NotAvailableBluetoothPermission) + } + + // 微信附近的设备权限被禁用 + static get NotAvailableWeChatNearbyDevicesPermission() { + return new Result(Result.codes.NotAvailableWeChatNearbyDevicesPermission) + } + + // 微信定位权限被禁用 + static get NotAvailableWeChatLocationPermission() { + return new Result(Result.codes.NotAvailableWeChatLocationPermission) + } + + // 手机定位服务被关闭 + static get NotAvailableWeChatNearbyDevicesEmpty() { + return new Result(Result.codes.NotAvailableWeChatNearbyDevicesEmpty) + } + + // 微信的蓝牙权限被禁用 + static get NotAvailableWeChatBluetoothPermission() { + return new Result(Result.codes.NotAvailableWeChatBluetoothPermission) + } + + // 设备已被重置 + static get DeviceHasBeenReset() { + return new Result(Result.codes.DeviceHasBeenReset) + } + + // 用户在锁端未注册 + static get NotRegisteredLock() { + return new Result(Result.codes.NotRegisteredLock) + } + + // 用户在锁端token失效 + static get NotTokenLock() { + return new Result(Result.codes.NotTokenLock) + } + + // 锁端钥匙数量已达上限 + static get NotMoreKeyLock() { + return new Result(Result.codes.NotMoreKeyLock) + } + + // 锁端钥匙数量已达上限 + static get ReadyHasKeyLock() { + return new Result(Result.codes.ReadyHasKeyLock) + } + + // 密码已存在 + static get ReadyHasPassword() { + return new Result(Result.codes.ReadyHasPassword) + } +} + +/** + * @typedef {Object} err + * @property {number} errno - 错误代码 + * @property {number} errCode - 错误代码 + * @property {String} errMsg - 错误信息 + */ + +// 查找设备并连接 +export function searchAndConnectDevice(name, reset = true) { + // 循环查找设备 + let timer + // 超时计时器 + let timeoutTimer + + return new Promise(async resolve => { + const result = await startBluetoothDevicesDiscovery() + if (result.code === Result.Success.code) { + let searchFlag = false + timeoutTimer = setTimeout(async () => { + await stopBluetoothDevicesDiscovery() + clearInterval(timer) + if (!searchFlag) { + resolve(Result.NotAvailableWeChatNearbyDevicesEmpty) + } else { + resolve(Result.Fail) + } + }, 10500) + timer = setInterval(async () => { + const queryResult = await getBluetoothDevices() + if (queryResult.code === Result.Success.code) { + const deviceList = queryResult.data + if (searchFlag === false && deviceList.length > 0) { + searchFlag = true + } + for (let i = 0; i < deviceList.length; i++) { + if (deviceList[i]?.name === name) { + const uuid = deviceList[i]?.advertisServiceUUIDs[0] + if (uuid && uuid.slice(2, 8) === '758824') { + await stopBluetoothDevicesDiscovery() + clearTimeout(timeoutTimer) + clearInterval(timer) + if (uuid.slice(30, 32) === '00' && reset) { + resolve(Result.DeviceHasBeenReset) + } else { + const connectResult = await createBLEConnection(deviceList[i].deviceId) + resolve(connectResult) + } + break + } + } + } + } else { + resolve(queryResult) + } + }, 1000) + } else { + resolve(result) + } + }) +} + +// 蓝牙操作报错处理 +async function handleError(err, event) { + if (err.errCode === 10000) { + const result = await openBluetoothAdapter() + if (result.code === Result.Success.code) { + return await event() + } + return result + } + if (err.errCode === 10001) { + if (err.state === 3) { + return Result.NotAvailableWeChatBluetoothPermission + } + return Result.NotAvailableBluetooth + } + if (err.errno === 3) { + return Result.NotAvailableWeChatNearbyDevicesPermission + } + if (err.errno === 103) { + return Result.NotAvailableBluetoothPermission + } + if (err.errno === 1509008) { + return Result.NotAvailableWeChatLocationPermission + } + if (err.errMsg === 'openBluetoothAdapter:fail already opened') { + return Result.Success + } + return Result.Fail +} + +// 初始化蓝牙模块 +function openBluetoothAdapter() { + return new Promise(resolve => { + uni.openBluetoothAdapter({ + success() { + resolve(Result.Success) + }, + async fail(err) { + resolve(await handleError(err)) + } + }) + }) +} + +// 关闭蓝牙模块 +export function closeBluetoothAdapter() { + uni.closeBluetoothAdapter() +} + +// 移除蓝牙适配器的全部监听 +export function offBluetoothAdapterStateChange() { + uni.offBluetoothAdapterStateChange() +} + +// 监听蓝牙特征值改变 +export function onBLECharacteristicValueChange(callback) { + uni.onBLECharacteristicValueChange(res => { + callback(res) + }) +} + +// 开始搜索附近的蓝牙设备 +export function startBluetoothDevicesDiscovery() { + return new Promise(resolve => { + uni.startBluetoothDevicesDiscovery({ + success() { + resolve(Result.Success) + }, + async fail(err) { + resolve(await handleError(err, startBluetoothDevicesDiscovery)) + } + }) + }) +} + +// 获取所有已发现的蓝牙设备 +export function getBluetoothDevices() { + return new Promise(resolve => { + uni.getBluetoothDevices({ + success(res) { + resolve(new Result(Result.Success.code, res.devices)) + }, + async fail(err) { + resolve(await handleError(err, getBluetoothDevices)) + } + }) + }) +} + +// 停止搜索附近的蓝牙设备 +export function stopBluetoothDevicesDiscovery() { + return new Promise(resolve => { + uni.stopBluetoothDevicesDiscovery({ + success() { + resolve(Result.Success) + }, + async fail(err) { + resolve(await handleError(err)) + } + }) + }) +} + +// 连接低功耗蓝牙设备 +export function createBLEConnection(deviceId, reconnectNumber = 0) { + return new Promise(resolve => { + uni.createBLEConnection({ + deviceId, + timeout: 10000, + async success() { + const res = await getBLEDeviceServicesAndCharacteristics(deviceId) + await notifyBLECharacteristicValueChange( + deviceId, + res.data.serviceId, + res.data.notifyCharacteristicId + ) + resolve(res) + }, + async fail(err) { + if (err.errno === 1509007) { + const res = await getBLEDeviceServicesAndCharacteristics(deviceId) + await notifyBLECharacteristicValueChange( + deviceId, + res.data.serviceId, + res.data.notifyCharacteristicId + ) + resolve(res) + } else if (err.errno === 1509001 && reconnectNumber < 1) { + resolve(Result.Fail) + } else if (reconnectNumber < 1) { + resolve(await createBLEConnection(deviceId, reconnectNumber + 1)) + } else { + resolve(Result.Fail) + } + } + }) + }) +} + +// 获取服务及对应特征值 +async function getBLEDeviceServicesAndCharacteristics(deviceId) { + const { code, data } = await getBLEDeviceServices(deviceId) + if (code === Result.Success.code) { + const { serviceId } = data + const { + code, + data: { notifyCharacteristicId, writeCharacteristicId } + } = await getBLEDeviceCharacteristics(deviceId, serviceId) + if (code === Result.Success.code) { + return new Result(Result.Success.code, { + deviceId, + serviceId, + notifyCharacteristicId, + writeCharacteristicId + }) + } + return Result.Fail + } + return Result.Fail +} + +// 获取设备的服务 +function getBLEDeviceServices(deviceId) { + return new Promise(resolve => { + uni.getBLEDeviceServices({ + deviceId, + success(res) { + let serviceId + for (let i = 0; i < res.services.length; i++) { + if (res.services[i].uuid.indexOf('FFF0') !== -1) { + serviceId = res.services[i].uuid + } + } + if (!serviceId) { + resolve(Result.Fail) + return + } + resolve(new Result(Result.Success.code, { serviceId })) + }, + fail() { + resolve(Result.Fail) + } + }) + }) +} + +// 获取服务的特征值 +function getBLEDeviceCharacteristics(deviceId, serviceId) { + return new Promise(resolve => { + uni.getBLEDeviceCharacteristics({ + deviceId, + 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 + } + } + if (notifyCharacteristicId && writeCharacteristicId) { + resolve( + new Result(Result.Success.code, { notifyCharacteristicId, writeCharacteristicId }) + ) + } else { + resolve(Result.Fail) + } + }, + fail() { + resolve(Result.Fail) + } + }) + }) +} + +// 订阅特征值 +function notifyBLECharacteristicValueChange(deviceId, serviceId, characteristicId) { + return new Promise(resolve => { + uni.notifyBLECharacteristicValueChange({ + deviceId, + serviceId, + characteristicId, + state: true, + success() { + resolve(Result.Success) + }, + fail() { + resolve(Result.Fail) + } + }) + }) +} + +// 断开与低功耗蓝牙设备的连接 +export function closeBLEConnection(deviceId) { + return new Promise(resolve => { + uni.closeBLEConnection({ + deviceId, + success() { + console.log('断开连接成功') + resolve(Result.Success) + }, + fail() { + console.log('断开连接失败') + resolve(Result.Fail) + } + }) + }) +} + +// 写入特征值 +export function writeBLECharacteristicValue(deviceId, serviceId, characteristicId, binaryData) { + return new Promise(resolve => { + const count = Math.ceil(binaryData.length / 20) + let successCount = 0 + for (let i = 0; i < count; i++) { + const writeData = binaryData.slice(i * 20, i === count - 1 ? binaryData.length : (i + 1) * 20) + uni.writeBLECharacteristicValue({ + deviceId, + serviceId, + characteristicId, + value: writeData.buffer, + success() { + successCount++ + if (successCount === count) { + resolve(Result.Success) + } + }, + fail() { + resolve(Result.Fail) + } + }) + } + }) +} diff --git a/src/starCloud/env.js b/src/starCloud/env.js new file mode 100644 index 0000000..8c9fe3c --- /dev/null +++ b/src/starCloud/env.js @@ -0,0 +1,28 @@ +// 版本号 +export const version = '1.0.0' +// 构建号 +export const buildNumber = 1 + +// 环境配置 +export const configs = { + DEV: { + name: 'DEV', + baseUrl: 'https://dev.cloud.star-lock.cn/sdk' + }, + PRE: { + name: 'PRE', + baseUrl: 'https://pre.cloud.star-lock.cn/sdk' + }, + PRE_SKY: { + name: 'PRE_SKY', + baseUrl: 'https://pre.cloud.star-lock.cn/sdk' + }, + XHJ: { + name: 'XHJ', + baseUrl: 'https://cloud.xhjcn.ltd/sdk' + }, + SKY: { + name: 'SKY', + baseUrl: 'https://cloud.skychip.top/sdk' + } +} diff --git a/src/starCloud/format.js b/src/starCloud/format.js new file mode 100644 index 0000000..b5afceb --- /dev/null +++ b/src/starCloud/format.js @@ -0,0 +1,79 @@ +import { md5 } from 'js-md5' +import crc from 'crc' + +// 周数组转换 +export function convertWeekdaysToNumber(weekDay) { + let weekStr = '00000000' + + // eslint-disable-next-line no-restricted-syntax + for (const day of weekDay) { + const index = day % 7 + weekStr = weekStr.substring(0, index) + '1' + weekStr.substring(index + 1) + } + + // 倒序 weekStr + weekStr = weekStr.split('').reverse().join('') + + return parseInt(weekStr, 2) +} + +// 时间戳转二进制 +export function 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 +} + +// md5加密 +export function md5Encrypt(text, token, key) { + const length = text.length + 4 + 16 + const md5Array = new Uint8Array(length) + + for (let i = 0; i < text.length; i++) { + md5Array[i] = text.charCodeAt(i) + } + + md5Array.set(token, text.length) + md5Array.set(key, text.length + 4) + + const md5Text = md5(md5Array) + return new Uint8Array(md5Text.match(/.{1,2}/g).map(byte => parseInt(byte, 16))) +} + +// 生成包尾 头部数据+内容数据 +export function createPackageEnd(headArray, contentArray) { + // 拼接头部和内容 + let mergerArray = new Uint8Array(headArray.length + contentArray.length) + mergerArray.set(headArray) + mergerArray.set(contentArray, 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 +} + +// 二进制转时间戳 +export function arrayToTimestamp(array) { + const timestamp = (array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3] + return timestamp >>> 0 +} + +// 二进制转字符串 +export function uint8ArrayToString(uint8Array) { + let str = '' + for (let i = 0; i < uint8Array.length; i++) { + if (uint8Array[i] !== 0) { + str += String.fromCharCode(uint8Array[i]) + } + } + return str +} diff --git a/src/starCloud/log.js b/src/starCloud/log.js new file mode 100644 index 0000000..684e025 --- /dev/null +++ b/src/starCloud/log.js @@ -0,0 +1,50 @@ +import { useStarCloudStore } from '@/starCloud/starCloud' + +const log = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null + +export default { + debug() { + if (!log) return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + // eslint-disable-next-line prefer-spread,prefer-rest-params + log.debug.apply(log, arguments) + }, + info() { + if (!log) return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + // eslint-disable-next-line prefer-spread,prefer-rest-params + log.info.apply(log, arguments) + }, + warn() { + if (!log) return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + // eslint-disable-next-line prefer-spread,prefer-rest-params + log.warn.apply(log, arguments) + }, + error() { + if (!log) return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + // eslint-disable-next-line prefer-spread,prefer-rest-params + log.error.apply(log, arguments) + }, + setFilterMsg(msg) { + // 从基础库2.7.3开始支持 + if (!log || !log.setFilterMsg) return + if (typeof msg !== 'string') return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + log.setFilterMsg(msg) + }, + addFilterMsg(msg) { + // 从基础库2.8.1开始支持 + if (!log || !log.addFilterMsg) return + if (typeof msg !== 'string') return + const $starCloud = useStarCloudStore() + if (!$starCloud.isReportLog) return + log.addFilterMsg(msg) + } +} diff --git a/src/starCloud/request.js b/src/starCloud/request.js new file mode 100644 index 0000000..58b2073 --- /dev/null +++ b/src/starCloud/request.js @@ -0,0 +1,105 @@ +import { getStorage, removeStorage } from '@/starCloud/storage' +import { useStarCloudStore } from '@/starCloud/starCloud' +import { Result } from '@/starCloud/basic' + +/* + * config + * baseUrl: 请求域名 + * url: 请求路径 + * method: 请求方法 + * header: 请求头 + * token: 请求token + * data: 请求参数 + * */ + +const request = config => { + const starCloud = useStarCloudStore() + let timer + return new Promise(async resolve => { + const baseConfig = starCloud.getConfig() + + const token = config?.token ? config.token : getStorage('starCloudToken') + // 请求地址 + const URL = config.baseUrl ? config.baseUrl + config.url : baseConfig.baseUrl + config.url + + // 默认请求头 + const headerDefault = { + version: baseConfig.version + '+' + baseConfig.buildNumber + } + const header = { + ...headerDefault, + ...config.header + } + const method = config.method || 'POST' + const data = { + ...config.data, + accessToken: token, + clientId: starCloud.clientId + } + const timestamp = new Date().getTime() + + // 超时处理 + timer = setTimeout(() => { + resolve(new Result(Result.Fail.code, {}, '网络访问失败,请检查网络是否正常')) + }, 3200) + + uni.request({ + url: URL, + method, + header, + data, + timeout: 3000, + async success(res) { + const { statusCode, data } = res + if (timer) { + clearTimeout(timer) + } + if (statusCode === 200) { + const code = data.errcode + const message = data.errmsg + if (code === 10003) { + removeStorage('starCloudToken') + removeStorage('starCloudUser') + const { code } = await starCloud.login({ + username: starCloud.starCloudAccountInfo.username, + password: starCloud.starCloudAccountInfo.password, + uid: starCloud.starCloudAccountInfo.uid + }) + if (code === Result.Success.code) { + resolve(await request(config)) + } + } else { + resolve({ + code, + data: data.data, + message + }) + } + } else { + resolve(new Result(Result.Fail.code, {}, '网络访问失败,请检查网络是否正常')) + } + }, + async fail(res) { + console.log('网络访问失败', res) + if (timer) { + clearTimeout(timer) + } + resolve(new Result(Result.Fail.code, {}, '网络访问失败,请检查网络是否正常')) + }, + async complete(res) { + console.log(URL.substring(baseConfig.baseUrl.length + 1), { + env: baseConfig.name, + url: URL.substring(baseConfig.baseUrl.length + 1), + req: config?.data || {}, + code: res?.data?.errcode, + res: res?.data?.data, + token: header?.authorization || '', + message: res?.data?.errmsg, + duration: new Date().getTime() - timestamp + }) + } + }) + }) +} + +export default request diff --git a/src/starCloud/starCloud.js b/src/starCloud/starCloud.js new file mode 100644 index 0000000..6977b39 --- /dev/null +++ b/src/starCloud/starCloud.js @@ -0,0 +1,2050 @@ +import { defineStore } from 'pinia' +import { sm4 } from 'sm-crypto' +import { buildNumber, configs, version } from '@/starCloud/env' +import { + addCustomPasswordRequest, + bindLockRequest, + changeAdminKeyboardPwdRequest, + deleteLockRequest, + deletePasswordRequest, + getLastRecordTimeRequest, + getLockDetailRequest, + getLockNetTokenRequest, + getOfflinePasswordRequest, + getServerDatetimeRequest, + getStarCloudToken, + getUserNoListRequest, + removeBadLockRequest, + starCloudCreateUser, + updateElectricQuantityRequest, + updateLockUserNoRequest, + updatePasswordRequest, + uploadRecordRequest +} 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' +import log from '@/starCloud/log' + +/** + * 离线密码 + * 该功能无需蓝牙交互直接请求服务端,详细参数说明请看星云接口文档 + * @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 = { + // 修改管理员密码 + updateAdminPassword: 2, + // 设置开锁密码 + setLockPassword: 3, + // 重置开锁密码 + resetLockPassword: 19, + // 同步开门记录 + syncOpenRecord: 41 +} + +// 特性值回调 +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, + // 是否上报日志 + isReportLog: false + } + }, + actions: { + /** + * 初始化星云 + * @param params + * @param {String} params.clientId 客户端Id + * @param {String} params.clientSecret 客户端密码 + * @param {String} params.env 环境 + * @param {Boolean} params.isReportLog 是否上报日志 + */ + initStarCloud(params) { + const { clientId, clientSecret, env } = params + const appInfo = uni.getAccountInfoSync() + this.envVersion = appInfo.miniProgram.envVersion + + this.isReportLog = params.isReportLog + + 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) { + this.lockInfo = data + let lockList = getStorage('starLockList') + if (!lockList) { + lockList = {} + } + if (lockList[accountInfo.uid]) { + const index = lockList[accountInfo.uid].findIndex(item => item.lockId === lockId) + if (index === -1) { + lockList[accountInfo.uid].push(this.lockInfo) + } else { + this.lockInfo.token = lockList[accountInfo.uid][index].token + lockList[accountInfo.uid][index] = this.lockInfo + } + setStorage('starLockList', lockList) + } else { + lockList[accountInfo.uid] = [this.lockInfo] + setStorage('starLockList', lockList) + } + } else { + const lockList = getStorage('starLockList') + 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 + + log.info( + new Result( + Result.Success.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `开始开门` + ) + ) + + // 设置执行账号 + const result = await this.login(accountInfo) + if (result.code !== Result.Success.code) { + return result + } + + log.info( + new Result( + result.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `登录星云账号: ${result.message}` + ) + ) + + // 确认设备连接正常 + if (!params.connected) { + const searchResult = await searchAndConnectDevice( + this.lockInfo.bluetooth.bluetoothDeviceName + ) + log.info( + new Result( + searchResult.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `连接设备: ${searchResult.message}` + ) + ) + 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 + } + + log.info( + new Result( + checkResult.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `确认是否为锁用户: ${checkResult.message}` + ) + ) + + // 是否需要联网 + 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 + } + } + + log.info( + new Result( + checkResult.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `判断是否需要联网token: ${this.lockInfo.lockSetting.appUnlockOnline}` + ) + ) + + // 开门方式 + 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) + + log.info( + new Result( + Result.Success.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `开始写入` + ) + ) + + const writeResult = await writeBLECharacteristicValue( + this.lockInfo.deviceId, + this.lockInfo.serviceId, + this.lockInfo.writeCharacteristicId, + packageArray + ) + + if (writeResult.code !== Result.Success.code) { + return writeResult + } + + log.info( + new Result( + writeResult.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + uid: accountInfo.uid, + time: new Date().getTime() + }, + `写入完成:${writeResult.message}` + ) + ) + + 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 + } + // 确认设备连接正常 + if (!params.connected) { + 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 + }, + /** + * 移除坏锁 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {List[int]} params.lockIds 锁Id列表 + * @returns {Promise} + */ + async removeBadLock(params) { + const { accountInfo, lockIds } = params + // 设置执行账号 + const result = await this.login(accountInfo) + if (result.code !== Result.Success.code) { + return result + } + const { code, message } = await removeBadLockRequest({ + lockIds: params.lockIds + }) + if (code === Result.Success.code) { + const lockList = getStorage('starLockList') + if (lockList[accountInfo.uid]) { + lockIds.forEach(lockId => { + const index = lockList[accountInfo.uid].findIndex(item => item.lockId === lockId) + if (index !== -1) { + lockList[accountInfo.uid].splice(index, 1) + } + }) + setStorage('starLockList', lockList) + } + } + + return new Result(code, {}, message) + }, + /** + * 删除锁 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + */ + async deleteLock(params) { + const { accountInfo } = params + // 设置执行账号 + const result = await this.login(accountInfo) + if (result.code !== Result.Success.code) { + return result + } + // 确认设备连接正常 + if (!params.connected) { + 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 + } + + const { + token, + bluetooth: { publicKey, privateKey } + } = this.lockInfo + + const authUid = this.lockInfo.uid.toString() + const name = this.lockInfo.bluetooth.bluetoothDeviceName + + const length = 2 + 40 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const contentArray = new Uint8Array(length) + + contentArray[0] = cmdIds.resetDevice / 256 + contentArray[1] = cmdIds.resetDevice % 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) + } + contentArray.set(token || new Uint8Array([0, 0, 0, 0]), 62) + contentArray[66] = 16 + + const md5Array = md5Encrypt(name, token || new Uint8Array([0, 0, 0, 0]), publicKey) + contentArray.set(md5Array, 67) + + const cebArray = sm4.encrypt(contentArray, 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.deleteLock, params) + }, + /** + * 修改管理员密码 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {String} params.adminPwd 管理员密码 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ + async updateAdminPassword(params) { + const { adminPwd, accountInfo } = params + + // 设置执行账号 + const result = await this.login(accountInfo) + if (result.code !== Result.Success.code) { + return result + } + + // 确认设备连接正常 + if (!params.connected) { + 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 = params + + const uid = this.lockInfo.uid.toString() + const keyId = this.lockInfo.keyId.toString() + const pwdNo = 1 + const userCountLimit = 0xff + const startDate = Math.floor(this.lockInfo.startDate / 1000) + const endDate = Math.floor(this.lockInfo.endDate / 1000) + + const length = 2 + 1 + 1 + 40 + 20 + 2 + 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.updateAdminPassword + + 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 + + for (let i = 0; i < adminPwd.length; i++) { + contentArray[i + 66] = adminPwd.charCodeAt(i) + } + + contentArray[86] = userCountLimit / 256 + contentArray[87] = userCountLimit % 256 + + contentArray.set(this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), 88) + + contentArray.set(timestampToArray(startDate), 92) + contentArray.set(timestampToArray(endDate), 96) + + contentArray[100] = 16 + + const md5Array = md5Encrypt( + keyId + uid, + this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), + this.lockInfo.bluetooth.signKey + ) + + contentArray.set(md5Array, 101) + + 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.updateAdminPassword, params) + }, + /** + * 同步全部开门记录 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ + async syncAllOpenRecord(params) { + const { accountInfo, disconnect } = params + + const { code, data, message } = await this.syncOpenRecord({ + accountInfo, + disconnect: false + }) + + if (code === Result.Success.code) { + if (data.count === 10) { + return await this.syncAllOpenRecord({ + accountInfo, + disconnect + }) + } + if (disconnect) { + await this.disconnectDevice() + } + return new Result(code, data, message) + } + return new Result(code, data, message) + }, + /** + * 同步开门记录 + * @param params + * @param {AccountInfo} params.accountInfo 账号信息 + * @param {Boolean} params.disconnect 操作后是否断开连接 + * @returns {Promise} + */ + async syncOpenRecord(params) { + const { accountInfo } = params + + // 设置执行账号 + const result = await this.login(accountInfo) + if (result.code !== Result.Success.code) { + return result + } + + // 确认设备连接正常 + if (!params.connected) { + 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 + } + + const uid = this.lockInfo.uid.toString() + const keyId = this.lockInfo.keyId.toString() + const logsCount = 10 + + const timeResult = await getLastRecordTimeRequest({ + lockId: this.lockInfo.lockId + }) + + if (timeResult.code !== Result.Success.code) { + return timeResult + } + + const operateDate = Math.ceil(timeResult.data.operateDate / 1000) + const currentDate = Math.ceil(timeResult.data.currentDate / 1000) + + const length = 2 + 1 + 1 + 40 + 20 + 2 + 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.syncOpenRecord + + 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] = logsCount / 256 + contentArray[65] = logsCount % 256 + + contentArray.set(timestampToArray(operateDate), 66) + contentArray.set(timestampToArray(currentDate), 70) + + contentArray[74] = 16 + + const md5Array = md5Encrypt( + uid + keyId, + this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), + this.lockInfo.bluetooth.publicKey + ) + + contentArray.set(md5Array, 75) + + 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.syncOpenRecord, params) + }, + // 获取服务器时间 + 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) + }, + + /** + * 清理用户 + * @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 addLockUser(params) { + const { params: data } = params + + // 确认设备连接正常 + if (!params.connected) { + 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) { + log.info( + new Result( + data.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + time: new Date().getTime() + }, + `token过期:${data.message}` + ) + ) + resolve(await request({ ...params, connected: true })) + } else if (data.code === Result.NotRegisteredLock.code) { + const checkResult = await this.checkLockUser(true) + if (checkResult.code === Result.Success.code) { + resolve(await request({ ...params, connected: true })) + } else { + clearTimeout(getWriteResultTimer) + resolve(checkResult) + } + } else { + clearTimeout(getWriteResultTimer) + if (params.disconnect) { + await this.disconnectDevice() + } + console.log('写入结果', data, request, params) + log.info( + new Result( + data.code, + { + lockName: this.lockInfo.bluetooth.bluetoothDeviceName, + lockId: this.lockInfo.lockId, + time: new Date().getTime() + }, + `开门结果:${data.message}` + ) + ) + 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 + } + + const lockList = getStorage('starLockList') + if (lockList[this.accountInfo.uid]) { + const index = lockList[this.accountInfo.uid].findIndex( + item => item.lockId === this.lockInfo.lockId + ) + if (index !== -1) { + lockList[this.accountInfo.uid][index] = this.lockInfo + } + setStorage('starLockList', lockList) + } + }, + // 特征值变化回调 + 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.updateAdminPassword: + this.updateLockInfo({ + token: decrypted.slice(5, 9) + }) + if (decrypted[2] === Result.Success.code) { + const result = await changeAdminKeyboardPwdRequest({ + password: requestParams.adminPwd, + lockId: this.lockInfo.lockId + }) + return characteristicValueCallback(new Result(result.code)) + } + characteristicValueCallback(new Result(decrypted[2])) + + break + 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 + case subCmdIds.syncOpenRecord: + if (decrypted[2] === Result.Success.code && decrypted[6] > 0) { + const records = [] + const count = decrypted[6] || 0 + for (let i = 0; i < count; i++) { + let password = decrypted.slice(14 + 17 * i, 14 + 17 * i + 10) + if (password.every(item => item === 0)) { + password = null + } else { + password = uint8ArrayToString(password) + } + const record = { + type: decrypted[7 + 17 * i], + user: decrypted[8 + 17 * i] * 256 + decrypted[9 + 17 * i], + date: arrayToTimestamp(decrypted.slice(10 + 17 * i, 14 + 17 * i)) * 1000, + success: 1, + password + } + records.push(record) + } + const { code, message } = await uploadRecordRequest({ + records, + lockId: this.lockInfo.lockId + }) + characteristicValueCallback( + new Result( + code, + { + count + }, + message + ) + ) + } else { + characteristicValueCallback(new Result(decrypted[2])) + } + break + default: + break + } + break + case cmdIds.openDoor: + this.updateLockInfo({ + token: decrypted.slice(2, 6), + electricQuantity: decrypted[7], + electricQuantityStandby: decrypted[9] + }) + if (decrypted[6] === Result.Success.code) { + updateElectricQuantityRequest({ + lockId: this.lockInfo.lockId, + electricQuantity: decrypted[7], + electricQuantityStandby: decrypted[9] + }) + } + characteristicValueCallback(new Result(decrypted[6], { lock: this.lockInfo })) + break + case cmdIds.resetDevice: + this.updateLockInfo({ + token: decrypted.slice(2, 6) + }) + if (decrypted[6] === Result.Success.code) { + const { code, message } = await deleteLockRequest({ + lockId: this.lockInfo.lockId + }) + if (code === Result.Success.code) { + const lockList = getStorage('starLockList') + if (lockList[this.accountInfo.uid]) { + const index = lockList[this.accountInfo.uid].findIndex( + item => item.lockId === this.lockInfo.lockId + ) + if (index !== -1) { + lockList[this.accountInfo.uid].splice(index, 1) + } + setStorage('starLockList', lockList) + } + } + characteristicValueCallback(new Result(code, {}, message)) + } else { + 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) + } + } +}) diff --git a/src/starCloud/storage.js b/src/starCloud/storage.js new file mode 100644 index 0000000..febe265 --- /dev/null +++ b/src/starCloud/storage.js @@ -0,0 +1,18 @@ +import { useStarCloudStore } from '@/starCloud/starCloud' + +export function setStorage(key, value) { + return uni.setStorageSync(getPrefix() + key, value) +} + +export function getStorage(key) { + return uni.getStorageSync(getPrefix() + key) +} + +export function removeStorage(key) { + return uni.removeStorageSync(getPrefix() + key) +} + +function getPrefix() { + const starCloud = useStarCloudStore() + return `${starCloud.envVersion}:` +}