import { genReqSecretPkg, genOpDoorPkg, genQuPowerPkg, gnQuDoorLogPkg, Delayed, decryptAes, parseBELDeviceId, hexStringToUint8Array } from "../utils/bluetoothUtils"; import { getOpenDoorInfo, unloadBLEOpenDoorLog, getCheckinInfo } from "../utils/api"; import moment from "moment"; import { mapState } from "vuex"; export const bluetooth = { data() { return { // allowedOpenDoor为0时允许点击开门,为1时点击开门替换为loading状态,为2时会弹提示 allowedOpenDoor: 2, isOpeningDoor: false, loadingContent: "初始化蓝牙中...", hotelId: null, openDoorInfo: { gatewayDeviceId: "", bluetoothDeviceId: "", bluetoothName: "", bluetoothKey: "", androidOptimization: true, openDoorParam: null, }, checkinInfo: {} }; }, computed: { ...mapState("m_user", ["userInfo"]), ...mapState("m_business", ["currentHotel"]) }, methods: { async bluetoothOperate() { const vueComponent = this; /** * 此次操作的生命周期数据,会上传给后端 */ const bluetoothLifeData = { name: this.openDoorInfo.bluetoothName, // 蓝牙门锁名称 hotelId: this.hotelId, operationType: 1, isBluetoothOpened: null, // 蓝牙开关是否打开 bluetoothConnected: false, // 蓝牙是否连接上 deviceId: null, // 进行连接时,蓝牙的deviceId。安卓可以计算出来,iOS需要扫描 serviceId: "0000FFF0-0000-1000-8000-00805F9B34FB", // 蓝牙交互服务的service的UUID characteristicId: "0000FFF1-0000-1000-8000-00805F9B34FB", // 特征值的UUID initBluetoothAdapterTime: null, // 进行蓝牙初始化时的时间 startBluetoothDiscoveryTime: null, // 蓝牙初始化完成同时开始探索蓝牙 gap1蓝牙初始化 targetBluetoothFoundTime: null, // 目标蓝牙被找到,同时开始蓝牙的连接 gap2探索目标蓝牙 targetBluetoothConnectedTime: null, // 蓝牙被连接时的时间 gap3连接蓝牙 opForIosEndedTime: null, // 连接蓝牙后,完成iOS设备所必须的操作时的时间 gap4 iOS服务耗时 requestSessionKeyTime: null, // 请求会话秘钥的开始时间 gap5 订阅特征值变化 getSessionKeyPackageTime: null, // 获取到请求会话秘钥ack的时间 requestBleOpenDoorTime: null, // 发送开门请求的时间 getOpenDoorPackageTime: null, // 获取到请求开门返回包的时间 requestOpenDoorLogTime: null, //发送获取开门记录的时间 getOpenDoorLogPackageTime: null, // 获取到开门记录返回包的时间 requestPowerLogTime: null, // 发送获取电量记录的时间 getPowerLogPackageTime: null, // 获取到电量记录返回报文的时间 result: 0, endTime: null, // 流程中断的时间点 mobileOs: null, totalTime: null, isLocationEnabled: null, androidOptimizationStatus: this.openDoorInfo.androidOptimization, hostName: null, // 小程序宿主名 hostVersion: null, // 小程序宿主版本 openDoorParam: JSON.stringify(this.openDoorInfo.openDoorParam), // 开门时的自定义参数 bluetoothAuthorized: false, // 蓝牙授权开启情况 errMessage: '', // 剩下的为上传相关记录的操作 floor: Number(this.checkinInfo.floor), room: this.checkinInfo.room }; /** * * 抓取小程序宿主环境的相关信息 * 由于小程序可能存在bug,当开启蓝牙和位置之后仍旧报错的,退出小程序再重新进入 * https://developers.weixin.qq.com/community/develop/doc/000a6e376308d0c306a88a20f56800 */ try { const res = await uni.getSystemInfoSync(); // console.log("uni.getSystemInfoSync({ includeBluetooth: true })", res); bluetoothLifeData.hostName = res.hostName; // 小程序的宿主名称,微信、支付宝等 bluetoothLifeData.hostVersion = res.hostVersion; // 宿主环境的版本信息 bluetoothLifeData.mobileOs = res.osName; // 收集操作系统 // 后期考虑能否获取到开发环境基础库的版本,并添加进数据库 } catch (e) { console.log("抓取小程序宿主环境的相关信息失败", e); } /** * 处理宿主系统为ios且宿主环境为alipay时的服务id和特征值id */ if (bluetoothLifeData.mobileOs === 'ios' && bluetoothLifeData.hostName === 'alipay') { bluetoothLifeData.serviceId = 'FFF0' bluetoothLifeData.characteristicId = 'FFF1' } /** * 蓝牙的开关以及在未开启安卓优化时位置的开关 */ const systemBleAndLocationEnabled = uni.getSystemSetting() bluetoothLifeData.isBluetoothOpened = systemBleAndLocationEnabled.bluetoothEnabled // 处理手机蓝牙未打开 if (!bluetoothLifeData.isBluetoothOpened) { uni.showModal({ title: "蓝牙未打开", content: "紧急钥匙功能需要蓝牙支持,请打开手机蓝牙后重新开门。", }); vueComponent.allowedOpenDoor = 0; return } // 不开启安卓优化,设备系统是安卓,校验位置是否开启;暂时不校验位置信息授权,测试后再确定是否校验 bluetoothLifeData.isLocationEnabled = systemBleAndLocationEnabled.locationEnabled if (vueComponent.openDoorInfo.androidOptimization === false && bluetoothLifeData.mobileOs === "android" && !bluetoothLifeData.isLocationEnabled) { uni.showModal({ title: "位置未开启", content: "紧急钥匙功能需要地理位置支持,请开启位置信息后重试。", }); vueComponent.allowedOpenDoor = 0; return } /** * 判断是否获取到当前房间的蓝牙名和蓝牙秘钥,没获取到则报异常并return */ if (!(this.openDoorInfo.bluetoothName && this.openDoorInfo.bluetoothKey)) { uni.showModal({ title: "未获取到设备信息", content: "未获取到门锁设备信息,请稍后重试。", }); vueComponent.allowedOpenDoor = 0; uni.switchTab({ url: '/pages/home/home' }) console.log("方法入口告警:蓝牙名或蓝牙秘钥为空"); return; } /** * 一些超时时间的设置 */ const timeoutValue = { findDevice: vueComponent.openDoorInfo.openDoorParam.androidScanTimeout ? vueComponent.openDoorInfo.openDoorParam.androidScanTimeout : 10000, // 探索发现设备的超时时间 connectBLE: 5000, // 连接蓝牙的超时时间 requestSessionKey: 3000, // 请求秘钥的超时时间 openDoor: 4000, // 请求开门的超时时间 getOpenDoorLog: 8000, getPowerLog: 3000, }; /** * 错误码定义 */ const ErrCode = { bleNotOpened: 10001, // 蓝牙未打开 findDeviceTimeout: 10002, // 发现蓝牙设备超时 bleConnectFailed: 10003, // 连接蓝牙失败 requestSessionKeyTimeout: 10004, // 请求会话秘钥超时 openDoorTimeout: 10005, // 蓝牙开门超时 getDoorLogTimeout: 10006, // 获取开门记录超时 getPowerLogTimeout: 10007, // 获取电量记录超时 androidNotOpenLocation: 10008, // 安卓设备未打开位置信息,无法扫描蓝牙;仅在未开启蓝牙优化时出现 }; /** * 蓝牙开门加载时,显示的内容 */ const loading = { initBLE: '初始化手机蓝牙中...', discoveryBLE: '扫描门锁蓝牙中...', connectBLE: "连接门锁蓝牙中...", isOpeningDoor: '门锁开门中...' } // loading,开始进行蓝牙开门操作 vueComponent.isOpeningDoor = true; vueComponent.allowedOpenDoor = 1; /** * 获取用户的当前设置(蓝牙的授权信息)。返回值中只会出现小程序已经向用户请求过的权限。 */ await uni.getSetting().then(res => { console.log("getSetting", res); if (bluetoothLifeData.hostName === 'WeChat') { bluetoothLifeData.bluetoothAuthorized = res[1].authSetting['scope.bluetooth'] ? res[1].authSetting['scope.bluetooth'] : false } else if (bluetoothLifeData.hostName === 'alipay') { bluetoothLifeData.bluetoothAuthorized = res[1].authSetting['bluetooth'] ? res[1].authSetting['bluetooth'] : false } console.log("bluetoothLifeData.bluetoothAuthorized", bluetoothLifeData .bluetoothAuthorized); }).catch(err => { console.log("getSetting catch err", err); }) /** * * 开启蓝牙适配器,必须操作,并在Promise中处理授权相关的信息 */ bluetoothLifeData.initBluetoothAdapterTime = +new Date(); await new Promise(async (resolve, reject) => { await uni.openBluetoothAdapter({ mode: "central", //小程序作为中央设备 }).then((res) => { console.log("openBluetoothAdapter success", res); if (bluetoothLifeData.hostName === 'WeChat') { // 初始化成功且蓝牙已授权 if (res[1]) { if (!res[1]['errno']) { bluetoothLifeData.bluetoothAuthorized = true resolve('success') } } else if (res[0]) { // 初始化成功,但蓝牙未授权 微信错误码103为用户拒绝授权 if (res[0].hasOwnProperty('errno') && res[0].errno === 103) { uni.showModal({ title: "温馨提示", content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。", success: async (res) => { if (res.confirm) { await uni.openSetting().then( res => { console.log( "openSetting", res); bluetoothLifeData .bluetoothAuthorized = res[1].authSetting[ 'scope.bluetooth' ] resolve() }) } }, }); } } } else if (bluetoothLifeData.hostName === 'alipay') { // 初始化成功且蓝牙已授权 if (res[1]) { if (res[1]['isSupportBLE']) { bluetoothLifeData.bluetoothAuthorized = true resolve('success') } } else if (res[0]) { if (res[0].hasOwnProperty('error') && res[0].error === 2001) { // 初始化成功,但蓝牙未授权,支付宝错误码为2001 uni.showModal({ title: "温馨提示", content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。", success: async (res) => { if (res.confirm) { await uni.openSetting().then( res => { console.log( "openSetting", res); bluetoothLifeData .bluetoothAuthorized = res[1].authSetting[ 'bluetooth'] resolve() }) } }, }); } } } }).catch((err) => { console.log("openBluetoothAdapter catch err", err); reject(err) }); }).catch(err => { console.log(err); }) /** * * 判断蓝牙是否授权,未授权直接return */ if (!bluetoothLifeData.bluetoothAuthorized) { vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 0; console.log("手机蓝牙未授权,最外层函数。"); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; bluetoothLifeData.result = ErrCode.bleNotOpened; // 蓝牙未打开其实为蓝牙未授权 unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => { console.log(res); }); uni.showModal({ title: "温馨提示", content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。", success: (res) => { if (res.confirm) { uni.openSetting().then(res => { console.log("openSetting", res); }) } }, }); closeBluetoothAdapter(); return; } /** * * 在Promise中调用onBluetoothDeviceFound,直到找到目的设备 * 针对android设备且开启了安卓设备优化,直接跳过此流程 */ if (bluetoothLifeData.mobileOs === "ios" || (bluetoothLifeData.mobileOs === "android" && vueComponent.openDoorInfo.androidOptimization === false)) { await new Promise(async (resolve, reject) => { vueComponent.loadingContent = loading.discoveryBLE // console.log("enter onBluetoothDeviceFound Promise"); // 设置发现设备的超时 const timeout = setTimeout(() => { if (!bluetoothLifeData.targetBluetoothFoundTime) { console.log("onBluetoothDeviceFound Promise 发现设备超时"); bluetoothLifeData.result = ErrCode.findDeviceTimeout; uni.offBluetoothDeviceFound(); reject("发现设备超时"); } }, timeoutValue.findDevice); uni.onBluetoothDeviceFound((res) => { let devices = res.devices.filter( (device) => device.name === vueComponent.openDoorInfo.bluetoothName ); // console.log("onBluetoothDeviceFound 发现设备数", devices.length); if (devices.length <= 0) return; let { deviceId, name } = devices[0]; // console.log("deviceId", deviceId, "deviceName", name); bluetoothLifeData.deviceId = deviceId; bluetoothLifeData.targetBluetoothFoundTime = +new Date(); stopBluetoothDevicesDiscovery(); clearTimeout(timeout); resolve({ bleFound: true }); }); // 调用扫描蓝牙API,扫描周边蓝牙 bluetoothLifeData.startBluetoothDiscoveryTime = +new Date(); let discovery = await uni.startBluetoothDevicesDiscovery({ powerLevel: "high" }); console.log("startBluetoothDevicesDiscovery", discovery); }).catch((err) => { console.log("onBluetoothDeviceFound Promise catch err", err); }); /** * * 搜索蓝牙设备超时或安卓设备未打开位置信息时的处理逻辑 */ if (!bluetoothLifeData.targetBluetoothFoundTime) { console.log("发现设备超时,最外层函数"); vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE uni.showModal({ title: "未找到门锁", content: "未找到门锁设备,请确认您的位置是否正确。", }); console.log("未找到门锁"); await closeBluetoothAdapter(); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => { console.log(res); }); return; } } else { //该分支不进行蓝牙扫描 bluetoothLifeData.startBluetoothDiscoveryTime = +new Date(); bluetoothLifeData.targetBluetoothFoundTime = +new Date(); // 根据转换规则,置android设备的deviceId console.log(vueComponent.openDoorInfo.bluetoothDeviceId); bluetoothLifeData.deviceId = parseBELDeviceId( vueComponent.openDoorInfo.bluetoothDeviceId ); console.log( "android设备连接蓝牙时的deviceId", bluetoothLifeData.deviceId ); } /** * * 找到目标设备,开始进行蓝牙连接 * 在Promise中连接蓝牙设备,超时就 关闭蓝牙适配器 ,第一次超时return是否会影响第二次的成功连接 */ await new Promise((resolve, reject) => { vueComponent.loadingContent = loading.connectBLE const timeout = setTimeout(() => { if (!bluetoothLifeData.targetBluetoothConnectedTime) { bluetoothLifeData.result = ErrCode.bleConnectFailed; console.log("蓝牙连接超时"); reject("蓝牙连接超时"); } }, timeoutValue.connectBLE) uni.createBLEConnection({ deviceId: bluetoothLifeData.deviceId, }) .then((res) => { clearTimeout(timeout) bluetoothLifeData.bluetoothConnected = true; bluetoothLifeData.targetBluetoothConnectedTime = +new Date(); console.log("createBLEConnection res", res); resolve("蓝牙连接成功") }) .catch((err) => { bluetoothLifeData.result = ErrCode.bleConnectFailed; console.log("createBLEConnection catch err", err); }); }).catch(err => { console.log("createBLEConnection Promise catch err", err); }) /** * * 判断蓝牙是否连接成功,若失败则直接return */ if (!bluetoothLifeData.bluetoothConnected) { vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE uni.showModal({ title: "蓝牙连接失败", content: "蓝牙连接失败,请确认位置正确后重试。", }); await closeBluetoothAdapter(); console.log("蓝牙连接失败,最外层函数"); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; console.log("蓝牙连接失败"); unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => { console.log(res); }); return; } /** * * 蓝牙连接成功后,对iOS设备而言,需对服务及特征值进行扫描 */ if (bluetoothLifeData.mobileOs === "ios") { await uni .getBLEDeviceServices({ deviceId: bluetoothLifeData.deviceId, }) .then((res) => { console.log("getBLEDeviceServices", res); }) .catch((err) => { console.log("getBLEDeviceServices err", err); }); await uni .getBLEDeviceCharacteristics({ deviceId: bluetoothLifeData.deviceId, serviceId: bluetoothLifeData.serviceId, }) .then((res) => { console.log("getBLEDeviceCharacteristics", res); }) .catch((err) => { console.log("getBLEDeviceCharacteristics err", err); }); bluetoothLifeData.opForIosEndedTime = +new Date(); } else { bluetoothLifeData.opForIosEndedTime = +new Date(); } /** * * 对主服务特征值进行订阅; */ const subscribe = await notifyBLECharacteristicValueChange(); console.log("subscribe", subscribe); /** * * 向蓝牙发请求,请求会话秘钥(写特征值),解出会话秘钥 */ await new Promise(async (resolve, reject) => { vueComponent.loadingContent = loading.isOpeningDoor // console.log("onBLECharacteristicValueChange监听函数,请求会话秘钥"); // 设置请求会话秘钥的超时 const timeout = setTimeout(() => { if (!bluetoothLifeData.getSessionKeyPackageTime) { bluetoothLifeData.result = ErrCode.requestSessionKeyTimeout; console.log("请求会话秘钥超时"); reject("请求会话秘钥超时"); } }, timeoutValue.requestSessionKey); uni.offBLECharacteristicValueChange(); const requestSessionKeyRes = onBLECharacteristicValueChange(); bluetoothLifeData.requestSessionKeyTime = +new Date(); const requestSecret = await writeBLECharacteristicValue( genReqSecretPkg() ); console.log("requestSecret", requestSecret); requestSessionKeyRes.then((res) => { // console.log(res); if (res == 0x01) { clearTimeout(timeout); resolve({ requestSessionKey: true }); } else { clearTimeout(timeout); reject({ message: "应答包获取错误,应为请求会话秘钥的应答包。", }); } }); }).catch((err) => { console.log( "onBLECharacteristicValueChange监听函数,请求会话秘钥。catch err", err ); }); /** * * 获取会话秘钥超时的处理逻辑 */ if (!bluetoothLifeData.getSessionKeyPackageTime) { vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE uni.showModal({ title: "蓝牙开门失败", content: "蓝牙开门失败,请稍后重试!", // 带!号的蓝牙开门失败实际为请求会话秘钥超时 }); console.log("获取会话秘钥失败"); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; console.log("会话秘钥超时"); unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => { console.log(res); }); return; } /** * * 解完请求会话秘钥后,发蓝牙开门报文,解蓝牙开门返回包 */ await new Promise(async (resolve, reject) => { // console.log("onBLECharacteristicValueChange监听函数,蓝牙开门"); // 设置蓝牙开门的超时 const timeout = setTimeout(() => { if (!bluetoothLifeData.getOpenDoorPackageTime) { bluetoothLifeData.result = ErrCode.openDoorTimeout; console.log("蓝牙开门超时"); reject("蓝牙开门超时"); } }, timeoutValue.openDoor); uni.offBLECharacteristicValueChange(); const requestBLEOpenDoorRes = onBLECharacteristicValueChange(); bluetoothLifeData.requestBleOpenDoorTime = +new Date(); const openDoor = await writeBLECharacteristicValue(genOpDoorPkg()); // console.log("openDoor", openDoor); requestBLEOpenDoorRes.then((res) => { // console.log(res); if (res == 0x11) { vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 2; clearTimeout(timeout); uni.showModal({ title: "开门成功", content: "蓝牙开门成功,祝您入住愉快。", }); resolve({ bluetoothOpenDoor: true }); } else { clearTimeout(timeout); reject({ message: "应答包获取错误,应为请求会话秘钥的应答包。", }); } }); }).catch((err) => { console.log( "onBLECharacteristicValueChange监听函数,蓝牙开门。catch err", err ); }); /** * * 判断蓝牙开门是否成功,如果失败,则return */ if (!bluetoothLifeData.getOpenDoorPackageTime) { vueComponent.isOpeningDoor = false; vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE uni.showModal({ title: "蓝牙开门失败", content: "蓝牙开门失败,请稍后重试。", }); console.log("蓝牙开门失败"); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => { console.log(res); }); return; } /** * * 断开蓝牙连接,关闭蓝牙适配器,并上传所有记录 */ await closeBLEConnection(bluetoothLifeData.deviceId); await closeBluetoothAdapter(); bluetoothLifeData.endTime = new Date(); bluetoothLifeData.totalTime = bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime; unloadBLEOpenDoorLog(bluetoothLifeData) .then((res) => { console.log(res); vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE }) .catch((err) => { console.log("完成所有操作,上传开门记录", err); vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE }) .finally(() => { vueComponent.allowedOpenDoor = 0; vueComponent.loadingContent = loading.initBLE }); console.log(bluetoothLifeData); /** * * 以下为函数声明 */ function onBLECharacteristicValueChange() { // console.log("onBLECharacteristicValueChange监听函数"); return new Promise((resolve, reject) => { const BleStruct = { firstPacketReceived: false, //是否第一个报 buffer: null, // 用于缓存数据流的 ArrayBuffer cmd: null, //返回数据包命令号 length: 0, //数据包长度 received: 0, errCode: 0, }; uni.onBLECharacteristicValueChange(async function(res) { console.log("onBLECharacteristicValueChange", res); //打印特征值 let receiveBuffer = bluetoothLifeData.hostName === 'alipay' ? hexStringToUint8Array(res.value) : new Uint8Array(res.value); // console.log("receiveBuffer", receiveBuffer); // 接收到的报文包含开始码等报文头数据 if ( receiveBuffer[0] == 0xa5 && BleStruct.firstPacketReceived == false ) { // console.log("包含开始码等报文头数据的报文"); BleStruct.firstPacketReceived = true; BleStruct.length = (receiveBuffer[5] << 8) + receiveBuffer[4] + 6; //算出数据长度 BleStruct.received = 0; BleStruct.cmd = receiveBuffer[1]; //命令号赋值 BleStruct.errCode = receiveBuffer[6]; BleStruct.buffer = new Uint8Array(BleStruct.length); // 错误码!= 0,丢弃报文 if (BleStruct.errCode != 0) { BleStruct.firstPacketReceived = false; bluetoothLifeData.result = BleStruct.errCode; reject({ message: "errCode != 0", errCode: BleStruct.errCode }); return; } } // 接收到的报文的长度不对,丢弃报文 if (receiveBuffer.length > BleStruct.length - BleStruct.received) { BleStruct.firstPacketReceived = false; console.log( "onBLECharacteristicValueChange: length err", BleStruct, receiveBuffer.length ); reject({ message: "onBLECharacteristicValueChange: length err", bleStruct: BleStruct, length: receiveBuffer.length, }); return; } BleStruct.buffer.set(receiveBuffer, BleStruct .received); // 将接收到的报文放入buffer容器 BleStruct.received += receiveBuffer.length; // console.log("BleStruct", BleStruct); // 已经获取到全部报文 if (BleStruct.received >= BleStruct.length) { BleStruct.firstPacketReceived = false; const returnCmd = analysisData( BleStruct.cmd, BleStruct.buffer.slice(7) ); resolve(returnCmd); } }); }); } function analysisData(cmd, data) { // console.log("进入分析数据函数"); const buffer2Str = (buffer) => Array.from(buffer) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); const buffer2Num = (buffer) => parseInt(buffer2Str(buffer), 16); //请求会话密钥的返回包,对其进行处理 if (cmd == 0x01) { bluetoothLifeData.getSessionKeyPackageTime = +new Date(); var originalKey = vueComponent.openDoorInfo.bluetoothKey; // var originalKey = "ffffffffffffffffffffffffffffffff"; var encryptedData = data; var sessionKey = decryptAes(originalKey, encryptedData); const hexString = Array.from(sessionKey) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); uni.setStorageSync("sessionKey", hexString); return 0x01; } //获取电量返回包,对其进行处理 if (cmd == 0x04) { bluetoothLifeData.getPowerLogPackageTime = +new Date(); console.log("power:", data); let powerString = Array.from(data) .map((byte) => byte.toString(16).padStart(2, "0")) .join(""); powerString = parseInt(powerString, 16); uni.setStorageSync("power", powerString); return 0x04; } //蓝牙开门返回包,对其进行处理 if (cmd == 0x11) { bluetoothLifeData.getOpenDoorPackageTime = +new Date(); // console.log("蓝牙开门成功"); // console.log(data[0]); // console.log(data[1]); return 0x11; } //开门记录返回包,对其进行处理 if (cmd == 0x12) { bluetoothLifeData.getOpenDoorLogPackageTime = +new Date(); var dataBuffer = data; var doorBufferLogs = []; while (dataBuffer.length > 0) { length = Math.min(17, dataBuffer.length); doorBufferLogs.push(dataBuffer.slice(0, length)); // console.log( // "cardNum", // dataBuffer.slice(1, 5), // "roomNum", // dataBuffer.slice(5, 9) // ); dataBuffer = dataBuffer.slice(length); } let doorLogs = doorBufferLogs.map((buffer) => ({ type: buffer[0], cardNum: buffer2Num(buffer.slice(1, 5)), roomNum: buffer2Str(buffer.slice(5, 9)), time: new Date(buffer2Num(buffer.slice(9)) * 1000), })); // console.log("analysisData:", doorBufferLogs, doorLogs); uni.setStorageSync("doorLogs", doorLogs); return 0x12; } } // 启用蓝牙低功耗设备特征值变化时的 notify 功能, function notifyBLECharacteristicValueChange() { return new Promise((resolve, reject) => { // 连上蓝牙后再创建订阅 console.log("创建订阅"); uni.notifyBLECharacteristicValueChange({ deviceId: bluetoothLifeData.deviceId, // 蓝牙设备 id serviceId: bluetoothLifeData.serviceId, // 蓝牙特征对应服务的 UUID characteristicId: bluetoothLifeData.characteristicId, // 蓝牙特征的 UUID state: true, // 是否启用 notify success: function(res) { console.log("success notifyBLECharacteristicValueChange:", res); resolve(res); }, fail: function(res) { console.log("fail notifyBLECharacteristicValueChange:", res); resolve(res); }, }); }); } function onBLEConnectionStateChange() { uni.onBLEConnectionStateChange((res) => { // console.log("onBLEConnectionStateChange", res); }); } function stopBluetoothDevicesDiscovery() { uni.stopBluetoothDevicesDiscovery({ success: (res) => { // console.log("关闭蓝牙停止搜索成功"); }, fail: (res) => { console.log("关闭蓝牙停止搜索失败"); }, }); } // 向蓝牙低功耗设备特征值中写入二进制数据 async function writeBLECharacteristicValue(packetData) { // 连上蓝牙后再写数据 // console.log("进入写特征值函数,开始写入数据"); // 向蓝牙设备发送一个0x00的16进制数据 // let _Buffer = string2buffer(packetData) let count = 1; while (packetData.byteLength > 0) { let len = Math.min(16, packetData.byteLength); try { const res = await uni.writeBLECharacteristicValue({ deviceId: bluetoothLifeData.deviceId, serviceId: bluetoothLifeData.serviceId, characteristicId: bluetoothLifeData.characteristicId, value: packetData.slice(0, len), }); // console.log(`第${count}次写入`, res); count += 1; } catch (error) { console.log("writeBLECharacteristicValue err", error); } packetData = packetData.slice(len); await Delayed(500); } return; } // 关闭连接,也是异步操作 function closeBLEConnection(deviceId = "") { return new Promise((resolve, reject) => { // console.log("关闭连接"); uni.closeBLEConnection({ deviceId: deviceId, success: (res) => { // console.log("断开蓝牙连接成功", res); resolve(res); }, fail: (res) => { console.log("断开蓝牙连接失败", res); reject(res); }, }); }); } // 关闭蓝牙模块 function closeBluetoothAdapter() { return new Promise((resolve, reject) => { uni.closeBluetoothAdapter({ success: function(res) { // console.log("success closeBluetoothAdapter:", res); resolve(res); }, fail: function(res) { console.log("fail closeBluetoothAdapter:", res); resolve(); }, }); }); } }, }, async onLoad() { // console.log("currentHotelId", this.currentHotelId); // 先获取入住记录 let { data: infoRes } = await getCheckinInfo(this.currentHotel.hotelId) // console.log("获取入住记录", infoRes); if (infoRes.code !== 200 || !infoRes.success || !infoRes.data) { console.log("获取入住记录失败", infoRes); return } this.checkinInfo = infoRes.data; this.checkinInfo.startTime = moment(this.checkinInfo.startTime).format("YYYY/MM/DD HH:mm:ss"); this.checkinInfo.endTime = moment(this.checkinInfo.endTime).format("YYYY/MM/DD HH:mm:ss"); // 获取门锁信息 const { data:openDoorInfoRes } = await getOpenDoorInfo({ hotelId: this.currentHotel.hotelId, floor: Number(this.checkinInfo.floor), room: this.checkinInfo.room }) // console.log("获取入住记录和门锁记录", openDoorInfoRes); const { data: doorLockInfo } = openDoorInfoRes // 更新门锁设备相关信息 this.openDoorInfo.bluetoothDeviceId = doorLockInfo.doorLockDeviceId; this.openDoorInfo.bluetoothName = doorLockInfo.doorLockName; this.openDoorInfo.androidOptimization = doorLockInfo.androidOptimization === false ? false : true; this.openDoorInfo.openDoorParam = JSON.parse(doorLockInfo.openDoorParam) // console.log("this.openDoorInfo.openDoorParam", this.openDoorInfo.openDoorParam); this.openDoorInfo.bluetoothKey = doorLockInfo.doorLockKey; this.allowedOpenDoor = 0; }, };