var xBlufi = require('@/components/blufi/xBlufi.js') /** 设备状态:骑行中 */ var ADMIN_FINAL_STATUS_IN_USE = '3' export default { data() { return { unlockDeviceId: '', unlockSn: '', mac: '', adminFinalStatus: ADMIN_FINAL_STATUS_IN_USE, ver_dataflag: 1, deviceid: '', devicename: '', devicesarr: [], findDeviceTimer: null, jieliuflag: true, jiance: false, shibainum: 0, lat: null, lon: null, btProcessVisible: false, btProgress: 0, btStepMessage: '', btErrorMessage: '', btErrorType: '', btAdapterAvailable: null } }, onUnload() { this.clearUnlockRuntimeState() }, onHide() { if (!this.pendingOfflineAudit) { this.clearUnlockRuntimeState() } }, methods: { initBluetoothStack() { xBlufi.initXBlufi(1) xBlufi.notifyStartDiscoverBle({ isStart: true }) xBlufi.listenDeviceMsgEvent(true, this.funListenDeviceMsgEvent) this.bindBluetoothAdapterStateChange() }, clearUnlockRuntimeState() { const oldDeviceId = this.deviceid const oldDeviceName = this.devicename this.unbindBluetoothAdapterStateChange() try { uni.hideLoading() } catch (e) {} this.btProcessVisible = false this.btProgress = 0 this.btStepMessage = '' this.btErrorMessage = '' this.btErrorType = '' this.shibainum = 0 this.ver_dataflag = 1 this.jieliuflag = true this.jiance = false this.btAdapterAvailable = null if (this.findDeviceTimer) { clearTimeout(this.findDeviceTimer) this.findDeviceTimer = null } try { xBlufi.notifyConnectBle({ isStart: false, deviceId: oldDeviceId, name: oldDeviceName }) xBlufi.notifyStartDiscoverBle({ isStart: false }) xBlufi.listenDeviceMsgEvent(false, this.funListenDeviceMsgEvent) } catch (e) {} this.deviceid = '' this.devicename = '' this.devicesarr = [] }, bindBluetoothAdapterStateChange() { if (!uni.onBluetoothAdapterStateChange) { return } if (uni.offBluetoothAdapterStateChange) { try { uni.offBluetoothAdapterStateChange(this.handleBluetoothAdapterStateChange) } catch (e) {} } uni.onBluetoothAdapterStateChange(this.handleBluetoothAdapterStateChange) }, unbindBluetoothAdapterStateChange() { if (!uni.offBluetoothAdapterStateChange) { return } try { uni.offBluetoothAdapterStateChange(this.handleBluetoothAdapterStateChange) } catch (e) {} }, handleBluetoothAdapterStateChange(res) { const available = !!(res && res.available) if (available) { this.btAdapterAvailable = true return } const wasAvailable = this.btAdapterAvailable this.btAdapterAvailable = false this.ver_dataflag = 1 this.shibainum = 0 this.deviceid = '' this.devicename = '' if (this.btProcessVisible) { this.showBtStepError('手机蓝牙未开启,请去系统设置打开', 'phone_bluetooth_off') } if (wasAvailable !== false) { this.ver_dataflag = 1 uni.showToast({ title: '检测到手机蓝牙已关闭,连接已断开', icon: 'none', duration: 2000 }) } }, resetBtProcessUI() { this.btProcessVisible = true this.btProgress = 0 this.btStepMessage = '准备开始蓝牙自检' this.btErrorMessage = '' this.btErrorType = '' }, setBtProgress(stage) { if (stage === 'scan') { this.btProgress = 50 this.btStepMessage = '正在搜索设备...' } else if (stage === 'connect') { this.btProgress = 80 this.btStepMessage = '已搜索到设备,正在建立蓝牙连接...' } else if (stage === 'send') { this.btProgress = 100 this.btStepMessage = '蓝牙已连接,正在发送命令...' } }, showBtStepError(message, type) { this.btErrorMessage = message this.btErrorType = type || '' this.btStepMessage = '蓝牙自检未通过' this.ver_dataflag = 1 this.shibainum = 0 this.jieliuflag = true }, openWechatBluetoothAuthSetting() { uni.openSetting({ success: () => {}, fail: () => { uni.showToast({ title: '请在微信设置中开启蓝牙权限', icon: 'none', duration: 2000 }) } }) }, goPhoneBluetoothSetting() { // #ifdef MP-WEIXIN if (typeof wx !== 'undefined' && wx.openSystemBluetoothSetting) { wx.openSystemBluetoothSetting({ success: () => {}, fail: () => { uni.showToast({ title: '无法直接跳转,请手动打开手机蓝牙', icon: 'none', duration: 2000 }) } }) return } // #endif uni.showToast({ title: '请手动进入系统设置打开蓝牙', icon: 'none', duration: 2000 }) }, waitForCondition(checker, timeout, interval) { timeout = timeout || 6000 interval = interval || 250 return new Promise((resolve) => { const start = Date.now() const timer = setInterval(() => { if (checker()) { clearInterval(timer) resolve(true) return } if (Date.now() - start >= timeout) { clearInterval(timer) resolve(false) } }, interval) }) }, sleep(ms) { ms = ms || 300 return new Promise((resolve) => setTimeout(resolve, ms)) }, async connectMatchedDeviceWithRetry(matchedDevice, maxRetry) { maxRetry = maxRetry || 3 for (let attempt = 1; attempt <= maxRetry; attempt++) { this.btStepMessage = '已搜索到设备,正在连接...(第' + attempt + '/' + maxRetry + '次)' this.ver_dataflag = 2 try { if (this.deviceid) { xBlufi.notifyConnectBle({ isStart: false, deviceId: this.deviceid, name: this.devicename || matchedDevice.name }) await this.sleep(250) } } catch (e) {} xBlufi.notifyConnectBle({ isStart: true, deviceId: matchedDevice.deviceId, name: matchedDevice.name }) this.deviceid = matchedDevice.deviceId this.devicename = matchedDevice.name const connected = await this.waitForCondition(() => this.ver_dataflag === 3, 12000, 300) if (connected) { return true } const recheckConnected = await this.waitForCondition(() => this.ver_dataflag === 3, 2000, 200) if (recheckConnected) { return true } if (attempt < maxRetry) { await this.sleep(500) } } return false }, safeIotPut(url, data, timeoutMs) { timeoutMs = timeoutMs || 12000 const reqPromise = this.$u.put(url, data).catch(() => ({ code: 500, msg: '网络异常,请稍后重试' })) const timeoutPromise = new Promise((resolve) => { setTimeout(() => { resolve({ code: 20003, msg: '请求超时' }) }, timeoutMs) }) return Promise.race([reqPromise, timeoutPromise]) }, _isIotTimeoutLike(res) { return !!(res && (res.code == 20001 || res.code == 20002 || res.code == 20003)) }, iotRequestTwiceBeforeBle(url, data) { return this.safeIotPut(url, data).then((res) => { if (res && res.code == 200) { return { type: 'success', res: res } } if (this._isIotTimeoutLike(res)) { return this.safeIotPut(url, data).then((res2) => { if (res2 && res2.code == 200) { return { type: 'success', res: res2 } } if (this._isIotTimeoutLike(res2)) { return { type: 'ble' } } return { type: 'error', res: res2 } }) } return { type: 'error', res: res } }) }, openBtAdapter() { return new Promise((resolve, reject) => { uni.openBluetoothAdapter({ success: () => resolve(true), fail: (err) => reject(err || {}) }) }) }, getBtAuthState() { return new Promise((resolve) => { uni.getSetting({ success: (res) => { const authSetting = (res && res.authSetting) || {} resolve(authSetting['scope.bluetooth']) }, fail: () => resolve(undefined) }) }) }, authorizeBt() { return new Promise((resolve) => { uni.authorize({ scope: 'scope.bluetooth', success: () => resolve(true), fail: () => resolve(false) }) }) }, async runPreciseBluetoothSelfCheck() { this.resetBtProcessUI() try { await this.openBtAdapter() } catch (err) { const errMsg = (err && err.errMsg) || '' if (errMsg.indexOf('auth deny') > -1 || errMsg.indexOf('authorize no response') > -1) { this.showBtStepError('微信蓝牙未授权,请去微信授权', 'wechat_auth_denied') } else { this.showBtStepError('手机蓝牙未开启,请去系统设置打开', 'phone_bluetooth_off') } return { ok: false, step: 1 } } let authState = await this.getBtAuthState() if (authState === false) { this.showBtStepError('微信蓝牙未授权,请去微信授权', 'wechat_auth_denied') return { ok: false, step: 2 } } if (typeof authState === 'undefined') { const authorized = await this.authorizeBt() if (!authorized) { this.showBtStepError('微信蓝牙未授权,请去微信授权', 'wechat_auth_denied') return { ok: false, step: 2 } } } this.setBtProgress('scan') xBlufi.notifyStartDiscoverBle({ isStart: true }) const found = await this.waitForCondition(() => { return this.devicesarr.some((device) => { const name = (device && device.name) || '' const mac = this.mac || '' return name && mac && name.slice(-12) === mac.slice(-12) }) }, 8000, 400) if (!found) { this.showBtStepError('未搜索到目标设备,请检查设备是否开机', 'device_not_found') return { ok: false, step: 3 } } const matchedDevice = this.devicesarr.find((device) => { const name = (device && device.name) || '' const mac = this.mac || '' return name && mac && name.slice(-12) === mac.slice(-12) }) if (!matchedDevice) { this.showBtStepError('未搜索到目标设备,请检查设备是否开机', 'device_not_found') return { ok: false, step: 3 } } this.setBtProgress('connect') xBlufi.notifyStartDiscoverBle({ isStart: false }) const connected = await this.connectMatchedDeviceWithRetry(matchedDevice, 3) if (!connected) { this.showBtStepError('已找到设备,但连接失败,请重试', 'connect_failed') return { ok: false, step: 4 } } this.btStepMessage = '蓝牙已连接,正在准备发送命令...' await this.sleep(2000) this.setBtProgress('send') return { ok: true } }, _buildUnlockPayload(overrides) { overrides = overrides || {} const macList = this.devicesarr.map((item) => (item.name || '').slice(-12)).filter(Boolean) return { id: this.unlockDeviceId, requiredIot: true, lat: this.lat, lon: this.lon, macList: macList, sendIot: overrides.sendIot !== undefined ? overrides.sendIot : true, adminFinalStatus: String(this.adminFinalStatus) } }, _merchantUnlockBluetoothSequence(onComplete) { uni.hideLoading() this.runPreciseBluetoothSelfCheck().then((checkRes) => { if (!checkRes.ok) { if (onComplete) { onComplete(false) } return } setTimeout(() => { xBlufi.notifySendCustomData({ customData: '11opensub5@' }) this.btProcessVisible = false this.jieliuflag = true }, 1000) const datas = this._buildUnlockPayload({ sendIot: false }) this.safeIotPut('/bst/device/iot/unlock', datas).then((res) => { if (res.code == 200) { uni.showToast({ title: '开锁成功', icon: 'success', duration: 1500 }) const logData = { mac: this.mac, reason: '代客下单开锁', command: '11opensub5@', longitude: this.lon, latitude: this.lat, result: '成功' } this.$u.post('/app/commandLog/bluetooth', logData).then(() => {}) if (onComplete) { onComplete(true) } } else { uni.showToast({ title: res.msg || '蓝牙开锁失败', icon: 'none', duration: 2500 }) if (onComplete) { onComplete(false) } } }) }).catch(() => { this.showBtStepError('已找到设备,但连接失败,请重试', 'connect_failed') if (onComplete) { onComplete(false) } }) }, _doAdminIotUnlock(onComplete) { const payload = this._buildUnlockPayload() this.iotRequestTwiceBeforeBle('/bst/device/iot/unlock', payload).then((out) => { if (out.type === 'ble') { this._merchantUnlockBluetoothSequence(onComplete) return } if (out.type === 'error') { this._merchantUnlockBluetoothSequence(onComplete) return } const res = out.res if (res.data && res.data.db == 1 && res.data.iot === false) { this._merchantUnlockBluetoothSequence(onComplete) return } uni.hideLoading() uni.showToast({ title: '开锁成功', icon: 'success', duration: 1500 }) if (onComplete) { onComplete(true) } }) }, startAdminUnlockAfterPay(options) { options = options || {} const deviceInfo = options.deviceInfo || this.deviceInfo const onComplete = options.onComplete const adminFinalStatus = options.adminFinalStatus || ADMIN_FINAL_STATUS_IN_USE if (!deviceInfo || (deviceInfo.id == null && !this.deviceId)) { uni.showToast({ title: '车辆信息异常,无法开锁', icon: 'none', duration: 2500 }) if (onComplete) { onComplete(false) } return } this.unlockDeviceId = deviceInfo.id != null ? deviceInfo.id : this.deviceId this.unlockSn = deviceInfo.sn || '' this.mac = deviceInfo.mac || '' this.adminFinalStatus = adminFinalStatus this.initBluetoothStack() uni.showLoading({ title: '车辆开锁中...', mask: true }) uni.getLocation({ type: 'gcj02', isHighAccuracy: true, success: (res) => { this.lat = res.latitude this.lon = res.longitude this._doAdminIotUnlock(onComplete) }, fail: () => { this.lat = null this.lon = null this._doAdminIotUnlock(onComplete) } }) }, funListenDeviceMsgEvent(options) { switch (options.type) { case xBlufi.XBLUFI_TYPE.TYPE_STATUS_CONNECTED: if (!options.result) { this.ver_dataflag = 1 this.deviceid = '' this.devicename = '' } break case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS: if (options.result) { this.devicesarr = options.data } break case xBlufi.XBLUFI_TYPE.TYPE_CONNECTED: if (options.result === true) { this.ver_dataflag = 3 this.jiance = false xBlufi.notifyInitBleEsp32({ deviceId: this.deviceid }) this.deviceid = options.data && options.data.deviceId ? options.data.deviceId : this.deviceid } break case xBlufi.XBLUFI_TYPE.TYPE_RECIEVE_CUSTON_DATA: this.ver_dataflag = 3 this.jiance = false break case xBlufi.XBLUFI_TYPE.TYPE_GET_DEVICE_LISTS_START: if (!options.result) { this.jiance = true } break } } } }