From 64beacd03582129c5a9f232dffa6ed9badb5d66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=83=E9=B9=8F?= Date: Wed, 30 Oct 2024 14:09:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E9=94=81=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 ++++- api.js | 19 +++- basic.js | 21 ++-- starCloud.js | 310 ++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 347 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index f87dac4..7dac3f5 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,30 @@ ## 星云SDK ### 1. 安装 + 在需要引用的项目中执行命令 `git subtree add --prefix=starCloud git@code.star-lock.cn:xhj/starcloud-sdk-uniapp.git master` 更新 `git subtree pull --prefix=starCloud git@code.star-lock.cn:xhj/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() /** @@ -31,7 +39,7 @@ $starCloud.initStarCloud(clientId, clientSecret, env) // code对应报错码有三部分组合构成,锁端报错码+星云服务端报错码+自定义报错码 // Result类定义了所有自定义报错码,具体报错码请查看Result类 const { code, data, message } = await $starCloud.register() -if(code === Result.Success.code) { +if (code === Result.Success.code) { // 逻辑代码 } else { // 错误处理 @@ -48,6 +56,7 @@ const { code, data, message } = await $starCloud.login(username, password) * 选择锁 * @property {Number} lockId - 锁Id */ +// 所需信息data中会返回,如data中没有可通过$starCloud.lockInfo获取 const { code, data, message } = await $starCloud.selectLock(lockId) /** @@ -109,4 +118,22 @@ const data = await $starCloud.customPassword({ operate: 0, pwdRight: 0 }, true) + +/** + * 搜索蓝牙设备 + */ +await $starCloud.searchDevice(searchDevice) +const searchDevice = async result => { +} + +/** + * 停止搜索 + */ +await $starCloud.stopSearchDevice() + +/** + * 连接蓝牙设备 + * @property {String} name - 设备名称 + */ +const data = await $starCloud.bindDevice(name) ``` diff --git a/api.js b/api.js index d3a7b6b..2be80c1 100644 --- a/api.js +++ b/api.js @@ -46,7 +46,7 @@ export function getLockNetTokenRequest(data) { } // 获取服务器时间 -export function getServerDatetime(data) { +export function getServerDatetimeRequest(data) { return request({ url: '/v1/lock/queryDate', method: 'POST', @@ -55,7 +55,7 @@ export function getServerDatetime(data) { } // 获取锁详情 -export function getLockDetail(data) { +export function getLockDetailRequest(data) { return request({ url: '/v1/lock/detail', method: 'POST', @@ -64,7 +64,7 @@ export function getLockDetail(data) { } // 获取离线密码 -export function getOfflinePassword(data) { +export function getOfflinePasswordRequest(data) { return request({ url: '/v1/keyboardPwd/get', method: 'POST', @@ -73,7 +73,7 @@ export function getOfflinePassword(data) { } // 添加自定义密码 -export function addCustomPassword(data) { +export function addCustomPasswordRequest(data) { return request({ url: '/v1/keyboardPwd/add', method: 'POST', @@ -82,10 +82,19 @@ export function addCustomPassword(data) { } // 更新密码 -export function updatePassword(data) { +export function updatePasswordRequest(data) { return request({ url: '/v1/keyboardPwd/update', method: 'POST', data }) } + +// 绑定智能锁 +export function bindLockRequest(data) { + return request({ + url: '/v1/lock/initialize', + method: 'POST', + data + }) +} diff --git a/basic.js b/basic.js index 8463313..95be775 100644 --- a/basic.js +++ b/basic.js @@ -36,7 +36,10 @@ export class Result { ], [ Result.codes.NotAvailableWeChatNearbyDevicesEmpty, - { message: '微信附近的设备权限无法使用', data: {} } + { + message: '手机定位服务被关闭', + data: {} + } ], [Result.codes.DeviceHasBeenReset, { message: '设备已被重置', data: {} }], @@ -133,7 +136,7 @@ export class Result { */ // 查找设备并连接 -export function searchAndConnectDevice(name) { +export function searchAndConnectDevice(name, reset = true) { // 循环查找设备 let timer // 超时计时器 @@ -166,13 +169,11 @@ export function searchAndConnectDevice(name) { await stopBluetoothDevicesDiscovery() clearTimeout(timeoutTimer) clearInterval(timer) - if (uuid.slice(30, 32) === '00') { + if (uuid.slice(30, 32) === '00' && reset) { resolve(Result.DeviceHasBeenReset) - } else if (uuid.slice(30, 32) === '01') { + } else { const connectResult = await createBLEConnection(deviceList[i].deviceId) resolve(connectResult) - } else { - resolve(Result.Fail) } break } @@ -256,7 +257,7 @@ export function onBLECharacteristicValueChange(callback) { } // 开始搜索附近的蓝牙设备 -function startBluetoothDevicesDiscovery() { +export function startBluetoothDevicesDiscovery() { return new Promise(resolve => { uni.startBluetoothDevicesDiscovery({ success() { @@ -270,7 +271,7 @@ function startBluetoothDevicesDiscovery() { } // 获取所有已发现的蓝牙设备 -function getBluetoothDevices() { +export function getBluetoothDevices() { return new Promise(resolve => { uni.getBluetoothDevices({ success(res) { @@ -284,7 +285,7 @@ function getBluetoothDevices() { } // 停止搜索附近的蓝牙设备 -function stopBluetoothDevicesDiscovery() { +export function stopBluetoothDevicesDiscovery() { return new Promise(resolve => { uni.stopBluetoothDevicesDiscovery({ success() { @@ -298,7 +299,7 @@ function stopBluetoothDevicesDiscovery() { } // 连接低功耗蓝牙设备 -function createBLEConnection(deviceId, reconnectNumber = 0) { +export function createBLEConnection(deviceId, reconnectNumber = 0) { return new Promise(resolve => { uni.createBLEConnection({ deviceId, diff --git a/starCloud.js b/starCloud.js index 2d8f38a..6bd215b 100644 --- a/starCloud.js +++ b/starCloud.js @@ -2,23 +2,28 @@ import { defineStore } from 'pinia' import { sm4 } from 'sm-crypto' import { buildNumber, configs, version } from '@/starCloud/env' import { - addCustomPassword, - getLockDetail, + addCustomPasswordRequest, + bindLockRequest, + getLockDetailRequest, getLockNetTokenRequest, - getOfflinePassword, - getServerDatetime, + getOfflinePasswordRequest, + getServerDatetimeRequest, getStarCloudToken, getUserNoListRequest, starCloudCreateUser, updateLockUserNoRequest, - updatePassword + updatePasswordRequest } from '@/starCloud/api' import { getStorage, setStorage } from '@/starCloud/storage' import { closeBLEConnection, + createBLEConnection, + getBluetoothDevices, onBLECharacteristicValueChange, Result, searchAndConnectDevice, + startBluetoothDevicesDiscovery, + stopBluetoothDevicesDiscovery, writeBLECharacteristicValue } from '@/starCloud/basic' import { @@ -125,6 +130,8 @@ let completeArray let length // 请求参数 let requestParams = null +// 计时器 +let timer export const useStarCloudStore = defineStore('starCloud', { state() { @@ -150,7 +157,9 @@ export const useStarCloudStore = defineStore('starCloud', { // 时间差 timeDifference: 0, // 小程序环境 - envVersion: '' + envVersion: '', + // 搜索设备列表 + searchDeviceList: [] } }, actions: { @@ -211,7 +220,7 @@ export const useStarCloudStore = defineStore('starCloud', { }, // 选择锁 async selectLock(lockId) { - const { code, data, message } = await getLockDetail({ + const { code, data, message } = await getLockDetailRequest({ lockId }) if (code === Result.Success.code) { @@ -427,7 +436,7 @@ export const useStarCloudStore = defineStore('starCloud', { }, // 获取离线密码 async getOfflinePassword(password) { - return await getOfflinePassword({ + return await getOfflinePasswordRequest({ ...password, lockId: this.lockInfo.lockId }) @@ -531,6 +540,272 @@ export const useStarCloudStore = defineStore('starCloud', { return this.getWriteResult(this.customPassword, disconnect, password) }, + // 搜索设备 + 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, { + list + }) + ) + } else { + callback(queryResult) + } + }, 1000) + } else { + callback(result) + } + }, + + // 停止搜索 + async stopSearchDevice() { + console.log('停止搜索') + if (timer) { + clearInterval(timer) + } + + return await stopBluetoothDevicesDiscovery() + }, + + // 绑定设备 + async bindDevice(name) { + 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( + { + name: this.lockInfo.bluetooth.bluetoothDeviceName, + keyId: '0', + authUid: this.userInfo.uid.toString(), + uid: this.userInfo.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 + }, + true + ) + if (addUserResult.code !== Result.Success.code) { + return addUserResult + } + 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 + }) + } + return bindLockResult + } + return connectResult + }, + + // 获取公钥 + 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, 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.userInfo.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, 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.userInfo.uid.toString() + await this.getServerTimestamp() + const nowTime = this.serverTimestamp + const date = new Date() + const localTime = Math.floor(date.getTime() / 1000) - 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, false) + }, // 获取联网token async getNetToken() { const { code, data, message } = await getLockNetTokenRequest({ @@ -540,7 +815,7 @@ export const useStarCloudStore = defineStore('starCloud', { }, // 获取服务器时间 async getServerTimestamp() { - const { code, data, message } = await getServerDatetime({}) + 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) @@ -550,7 +825,10 @@ export const useStarCloudStore = defineStore('starCloud', { // 添加用户 async addLockUser(data, disconnect = false) { // 确认设备连接正常 - const searchResult = await searchAndConnectDevice(this.lockInfo.bluetooth.bluetoothDeviceName) + const searchResult = await searchAndConnectDevice( + this.lockInfo.bluetooth.bluetoothDeviceName, + data.role !== 0xff + ) if (searchResult.code !== Result.Success.code) { return searchResult } @@ -806,12 +1084,12 @@ export const useStarCloudStore = defineStore('starCloud', { bluetooth: { ...this.lockInfo.bluetooth, privateKey: decrypted.slice(3, 19), - signKey: decrypted.slice(19, 35), - pwdTimestamp: arrayToTimestamp(decrypted.slice(35, 39)) * 1000 - } + 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)) + console.log('signKey', Array.from(this.lockInfo.bluetooth.signKey)) } characteristicValueCallback(new Result(decrypted[2])) } @@ -893,7 +1171,7 @@ export const useStarCloudStore = defineStore('starCloud', { if (decrypted[11] === Result.Success.code) { const pwdNo = decrypted[9] * 256 + decrypted[10] if (requestParams.operate === 0) { - const addResult = await addCustomPassword({ + const addResult = await addCustomPasswordRequest({ ...requestParams, pwdUserNo: pwdNo, lockId: this.lockInfo.lockId @@ -912,7 +1190,7 @@ export const useStarCloudStore = defineStore('starCloud', { ) } } else if (requestParams.operate === 1) { - const updateResult = await updatePassword({ + const updateResult = await updatePasswordRequest({ ...requestParams, keyboardPwdId: this.lockInfo.keyboardPwdId })