diff --git a/star-cloud.js b/star-cloud.js index f04f0e0..6beb3c1 100644 --- a/star-cloud.js +++ b/star-cloud.js @@ -9,6 +9,7 @@ import * as common from './common.js' import * as elec from './star-cloud/elec.js' import * as coldWater from './star-cloud/coldWater.js' import * as hotWater from './star-cloud/hotWater.js' +import * as generalExtend from './star-cloud/generalExtend.js' import { onBLECharacteristicValueChange } from './uni/basic' /** @@ -140,7 +141,7 @@ class StarCloud { * @param {Boolean} params.isReportLog 是否上报日志 */ init(params) { - Object.assign(StarCloud.prototype, device, lock, other, password, record, user, common, elec,coldWater, hotWater) + Object.assign(StarCloud.prototype, device, lock, other, password, record, user, common, elec,coldWater, hotWater,generalExtend) const { clientId, clientSecret, env, platform, accounts,clientUrl } = params this.envVersion = 'release' diff --git a/star-cloud/generalExtend.js b/star-cloud/generalExtend.js new file mode 100644 index 0000000..d5d9eea --- /dev/null +++ b/star-cloud/generalExtend.js @@ -0,0 +1,337 @@ +import {searchAndConnectDevice, writeBLECharacteristicValue} from "../uni/basic.js"; +import {cmdIds, Result, subCmdIds} from '../constant' +import {convertWeekdaysToNumber, createPackageEnd, md5Encrypt, parseTimeToList, timestampToArray} from "../format.js"; +import {sm4} from "sm-crypto"; +import { + checkRepeatCardName, + clearAllIcCard, + deleteIcCardRequest, + getIcCardListRequest, + updateIcCardRequest +} from "../api.js"; + + +/** + * 检查并返回具体哪个参数为空 + * @param {Object} params - 参数对象 + * @param {Array} requiredFields - 必填字段列表 + * @returns {string | null} - 如果有字段为空,则返回错误消息;否则返回null + */ +function _checkRequiredFields(params, requiredFields) { + for (let field of requiredFields) { + if (!params[field]) { + return `${field} 不能为空`; + } + } + return null; +} + + +/** + * 注册扩展产品(卡片、指纹等) + * + * @param {Object} params - 所需参数 + * @param {string} params.type - 产品类型,['card','fingerprint'] + * @param {string} params.keyId - 钥匙 ID + * @param {string} params.uid - 用户 ID + * @param {number} params.userCountLimit - 使用次数限制,整型,0xFFFF 表示不限次数 + * @param {number} params.operate - 操作类型,整型,0: 注册, 1: 修改, 2: 删除, 3: 删除全部 + * @param {number} params.isAdmin - 是否管理员,整型,1 表示是,0 表示否 + * @param {number} params.isForce - 是否胁迫,整型,1 表示是,0 表示否 + * @param {number} params.isRound - 是否循环,整型,1 表示是,0 表示否 + * @param {array} params.weekDays - 循环周期,数组,Bit0 -- 6 置位分别代表周日 -- 周六,例如:循环星期一、星期二、星期四,对应为:[1,2,4] + * @param {number} params.startDate - 生效日期,时间戳 + * @param {number} params.endDate - 失效日期,时间戳 + * @param {string} params.startTime - 生效时间,字符串,例如:'00:00' + * @param {string} params.endTime - 失效时间,字符串,例如:'00:00' + * @param {number} params.cardId - 卡片Id,仅当 type 为 'card' 且operate=1 时需要 + * @param {number} params.cardNo - 卡片序号,仅当 type 为 'card' 时需要 + * @param {string} params.cardName - 卡片名称,仅当 type 为 'card' 时需要 + * @param {number} params.cardType - 卡片类型,1:永久,2:期限,4:循环,仅当 type 为 'card' 时需要 + * @param {number} params.cardUserNo - 卡UserNo,仅当 type 为 'card' 时需要 (选填) + * @param {number} params.fingerprintName - 指纹名,仅当 type 为 'fingerprint' 时需要 + * @param {string} params.fingerprintNumber - 指纹序号,仅当 type 为 'fingerprint' 时需要 + * @param {number} params.fingerprintType - 指纹类型,1:永久,2:期限,3:单次,4:循环,仅当 type 为 'fingerprint' 时需要 + * @param {number} params.fingerprintUserNo - 指纹UserNo,仅当 type 为 'fingerprint' 时需要 (选填) + * @returns {Promise} - 返回注册结果的 Promise 对象 + */ +export async function registerExtendedProducts(params) { + + // 确认产品类型存在 + if (!params.type) { + return new Result(Result.NotMoreData, null, '注册扩展产品类型不能为空'); + } + + // 如果是卡片的话需要增加的参数 + if (params.type === 'card') { + // 注册卡片时的参数校验 + if (params.operate === 0) { + const cardRequiredFields = ['cardName', 'cardType', 'cardNo']; + const missingField = _checkRequiredFields(params, cardRequiredFields); + if (missingField) { + return new Result(Result.NotMoreData, null, `卡片信息不完整: ${missingField}`); + } + // 检查卡昵称是否重复 + const checkRepeatCardNameResult = await checkRepeatCardName({ + lockId: this.lockInfo.lockId, + cardName: params.cardName + }) + if (checkRepeatCardNameResult.code !== Result.Success.code) { + return checkRepeatCardNameResult + } + } + + // 修改卡片时的参数校验 + if (params.operate === 1 || params.operate === 2) { + let cardRequiredFields = ['cardId']; + if (params.operate === 1) { + cardRequiredFields.push('cardType') + } + if (params.operate === 2) { + cardRequiredFields.push('cardNo') + } + const missingField = _checkRequiredFields(params, cardRequiredFields); + if (missingField) { + return new Result(Result.NotMoreData, null, `卡片信息不完整: ${missingField}`); + } + } + } + + // 如果是指纹的话需要增加的参数 + if (params.type === 'fingerprint') { + const cardRequiredFields = ['fingerprintName', 'fingerprintNumber', 'fingerprintType']; + const missingField = _checkRequiredFields(params, cardRequiredFields); + if (missingField) { + return new Result(Result.NotMoreData, null, `指纹信息不完整: ${missingField}`); + } + } + + // 添加类型:1蓝牙,2远程,3发卡器 + params.addType = 1; + // 操作后不断开蓝牙 + params.disconnect = false; + + let { + type, + keyId, + uid, + cardNo, + operate, + isAdmin, + userCountLimit, + isForce, + isRound, + weekDays, + startDate, + endDate, + startTime, + endTime, + } = params + + + // 设置执行账号 + const result = await this.login(uid) + 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) { + return { + code: -1 + } + } + + this.requestParams = params + + startDate = Math.floor(startDate / 1000) + endDate = Math.floor(endDate / 1000) + + const length = 2 + 1 + 1 + 40 + 20 + 2 + 2 + 1 + 1 + 1 + 4 + 1 + 1 + 4 + 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 + // 子命令 + if (type === 'card') { + contentArray[2] = subCmdIds.registerCard + } else if (type === 'fingerprint') { + contentArray[2] = subCmdIds.registerFingerprint + } else if (type === 'face') { + contentArray[2] = subCmdIds.registerFace + } else if (type === 'remote') { + contentArray[2] = subCmdIds.registerRemote + } else if (type === 'palmVein') { + contentArray[2] = subCmdIds.registerPalmVein + } + 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] = (cardNo || 0) / 256 + contentArray[65] = (cardNo || 0) % 256 + contentArray[66] = (userCountLimit || 0xffff) / 256 + contentArray[67] = (userCountLimit || 0xffff) % 256 + contentArray[68] = operate + contentArray[69] = isAdmin || 0 + contentArray[70] = isForce || 0 + + contentArray.set(this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), 71) + + contentArray[75] = isRound + contentArray[76] = convertWeekdaysToNumber(weekDays) + + contentArray.set(timestampToArray(startDate), 77) + contentArray.set(timestampToArray(endDate), 81) + + if (isRound) { + contentArray.set(parseTimeToList(startTime), 85) + contentArray.set(parseTimeToList(endTime), 89) + } else { + contentArray.set(new Uint8Array([0, 0, 0, 0]), 85) + contentArray.set(new Uint8Array([0, 0, 0, 0]), 89) + } + + contentArray[93] = 16 + + const md5Array = md5Encrypt( + keyId + uid, + this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), + this.lockInfo.bluetooth.signKey + ) + + contentArray.set(md5Array, 94) + + 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.registerExtendedProducts, params) +} + + +/** + * 取消注册扩展产品 + * + * @param {Object} params - 所需参数 + * @param {string} params.Type - 产品类型,['card','fingerprint','face','remote','palmVein'] + * @param {string} params.KeyID - 钥匙 ID + * @param {string} params.uid - 用户 ID + */ +export async function registerExtendedProductsCancel(params) { + const {type, keyId, uid} = params + // 设置执行账号 + const result = await this.login(uid) + 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) { + return { + code: -1 + } + } + + this.requestParams = params + const length = 2 + 1 + 1 + 40 + 20 + 4 + 1 + 16 + const headArray = this.createPackageHeader(3, length) + const contentArray = new Uint8Array(length) + + contentArray[0] = cmdIds.expandCmd / 256 + contentArray[1] = cmdIds.expandCmd % 256 + + // 子命令 + if (type === 'card') { + contentArray[2] = subCmdIds.registerCardCancel + } else if (type === 'fingerprint') { + contentArray[2] = subCmdIds.registerFingerprintCancel + } else if (type === 'face') { + contentArray[2] = subCmdIds.registerFaceCancel + } else if (type === 'remote') { + contentArray[2] = subCmdIds.registerRemoteCancel + } else if (type === 'palmVein') { + contentArray[2] = subCmdIds.registerPalmVeinCancel + } + + 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.set(this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), 64) + + contentArray[68] = 16 + + const md5Array = md5Encrypt( + keyId + uid, + this.lockInfo.token || new Uint8Array([0, 0, 0, 0]), + this.lockInfo.bluetooth.signKey + ) + + contentArray.set(md5Array, 69) + + const cebArray = sm4.encrypt(contentArray, this.lockInfo.bluetooth.privateKey, { + mode: 'ecb', + output: 'array' + }) + + const packageArray = createPackageEnd(headArray, cebArray) + + await writeBLECharacteristicValue(this.lockInfo.deviceId, + this.lockInfo.serviceId, + this.lockInfo.writeCharacteristicId, + packageArray) +} + +/** + * 获取Ic卡列表 + * @param params.lockId 锁id (必填) + * @param params.pageNo 页码(选填) + * @param params.pageSize 每页显示数(选填) + * @returns {Promise} + */ +export async function getIcCardList(params) { + return await getIcCardListRequest(params) +} \ No newline at end of file diff --git a/uni/index.js b/uni/index.js index 185df89..73d16b3 100644 --- a/uni/index.js +++ b/uni/index.js @@ -262,3 +262,61 @@ export const refreshColdWaterInfo = async params => { export const refreshHotWaterInfo = async params => { return await starCloudInstance.refreshHotWaterInfo(params) } + + + +/** + * 注册扩展产品(卡片、指纹等) + * + * @param {Object} params - 所需参数 + * @param {string} params.type - 产品类型,['card','fingerprint'] + * @param {string} params.keyId - 钥匙 ID + * @param {string} params.uid - 用户 ID + * @param {number} params.userCountLimit - 使用次数限制,整型,0xFFFF 表示不限次数 + * @param {number} params.operate - 操作类型,整型,0: 注册, 1: 修改, 2: 删除, 3: 删除全部 + * @param {number} params.isAdmin - 是否管理员,整型,1 表示是,0 表示否 + * @param {number} params.isForce - 是否胁迫,整型,1 表示是,0 表示否 + * @param {number} params.isRound - 是否循环,整型,1 表示是,0 表示否 + * @param {array} params.weekDays - 循环周期,数组,Bit0 -- 6 置位分别代表周日 -- 周六,例如:循环星期一、星期二、星期四,对应为:[1,2,4] + * @param {number} params.startDate - 生效日期,时间戳 + * @param {number} params.endDate - 失效日期,时间戳 + * @param {string} params.startTime - 生效时间,字符串,例如:'00:00' + * @param {string} params.endTime - 失效时间,字符串,例如:'00:00' + * @param {number} params.cardId - 卡片Id,仅当 type 为 'card' 且operate=1 时需要 + * @param {number} params.cardNo - 卡片序号,仅当 type 为 'card' 时需要 + * @param {string} params.cardName - 卡片名称,仅当 type 为 'card' 时需要 + * @param {number} params.cardType - 卡片类型,1:永久,2:期限,4:循环,仅当 type 为 'card' 时需要 + * @param {number} params.cardUserNo - 卡UserNo,仅当 type 为 'card' 时需要 (选填) + * @param {number} params.fingerprintName - 指纹名,仅当 type 为 'fingerprint' 时需要 + * @param {string} params.fingerprintNumber - 指纹序号,仅当 type 为 'fingerprint' 时需要 + * @param {number} params.fingerprintType - 指纹类型,1:永久,2:期限,3:单次,4:循环,仅当 type 为 'fingerprint' 时需要 + * @param {number} params.fingerprintUserNo - 指纹UserNo,仅当 type 为 'fingerprint' 时需要 (选填) + * @returns {Promise} - 返回注册结果的 Promise 对象 + */ +export const registerExtendedProducts = async params => { + return await starCloudInstance.registerExtendedProducts(params) +} + + +/** + * 取消注册扩展产品 + * + * @param {Object} params - 所需参数 + * @param {string} params.Type - 产品类型,['card','fingerprint','face','remote','palmVein'] + * @param {string} params.KeyID - 钥匙 ID + * @param {string} params.uid - 用户 ID + */ +export const registerExtendedProductsCancel = async params => { + return await starCloudInstance.registerExtendedProductsCancel(params) +} + + +/** + * 获取Ic卡列表 + * @param params.lockId 锁id (必填) + * @param params.pageNo 页码(选填) + * @param params.pageSize 每页显示数(选填) + */ +export const getIcCardList = async params => { + return await starCloudInstance.getIcCardList(params) +} \ No newline at end of file