emergencyKey.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. <template>
  2. <view>
  3. <view v-if="showCheckinInfo">
  4. <view class="info">
  5. <view class="title">
  6. <view class="hotel-name">
  7. <!-- <u-text :text="hotelStore.hotel.name" bold color="#333333" size="30rpx"></u-text> -->
  8. <text>{{hotelStore.hotel.name}}</text>
  9. </view>
  10. <view class="tips">
  11. <!-- <u-text text="入住信息" color="#666666" size="24rpx"></u-text> -->
  12. <text>入住信息</text>
  13. </view>
  14. </view>
  15. <view class="checkin-info">
  16. <view class="checkin-info--item">
  17. <view class="checkin-info--item--text">
  18. <!-- <u-text text="所在楼层: " color="#999999" size="26rpx"></u-text> -->
  19. <text>所在楼层:</text>
  20. </view>
  21. <view class="checkin-info--item--text">
  22. <!-- <u-text :text="checkinInfo.floor + '楼'" color="#999999" size="26rpx"></u-text> -->
  23. <text>{{`${checkinInfo.floor}楼`}}</text>
  24. </view>
  25. </view>
  26. <view class="checkin-info--item">
  27. <view class="checkin-info--item--text">
  28. <!-- <u-text text="房间信息 : " color="#999999" size="26rpx"></u-text> -->
  29. <text>房间信息:</text>
  30. </view>
  31. <view class="checkin-info--item--text">
  32. <!-- <u-text :text="checkinInfo.room" color="#999999" size="26rpx"></u-text> -->
  33. <text>{{checkinInfo.room}}</text>
  34. </view>
  35. </view>
  36. <view class="checkin-info--item">
  37. <view class="checkin-info--item--text">
  38. <!-- <u-text text="起始时间 : " color="#999999" size="26rpx"></u-text> -->
  39. <text>起始时间:</text>
  40. </view>
  41. <view class="checkin-info--item--text">
  42. <!-- <u-text :text="formatDateTime(checkinInfo.startTime)" color="#999999" size="26rpx"></u-text> -->
  43. <text>{{formatDateTime(checkinInfo.startTime)}}</text>
  44. </view>
  45. </view>
  46. <view class="checkin-info--item">
  47. <view class="checkin-info--item--text">
  48. <!-- <u-text text="结束时间 : " color="#999999" size="26rpx"></u-text> -->
  49. <text>结束时间:</text>
  50. </view>
  51. <view class="checkin-info--item--text">
  52. <!-- <u-text :text="formatDateTime(checkinInfo.endTime)" color="#999999" size="26rpx"></u-text> -->
  53. <text>{{formatDateTime(checkinInfo.endTime)}}</text>
  54. </view>
  55. </view>
  56. </view>
  57. </view>
  58. <view class="room__operate">
  59. <view v-if="doorLockStatus === 'doorLockReady'" class="room__operate--display" @click="openBoYuanLock">
  60. <uni-icons type="locked" size="30"></uni-icons>
  61. <view class="display__desc">手机开门</view>
  62. </view>
  63. <view v-if="doorLockStatus === 'doorLockUnavailable'" class="room__operate--display">
  64. <uni-icons type="locked" size="30"></uni-icons>
  65. <view class="display__desc">手机开门</view>
  66. </view>
  67. <view v-if="doorLockStatus === 'doorLockOpening'" class="room__operate--display">
  68. <view class="display__desc">{{openDoorTips}}</view>
  69. </view>
  70. </view>
  71. </view>
  72. <view v-else class="not-checkin">
  73. <view class="not-checkin-text">
  74. <text>您还未入住!</text>
  75. </view>
  76. <view class="not-checkin-text">
  77. <text>若已退房或房间到期,请至前台办理</text>
  78. </view>
  79. </view>
  80. </view>
  81. </template>
  82. <script setup>
  83. import {
  84. ref
  85. } from "vue";
  86. import {
  87. getCheckinInfo,
  88. getOpenDoorInfo,
  89. unloadBLEOpenDoorLog
  90. } from "../../utils/api"
  91. import {
  92. useHotelStore
  93. } from '@/store/hotelStore.js'
  94. import {
  95. onLoad
  96. } from '@dcloudio/uni-app'
  97. import moment from "moment";
  98. import {
  99. isIosDevice,
  100. ErrorCode,
  101. TimeoutValue,
  102. needScanDevice,
  103. parseBELDeviceId,
  104. genReqSecretPkg,
  105. genOpDoorPkg,
  106. hexStringToUint8Array,
  107. decryptAes,
  108. Delayed
  109. } from "../../utils/emergencyKeyUtils";
  110. const hotelStore = useHotelStore()
  111. const showCheckinInfo = ref(false)
  112. const checkinInfo = ref(null)
  113. const openDoorInfo = ref(null)
  114. const doorLockReady = "doorLockReady"
  115. const doorLockOpening = "doorLockOpening"
  116. const doorLockStatus = ref("doorLockUnavailable")
  117. const openDoorTips = ref("正在扫描门锁蓝牙...")
  118. const openBoYuanLock = async () => {
  119. console.log("openDoorInfo", openDoorInfo.value);
  120. doorLockStatus.value = doorLockOpening
  121. openDoorTips.value = "正在扫描门锁蓝牙..."
  122. const BluetoothLifeData = {
  123. name: null, // 蓝牙门锁名称
  124. hotelId: null,
  125. operationType: 1, // 蓝牙开门
  126. isBluetoothOpened: false, // 蓝牙开关是否打开
  127. bluetoothConnected: false, // 蓝牙是否连接上
  128. deviceId: null, // 进行连接时,蓝牙的deviceId。安卓可以计算出来,iOS需要扫描
  129. originalSecret: null,
  130. sessionKey: null,
  131. serviceId: "0000FFF0-0000-1000-8000-00805F9B34FB", // 蓝牙交互服务的service的UUID。苹果端支付宝侧有点特殊,需进行处理
  132. characteristicId: "0000FFF1-0000-1000-8000-00805F9B34FB", // 特征值的UUID。苹果端支付宝侧有点特殊,需进行处理
  133. initBluetoothAdapterTime: null, // 进行蓝牙初始化时的时间
  134. startBluetoothDiscoveryTime: null, // 蓝牙初始化完成同时开始探索蓝牙 gap1蓝牙初始化
  135. targetBluetoothFoundTime: null, // 目标蓝牙被找到,同时开始蓝牙的连接 gap2探索目标蓝牙
  136. targetBluetoothConnectedTime: null, // 蓝牙被连接时的时间 gap3连接蓝牙
  137. opForIosEndedTime: null, // 连接蓝牙后,完成iOS设备所必须的操作时的时间 gap4 iOS服务耗时
  138. requestSessionKeyTime: null, // 请求会话秘钥的开始时间 gap5 订阅特征值变化
  139. getSessionKeyPackageTime: null, // 获取到请求会话秘钥ack的时间
  140. requestBleOpenDoorTime: null, // 发送开门请求的时间
  141. getOpenDoorPackageTime: null, // 获取到请求开门返回包的时间
  142. result: null, // 蓝牙操作结果,与错误码相对应
  143. endTime: null, // 流程中断的时间点
  144. mobileOs: null,
  145. totalTime: null,
  146. isLocationEnabled: false,
  147. androidOptimization: false,
  148. hostName: null, // 小程序宿主名
  149. hostVersion: null, // 小程序宿主版本
  150. openDoorParam: null, // 开门时的自定义参数
  151. bluetoothAuthorized: false, // 蓝牙授权开启情况
  152. errMessage: '',
  153. // 剩下的为上传相关记录的操作
  154. floor: null,
  155. room: null
  156. }
  157. BluetoothLifeData.androidOptimization = openDoorInfo.value.androidOptimization
  158. BluetoothLifeData.deviceId = openDoorInfo.value.doorLockDeviceId
  159. BluetoothLifeData.name = openDoorInfo.value.doorLockName
  160. BluetoothLifeData.openDoorParam = openDoorInfo.value.openDoorParam
  161. BluetoothLifeData.originalSecret = openDoorInfo.value.doorLockKey
  162. BluetoothLifeData.hotelId = hotelStore.hotel.hotelId
  163. BluetoothLifeData.room = checkinInfo.value.room
  164. BluetoothLifeData.floor = checkinInfo.value.floor
  165. const systemInfo = uni.getSystemInfoSync();
  166. //console.log("小程序宿主环境信息", systemInfo);
  167. BluetoothLifeData.hostName = systemInfo.hostName // 小程序的宿主名称,微信、支付宝等
  168. BluetoothLifeData.hostVersion = systemInfo.hostVersion; // 宿主环境的版本信息
  169. BluetoothLifeData.mobileOs = systemInfo.osName; // 收集操作系统
  170. // 后期考虑能否获取到开发环境基础库的版本,并添加进数据库
  171. // 处理宿主系统为ios且宿主环境为alipay时的服务id和特征值id
  172. if (isIosDevice(BluetoothLifeData) && BluetoothLifeData.hostName === 'alipay') {
  173. BluetoothLifeData.serviceId = 'FFF0'
  174. BluetoothLifeData.characteristicId = 'FFF1'
  175. }
  176. /**
  177. * 蓝牙的开关以及在未开启安卓优化时位置的开关
  178. */
  179. const systemSetting = uni.getSystemSetting()
  180. //console.log("获取系统设置信息", systemSetting);
  181. BluetoothLifeData.isBluetoothOpened = systemSetting.bluetoothEnabled
  182. // 处理手机蓝牙未打开
  183. if (!BluetoothLifeData.isBluetoothOpened) {
  184. return ErrorCode.get(10001)
  185. }
  186. // 不开启安卓优化,设备系统是安卓,校验位置是否开启
  187. BluetoothLifeData.isLocationEnabled = systemSetting.locationEnabled
  188. //console.log("BluetoothLifeData", BluetoothLifeData);
  189. /**
  190. * 获取用户的当前设置(蓝牙的授权信息)。返回值中只会出现小程序已经向用户请求过的权限。
  191. */
  192. const userSetting = await uni.getSetting()
  193. //console.log("获取用户系统设置信息", userSetting);
  194. if (BluetoothLifeData.hostName === "alipay") {
  195. BluetoothLifeData.bluetoothAuthorized = userSetting.authSetting['bluetooth'] ?
  196. userSetting.authSetting['bluetooth'] : false
  197. } else if (BluetoothLifeData.hostName === "WeChat") {
  198. BluetoothLifeData.bluetoothAuthorized = userSetting.authSetting['scope.bluetooth'] ?
  199. userSetting.authSetting['scope.bluetooth'] : false
  200. }
  201. //console.log("BluetoothLifeData.bluetoothAuthorized", BluetoothLifeData.bluetoothAuthorized);
  202. /**
  203. *
  204. * 开启蓝牙适配器,必须操作,并在Promise中处理授权相关的信息
  205. */
  206. BluetoothLifeData.initBluetoothAdapterTime = +new Date();
  207. console.info("开始打开蓝牙适配器")
  208. let openBluetoothAdapter;
  209. try {
  210. // 请求授权时:同意:支付宝{isSupportBLE: true}
  211. // 请求授权时:拒绝:new Error()
  212. openBluetoothAdapter = await uni.openBluetoothAdapter()
  213. // console.log("openBluetoothAdapter", openBluetoothAdapter);
  214. if (BluetoothLifeData.hostName === "alipay") {
  215. BluetoothLifeData.bluetoothAuthorized = openBluetoothAdapter.isSupportBLE
  216. } else {
  217. BluetoothLifeData.bluetoothAuthorized = true
  218. }
  219. } catch (err) {
  220. BluetoothLifeData.bluetoothAuthorized = false
  221. //console.log("打开蓝牙适配器错误", err);
  222. if ((BluetoothLifeData.hostName === "alipay" && err.error === 2001) ||
  223. (BluetoothLifeData.hostName === "WeChat" && err.errno === 103)) {
  224. // 用户拒绝蓝牙授权,引导用户进行蓝牙授权
  225. await new Promise(async (resolve, reject) => {
  226. uni.showModal({
  227. title: "温馨提示",
  228. content: "蓝牙开门功能需要使用您的蓝牙,授权后才能开门。",
  229. showCancel: false,
  230. success: async (res) => {
  231. if (res.confirm) {
  232. await uni.openSetting().then(async (res) => {
  233. //console.log("openSetting", res);
  234. if (BluetoothLifeData.hostName ===
  235. "alipay") {
  236. BluetoothLifeData.bluetoothAuthorized =
  237. res.authSetting['bluetooth']
  238. } else if (BluetoothLifeData.hostName ===
  239. "WeChat") {
  240. BluetoothLifeData.bluetoothAuthorized =
  241. res.authSetting['scope.bluetooth']
  242. }
  243. try {
  244. await uni.openBluetoothAdapter()
  245. } catch (error) {
  246. //console.log(error);
  247. }
  248. resolve()
  249. })
  250. }
  251. },
  252. });
  253. })
  254. }
  255. }
  256. //console.log("1------------------>开启蓝牙适配器", openBluetoothAdapter);
  257. // 判断蓝牙是否授权,未授权直接return
  258. if (!BluetoothLifeData.bluetoothAuthorized) {
  259. BluetoothLifeData.endTime = new Date();
  260. BluetoothLifeData.totalTime =
  261. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  262. // 蓝牙操作未授权
  263. BluetoothLifeData.result = 10002;
  264. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  265. //console.log("上传开门记录结果", uploadRes);
  266. await closeBluetoothAdapter();
  267. doorLockStatus.value = doorLockReady
  268. return ErrorCode.get(10002);
  269. }
  270. /**
  271. *
  272. * 在Promise中调用onBluetoothDeviceFound,直到找到目的设备
  273. * 针对android设备且开启了安卓设备优化,直接跳过此流程
  274. */
  275. if (needScanDevice(BluetoothLifeData)) {
  276. console.info("发现目标蓝牙门锁")
  277. if (BluetoothLifeData.mobileOs === "android" && !BluetoothLifeData.isLocationEnabled) {
  278. BluetoothLifeData.result = 10008;
  279. BluetoothLifeData.endTime = new Date();
  280. BluetoothLifeData.totalTime =
  281. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  282. await closeBluetoothAdapter();
  283. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  284. doorLockStatus.value = doorLockReady
  285. //console.log("上传开门记录结果", uploadRes);
  286. return ErrorCode.get(10008);
  287. }
  288. await new Promise(async (resolve, reject) => {
  289. //console.log("1.1------------------>发现周边蓝牙设备");
  290. // 注册超时函数
  291. const timeout = setTimeout(() => {
  292. if (!BluetoothLifeData.targetBluetoothFoundTime) {
  293. //console.log("onBluetoothDeviceFound Promise 发现设备超时");
  294. BluetoothLifeData.result = 10003;
  295. uni.stopBluetoothDevicesDiscovery();
  296. reject("发现设备超时");
  297. }
  298. }, TimeoutValue.findDevice);
  299. // 注册发现蓝牙事件
  300. uni.onBluetoothDeviceFound((res) => {
  301. let devices = res.devices.filter((device) =>
  302. device.name === BluetoothLifeData.name
  303. );
  304. //console.log("onBluetoothDeviceFound 发现设备数", devices.length);
  305. if (devices.length <= 0) return;
  306. let {
  307. deviceId,
  308. name
  309. } = devices[0];
  310. // console.log("deviceId", deviceId, "deviceName", name);
  311. BluetoothLifeData.deviceId = deviceId;
  312. BluetoothLifeData.targetBluetoothFoundTime = +new Date();
  313. uni.stopBluetoothDevicesDiscovery()
  314. clearTimeout(timeout);
  315. resolve({
  316. bleFound: true
  317. });
  318. });
  319. // 调用扫描蓝牙API,扫描周边蓝牙
  320. BluetoothLifeData.startBluetoothDiscoveryTime = +new Date();
  321. let discovery = await uni.startBluetoothDevicesDiscovery({
  322. powerLevel: "high"
  323. });
  324. //console.log("startBluetoothDevicesDiscovery", discovery);
  325. }).catch((err) => {
  326. //console.log("onBluetoothDeviceFound Promise catch err", err);
  327. });
  328. /**
  329. *
  330. * 搜索蓝牙设备超时的处理逻辑
  331. */
  332. if (!BluetoothLifeData.targetBluetoothFoundTime) {
  333. //console.log("超时时间内未发现门锁");
  334. BluetoothLifeData.endTime = new Date();
  335. BluetoothLifeData.totalTime =
  336. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  337. await closeBluetoothAdapter();
  338. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  339. doorLockStatus.value = doorLockReady
  340. //console.log("上传开门记录结果", uploadRes);
  341. return ErrorCode.get(10003)
  342. }
  343. } else {
  344. //该分支不进行蓝牙扫描
  345. BluetoothLifeData.startBluetoothDiscoveryTime = +new Date();
  346. BluetoothLifeData.targetBluetoothFoundTime = +new Date();
  347. // 根据转换规则,置android设备的deviceId
  348. BluetoothLifeData.deviceId = parseBELDeviceId(
  349. BluetoothLifeData.deviceId
  350. );
  351. //console.log("android设备连接蓝牙时的deviceId", BluetoothLifeData.deviceId);
  352. }
  353. /**
  354. *
  355. * 找到目标设备,开始进行蓝牙连接
  356. * 在Promise中连接蓝牙设备,超时就 关闭蓝牙适配器 ,第一次超时return是否会影响第二次的成功连接
  357. */
  358. console.info("开始连接目标蓝牙门锁")
  359. openDoorTips.value = "正在连接门锁蓝牙..."
  360. await new Promise(async (resolve, reject) => {
  361. //console.log("2------------------>进行蓝牙连接");
  362. // 注册超时函数
  363. const timeout = setTimeout(() => {
  364. if (!BluetoothLifeData.targetBluetoothConnectedTime) {
  365. BluetoothLifeData.result = 10004;
  366. //console.log("蓝牙连接超时");
  367. reject("蓝牙连接超时");
  368. }
  369. }, TimeoutValue.connectBLE)
  370. // 微信和支付宝连接蓝牙的函数不一样
  371. // 发起蓝牙连接
  372. uni.createBLEConnection({
  373. deviceId: BluetoothLifeData.deviceId,
  374. success: (res) => {
  375. clearTimeout(timeout)
  376. BluetoothLifeData.bluetoothConnected = true;
  377. BluetoothLifeData.targetBluetoothConnectedTime = +new Date();
  378. //console.log("蓝牙连接成功", res);
  379. resolve("蓝牙连接成功")
  380. },
  381. fail: (err) => {
  382. BluetoothLifeData.result = 10005;
  383. //console.log("蓝牙连接失败", err);
  384. }
  385. })
  386. }).catch(err => {
  387. //console.log("createBLEConnection Promise catch err", err);
  388. })
  389. // 判断蓝牙是否连接成功,若失败则直接return
  390. if (!BluetoothLifeData.bluetoothConnected) {
  391. BluetoothLifeData.endTime = new Date();
  392. BluetoothLifeData.totalTime =
  393. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  394. await closeBluetoothAdapter();
  395. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  396. //console.log("上传开门记录结果", uploadRes);
  397. doorLockStatus.value = doorLockReady
  398. if (BluetoothLifeData.result === 10004) {
  399. return ErrorCode.get(10004)
  400. } else {
  401. return ErrorCode.get(10005)
  402. }
  403. }
  404. /**
  405. *
  406. * 蓝牙连接成功后,对iOS设备而言,需对服务及特征值进行扫描
  407. */
  408. if (isIosDevice(BluetoothLifeData)) {
  409. await new Promise((reslove, reject) => {
  410. uni.getBLEDeviceServices({
  411. deviceId: BluetoothLifeData.deviceId,
  412. success: res => {
  413. //console.log("获取蓝牙设备所有服务列表成功", res);
  414. reslove(res)
  415. },
  416. fail: err => {
  417. //console.log("获取蓝牙设备所有服务列表失败", err);
  418. reject(err)
  419. }
  420. })
  421. }).catch(err => {
  422. //console.log("获取蓝牙设备所有服务列表Promise err", err);
  423. })
  424. await new Promise((reslove, reject) => {
  425. uni.getBLEDeviceCharacteristics({
  426. deviceId: BluetoothLifeData.deviceId,
  427. serviceId: BluetoothLifeData.serviceId,
  428. success: res => {
  429. //console.log("获取低功耗蓝牙设备服务中所有特征成功", res);
  430. reslove(res)
  431. },
  432. fail: err => {
  433. //console.log("获取低功耗蓝牙设备服务中所有特征失败", err);
  434. reject(err)
  435. }
  436. })
  437. }).catch(err => {
  438. //console.log("获取低功耗蓝牙设备服务中所有特征Promise err", err);
  439. })
  440. BluetoothLifeData.opForIosEndedTime = +new Date();
  441. } else {
  442. BluetoothLifeData.opForIosEndedTime = +new Date();
  443. }
  444. // 对主服务特征值进行订阅
  445. await new Promise((resolve, reject) => {
  446. uni.notifyBLECharacteristicValueChange({
  447. deviceId: BluetoothLifeData.deviceId, // 蓝牙设备 id
  448. serviceId: BluetoothLifeData.serviceId, // 蓝牙特征对应服务的 UUID
  449. characteristicId: BluetoothLifeData.characteristicId, // 蓝牙特征的 UUID
  450. state: true, // 是否启用 notify
  451. success: function(res) {
  452. //console.log("对主服务特征值订阅成功", res);
  453. resolve(res);
  454. },
  455. fail: function(res) {
  456. //console.log("对主服务特征值订阅失败", res);
  457. reject(res);
  458. },
  459. });
  460. }).catch(err => {
  461. //console.log("对主服务特征值订阅Promise err", err);
  462. })
  463. // 向蓝牙发请求,请求会话秘钥(写特征值),解出会话秘钥
  464. await new Promise(async (resolve, reject) => {
  465. //console.log("3------------------>请求会话秘钥");
  466. // 注册请求蓝牙会话秘钥超时函数
  467. const timeout = setTimeout(() => {
  468. if (!BluetoothLifeData.getSessionKeyPackageTime) {
  469. BluetoothLifeData.result = 10006;
  470. reject("请求会话秘钥超时");
  471. }
  472. }, TimeoutValue.requestSessionKey);
  473. uni.offBLECharacteristicValueChange();
  474. const submitSessionKeyReq = onBLECharacteristicValueChange();
  475. BluetoothLifeData.requestSessionKeyTime = +new Date();
  476. await writeBLECharacteristicValue(genReqSecretPkg());
  477. //console.log("发完请求会话密钥请求报文");
  478. submitSessionKeyReq.then((res) => {
  479. //console.log("请求会话密钥promise", res);
  480. if (res === 0x01) {
  481. clearTimeout(timeout);
  482. resolve({
  483. requestSessionKey: true
  484. });
  485. } else {
  486. clearTimeout(timeout);
  487. reject({
  488. message: "应答包获取错误,应为请求会话秘钥的应答包。",
  489. });
  490. }
  491. }).catch(err => {
  492. //console.log("处理会话密钥出现问题", err);
  493. })
  494. }).catch((err) => {
  495. //console.log("监听函数,请求会话秘钥。catch err", err);
  496. });
  497. // 获取会话秘钥超时的处理逻辑
  498. if (!BluetoothLifeData.getSessionKeyPackageTime) {
  499. BluetoothLifeData.endTime = new Date();
  500. BluetoothLifeData.totalTime =
  501. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  502. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  503. //console.log("上传开门记录结果", uploadRes);
  504. await closeBLEConnection(BluetoothLifeData.deviceId);
  505. await closeBluetoothAdapter();
  506. doorLockStatus.value = doorLockReady
  507. return ErrorCode.get(10006)
  508. }
  509. /**
  510. *
  511. * 解完请求会话秘钥后,发蓝牙开门报文,解蓝牙开门返回包
  512. */
  513. openDoorTips.value = "正在开启门锁..."
  514. console.info("开启目标门锁中...")
  515. await new Promise(async (resolve, reject) => {
  516. //console.log("4------------------>发蓝牙开门报文");
  517. // 设置蓝牙开门的超时
  518. const timeout = setTimeout(() => {
  519. if (!BluetoothLifeData.getOpenDoorPackageTime) {
  520. BluetoothLifeData.result = 10007;
  521. reject("蓝牙开门超时");
  522. }
  523. }, TimeoutValue.openDoor);
  524. uni.offBLECharacteristicValueChange();
  525. const requestBLEOpenDoorRes = onBLECharacteristicValueChange();
  526. BluetoothLifeData.requestBleOpenDoorTime = +new Date();
  527. await writeBLECharacteristicValue(genOpDoorPkg(BluetoothLifeData.sessionKey));
  528. //console.log("发完蓝牙开门报文");
  529. requestBLEOpenDoorRes.then((res) => {
  530. if (res == 0x11) {
  531. clearTimeout(timeout);
  532. resolve({
  533. bluetoothOpenDoor: true
  534. });
  535. } else {
  536. clearTimeout(timeout);
  537. reject({
  538. message: "应答包获取错误,应为请求会话秘钥的应答包。",
  539. });
  540. }
  541. });
  542. }).catch((err) => {
  543. //console.log("监听函数,蓝牙开门。catch err", err);
  544. });
  545. // 判断蓝牙开门是否成功,如果失败,则return
  546. if (!BluetoothLifeData.getOpenDoorPackageTime) {
  547. //console.log("蓝牙开门失败");
  548. BluetoothLifeData.endTime = new Date();
  549. BluetoothLifeData.totalTime =
  550. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  551. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  552. //console.log("上传开门记录结果", uploadRes);
  553. await closeBLEConnection(BluetoothLifeData.deviceId);
  554. await closeBluetoothAdapter();
  555. doorLockStatus.value = doorLockReady
  556. return ErrorCode.get(10007)
  557. }
  558. // 断开蓝牙连接,关闭蓝牙适配器,并上传所有记录
  559. await closeBLEConnection(BluetoothLifeData.deviceId);
  560. await closeBluetoothAdapter();
  561. BluetoothLifeData.endTime = new Date();
  562. BluetoothLifeData.totalTime =
  563. BluetoothLifeData.endTime - BluetoothLifeData.initBluetoothAdapterTime;
  564. BluetoothLifeData.result = 10000
  565. const uploadRes = await unloadBLEOpenDoorLog(BluetoothLifeData);
  566. //console.log("上传开门记录结果", uploadRes);
  567. //console.log(BluetoothLifeData);
  568. doorLockStatus.value = doorLockReady
  569. return ErrorCode.get(10000)
  570. function onBLECharacteristicValueChange() {
  571. return new Promise((resolve, reject) => {
  572. const BleStruct = {
  573. firstPacketReceived: false, // 是否已接收第一个报文
  574. buffer: null, // 用于缓存数据流的 ArrayBuffer
  575. cmd: null, //返回数据包命令号
  576. length: 0, //数据包长度
  577. received: 0,
  578. errCode: 0,
  579. };
  580. uni.onBLECharacteristicValueChange(async (res) => {
  581. //console.log("onBLECharacteristicValueChange", res); //打印特征值
  582. let receiveBuffer = BluetoothLifeData.hostName === 'alipay' ?
  583. hexStringToUint8Array(res.value) : new Uint8Array(res.value);
  584. // 接收到的报文包含开始码等报文头数据
  585. if (receiveBuffer[0] === 0xa5 && BleStruct.firstPacketReceived === false) {
  586. BleStruct.firstPacketReceived = true;
  587. BleStruct.length = (receiveBuffer[5] << 8) + receiveBuffer[4] +
  588. 6; //算出数据长度
  589. BleStruct.received = 0;
  590. BleStruct.cmd = receiveBuffer[1]; //命令号赋值
  591. BleStruct.errCode = receiveBuffer[6];
  592. BleStruct.buffer = new Uint8Array(BleStruct.length);
  593. // 错误码!= 0,丢弃报文
  594. if (BleStruct.errCode != 0) {
  595. BleStruct.firstPacketReceived = false;
  596. BluetoothLifeData.result = BleStruct.errCode;
  597. reject({
  598. message: "errCode != 0",
  599. errCode: BleStruct.errCode
  600. });
  601. }
  602. }
  603. // 接收到的报文的长度不对,丢弃报文
  604. if (receiveBuffer.length > BleStruct.length - BleStruct.received) {
  605. BleStruct.firstPacketReceived = false;
  606. reject({
  607. message: "onBLECharacteristicValueChange: length err",
  608. bleStruct: BleStruct,
  609. length: receiveBuffer.length,
  610. });
  611. return;
  612. }
  613. BleStruct.buffer.set(receiveBuffer, BleStruct
  614. .received); // 将接收到的报文放入buffer容器
  615. BleStruct.received += receiveBuffer.length;
  616. // 已经获取到全部报文
  617. if (BleStruct.received >= BleStruct.length) {
  618. BleStruct.firstPacketReceived = false;
  619. const returnCmd = analysisData(
  620. BleStruct.cmd,
  621. BleStruct.buffer.slice(7)
  622. );
  623. resolve(returnCmd);
  624. }
  625. });
  626. });
  627. }
  628. function analysisData(cmd, data) {
  629. // 请求会话密钥的返回包,对其进行处理
  630. if (cmd === 0x01) {
  631. BluetoothLifeData.getSessionKeyPackageTime = +new Date();
  632. const originalKey = BluetoothLifeData.originalSecret;
  633. const encryptedData = data;
  634. const sessionKey = decryptAes(originalKey, encryptedData);
  635. const hexString = Array.from(sessionKey)
  636. .map((byte) => byte.toString(16).padStart(2, "0"))
  637. .join("");
  638. BluetoothLifeData.sessionKey = hexString;
  639. return 0x01;
  640. }
  641. //蓝牙开门返回包,对其进行处理
  642. if (cmd == 0x11) {
  643. BluetoothLifeData.getOpenDoorPackageTime = +new Date();
  644. return 0x11;
  645. }
  646. }
  647. // 向蓝牙低功耗设备特征值中写入二进制数据
  648. async function writeBLECharacteristicValue(packetData) {
  649. while (packetData.byteLength > 0) {
  650. let len = Math.min(16, packetData.byteLength);
  651. try {
  652. await uni.writeBLECharacteristicValue({
  653. deviceId: BluetoothLifeData.deviceId,
  654. serviceId: BluetoothLifeData.serviceId,
  655. characteristicId: BluetoothLifeData.characteristicId,
  656. value: packetData.slice(0, len),
  657. });
  658. } catch (error) {
  659. //console.log("writeBLECharacteristicValue err", error);
  660. }
  661. packetData = packetData.slice(len);
  662. // 写完第一个报文后等待半秒
  663. await Delayed(500);
  664. }
  665. }
  666. // 关闭连接,也是异步操作
  667. function closeBLEConnection(deviceId = "") {
  668. return new Promise((resolve, reject) => {
  669. uni.closeBLEConnection({
  670. deviceId: deviceId,
  671. success: (res) => {
  672. //console.log("断开蓝牙连接成功", res);
  673. resolve(res);
  674. },
  675. fail: (res) => {
  676. //console.log("断开蓝牙连接失败", res);
  677. reject(res);
  678. },
  679. });
  680. });
  681. }
  682. // 关闭蓝牙模块
  683. function closeBluetoothAdapter() {
  684. return new Promise((resolve, reject) => {
  685. uni.closeBluetoothAdapter({
  686. success: function(res) {
  687. //console.log("success closeBluetoothAdapter:", res);
  688. resolve(res);
  689. },
  690. fail: function(res) {
  691. //console.log("fail closeBluetoothAdapter:", res);
  692. resolve();
  693. },
  694. });
  695. });
  696. }
  697. }
  698. onLoad(async (options) => {
  699. console.log("onLoad", options);
  700. try {
  701. const infoRes = await getCheckinInfo(hotelStore.hotel.hotelId)
  702. // console.log("获取入住记录结果", infoRes);
  703. if (!infoRes.data || !infoRes.success) {
  704. return
  705. }
  706. checkinInfo.value = infoRes.data
  707. showCheckinInfo.value = true
  708. } catch (err) {
  709. console.log("获取入住记录异常", err);
  710. return
  711. }
  712. try {
  713. console.log("checkinInfo", checkinInfo.value);
  714. const res = await getOpenDoorInfo({
  715. hotelId: hotelStore.hotel.hotelId,
  716. floor: checkinInfo.value.floor,
  717. room: checkinInfo.value.room
  718. })
  719. console.log("获取门锁信息结果", res);
  720. if (!res.data || !res.success) {
  721. return
  722. }
  723. openDoorInfo.value = res.data
  724. doorLockStatus.value = doorLockReady
  725. } catch (err) {
  726. console.log("获取门锁信息异常", err);
  727. return
  728. }
  729. })
  730. const formatDateTime = (dateTime) => {
  731. return moment(dateTime).format("YYYY年MM月DD日HH时mm分")
  732. }
  733. </script>
  734. <style lang="scss">
  735. page {
  736. background-color: #FFFFFF;
  737. .info {
  738. background: #F5F8FE;
  739. box-shadow: -6px 17px 21px 0px rgba(112, 152, 246, 0.14);
  740. margin-bottom: 200rpx;
  741. .title {
  742. display: flex;
  743. justify-content: space-between;
  744. padding: 0 40rpx;
  745. line-height: 80rpx;
  746. border-bottom: 2rpx solid #E0E8FB;
  747. }
  748. .checkin-info {
  749. margin: 20rpx auto;
  750. .checkin-info--item {
  751. display: flex;
  752. margin: 0 40rpx;
  753. .checkin-info--item--text {
  754. margin: 10rpx;
  755. margin-left: 0;
  756. }
  757. &:last-child {
  758. padding-bottom: 20rpx;
  759. }
  760. }
  761. }
  762. }
  763. .room__operate {
  764. height: 90rpx;
  765. display: flex;
  766. margin: 2*5rpx 2 * 15rpx;
  767. background-color: #9e97c3;
  768. align-items: center;
  769. justify-content: center;
  770. border-radius: 10rpx;
  771. .room__operate--display {
  772. height: 2 * 25rpx;
  773. margin: 5rpx;
  774. padding: 2 * 6rpx;
  775. display: flex;
  776. align-items: center;
  777. justify-content: center;
  778. width: 100%;
  779. .display__desc {
  780. height: 100%;
  781. display: flex;
  782. align-items: center;
  783. justify-content: center;
  784. padding-left: 10rpx;
  785. font-size: 40rpx;
  786. color: #FFFFFF;
  787. font-size: 32rpx;
  788. }
  789. }
  790. }
  791. }
  792. .not-checkin {
  793. margin-top: 40vh;
  794. .not-checkin-text {
  795. display: flex;
  796. justify-content: center;
  797. }
  798. }
  799. </style>