bluetoothMixin.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. import {
  2. genReqSecretPkg,
  3. genOpDoorPkg,
  4. genQuPowerPkg,
  5. gnQuDoorLogPkg,
  6. Delayed,
  7. decryptAes,
  8. parseBELDeviceId,
  9. hexStringToUint8Array
  10. } from "../utils/bluetoothUtils";
  11. import {
  12. getCheckinInfoAndLockInfo,
  13. getBluetoothKey,
  14. remoteOpenDoor,
  15. unloadBLEOpenDoorLog,
  16. getCheckinInfo
  17. } from "../utils/api";
  18. import moment from "moment";
  19. import {
  20. mapState
  21. } from "vuex";
  22. export const bluetooth = {
  23. data() {
  24. return {
  25. // allowedOpenDoor为0时允许点击开门,为1时点击开门替换为loading状态,为2时会弹提示
  26. allowedOpenDoor: 2,
  27. isOpeningDoor: false,
  28. loadingContent: "初始化蓝牙中...",
  29. hotelId: null,
  30. openDoorInfo: {
  31. gatewayDeviceId: "",
  32. bluetoothDeviceId: "",
  33. bluetoothName: "",
  34. bluetoothKey: "",
  35. androidOptimization: true,
  36. openDoorParam: null,
  37. },
  38. checkinInfo: {}
  39. };
  40. },
  41. computed: {
  42. ...mapState("m_user", ["userInfo"]),
  43. ...mapState("m_business", ["currentHotel"])
  44. },
  45. methods: {
  46. async bluetoothOperate() {
  47. const vueComponent = this;
  48. /**
  49. * 此次操作的生命周期数据,会上传给后端
  50. */
  51. const bluetoothLifeData = {
  52. name: this.openDoorInfo.bluetoothName, // 蓝牙门锁名称
  53. hotelId: this.hotelId,
  54. operationType: 1,
  55. isBluetoothOpened: null, // 蓝牙开关是否打开
  56. bluetoothConnected: false, // 蓝牙是否连接上
  57. deviceId: null, // 进行连接时,蓝牙的deviceId。安卓可以计算出来,iOS需要扫描
  58. serviceId: "0000FFF0-0000-1000-8000-00805F9B34FB", // 蓝牙交互服务的service的UUID
  59. characteristicId: "0000FFF1-0000-1000-8000-00805F9B34FB", // 特征值的UUID
  60. initBluetoothAdapterTime: null, // 进行蓝牙初始化时的时间
  61. startBluetoothDiscoveryTime: null, // 蓝牙初始化完成同时开始探索蓝牙 gap1蓝牙初始化
  62. targetBluetoothFoundTime: null, // 目标蓝牙被找到,同时开始蓝牙的连接 gap2探索目标蓝牙
  63. targetBluetoothConnectedTime: null, // 蓝牙被连接时的时间 gap3连接蓝牙
  64. opForIosEndedTime: null, // 连接蓝牙后,完成iOS设备所必须的操作时的时间 gap4 iOS服务耗时
  65. requestSessionKeyTime: null, // 请求会话秘钥的开始时间 gap5 订阅特征值变化
  66. getSessionKeyPackageTime: null, // 获取到请求会话秘钥ack的时间
  67. requestBleOpenDoorTime: null, // 发送开门请求的时间
  68. getOpenDoorPackageTime: null, // 获取到请求开门返回包的时间
  69. requestOpenDoorLogTime: null, //发送获取开门记录的时间
  70. getOpenDoorLogPackageTime: null, // 获取到开门记录返回包的时间
  71. requestPowerLogTime: null, // 发送获取电量记录的时间
  72. getPowerLogPackageTime: null, // 获取到电量记录返回报文的时间
  73. result: 0,
  74. endTime: null, // 流程中断的时间点
  75. mobileOs: null,
  76. totalTime: null,
  77. isLocationEnabled: null,
  78. androidOptimizationStatus: this.openDoorInfo.androidOptimization,
  79. hostName: null, // 小程序宿主名
  80. hostVersion: null, // 小程序宿主版本
  81. openDoorParam: JSON.stringify(this.openDoorInfo.openDoorParam), // 开门时的自定义参数
  82. bluetoothAuthorized: false, // 蓝牙授权开启情况
  83. errMessage: '',
  84. // 剩下的为上传相关记录的操作
  85. };
  86. /**
  87. *
  88. * 抓取小程序宿主环境的相关信息
  89. * 由于小程序可能存在bug,当开启蓝牙和位置之后仍旧报错的,退出小程序再重新进入
  90. * https://developers.weixin.qq.com/community/develop/doc/000a6e376308d0c306a88a20f56800
  91. */
  92. try {
  93. const res = await uni.getSystemInfoSync();
  94. // console.log("uni.getSystemInfoSync({ includeBluetooth: true })", res);
  95. bluetoothLifeData.hostName = res.hostName; // 小程序的宿主名称,微信、支付宝等
  96. bluetoothLifeData.hostVersion = res.hostVersion; // 宿主环境的版本信息
  97. bluetoothLifeData.mobileOs = res.osName; // 收集操作系统
  98. // 后期考虑能否获取到开发环境基础库的版本,并添加进数据库
  99. } catch (e) {
  100. console.log("抓取小程序宿主环境的相关信息失败", e);
  101. }
  102. /**
  103. * 处理宿主系统为ios且宿主环境为alipay时的服务id和特征值id
  104. */
  105. if (bluetoothLifeData.mobileOs === 'ios' && bluetoothLifeData.hostName === 'alipay') {
  106. bluetoothLifeData.serviceId = 'FFF0'
  107. bluetoothLifeData.characteristicId = 'FFF1'
  108. }
  109. /**
  110. * 蓝牙的开关以及在未开启安卓优化时位置的开关
  111. */
  112. const systemBleAndLocationEnabled = uni.getSystemSetting()
  113. bluetoothLifeData.isBluetoothOpened = systemBleAndLocationEnabled.bluetoothEnabled
  114. // 处理手机蓝牙未打开
  115. if (!bluetoothLifeData.isBluetoothOpened) {
  116. uni.showModal({
  117. title: "蓝牙未打开",
  118. content: "紧急钥匙功能需要蓝牙支持,请打开手机蓝牙后重新开门。",
  119. });
  120. vueComponent.allowedOpenDoor = 0;
  121. return
  122. }
  123. // 不开启安卓优化,设备系统是安卓,校验位置是否开启;暂时不校验位置信息授权,测试后再确定是否校验
  124. bluetoothLifeData.isLocationEnabled = systemBleAndLocationEnabled.locationEnabled
  125. if (vueComponent.openDoorInfo.androidOptimization === false &&
  126. bluetoothLifeData.mobileOs === "android" &&
  127. !bluetoothLifeData.isLocationEnabled) {
  128. uni.showModal({
  129. title: "位置未开启",
  130. content: "紧急钥匙功能需要地理位置支持,请开启位置信息后重试。",
  131. });
  132. vueComponent.allowedOpenDoor = 0;
  133. return
  134. }
  135. /**
  136. * 判断是否获取到当前房间的蓝牙名和蓝牙秘钥,没获取到则报异常并return
  137. */
  138. if (!(this.openDoorInfo.bluetoothName && this.openDoorInfo.bluetoothKey)) {
  139. uni.showModal({
  140. title: "未获取到设备信息",
  141. content: "未获取到门锁设备信息,请稍后重试。",
  142. });
  143. vueComponent.allowedOpenDoor = 0;
  144. uni.switchTab({
  145. url: '/pages/home/home'
  146. })
  147. console.log("方法入口告警:蓝牙名或蓝牙秘钥为空");
  148. return;
  149. }
  150. /**
  151. * 一些超时时间的设置
  152. */
  153. const timeoutValue = {
  154. findDevice: vueComponent.openDoorInfo.openDoorParam.androidScanTimeout ?
  155. vueComponent.openDoorInfo.openDoorParam.androidScanTimeout : 10000, // 探索发现设备的超时时间
  156. connectBLE: 5000, // 连接蓝牙的超时时间
  157. requestSessionKey: 3000, // 请求秘钥的超时时间
  158. openDoor: 4000, // 请求开门的超时时间
  159. getOpenDoorLog: 8000,
  160. getPowerLog: 3000,
  161. };
  162. /**
  163. * 错误码定义
  164. */
  165. const ErrCode = {
  166. bleNotOpened: 10001, // 蓝牙未打开
  167. findDeviceTimeout: 10002, // 发现蓝牙设备超时
  168. bleConnectFailed: 10003, // 连接蓝牙失败
  169. requestSessionKeyTimeout: 10004, // 请求会话秘钥超时
  170. openDoorTimeout: 10005, // 蓝牙开门超时
  171. getDoorLogTimeout: 10006, // 获取开门记录超时
  172. getPowerLogTimeout: 10007, // 获取电量记录超时
  173. androidNotOpenLocation: 10008, // 安卓设备未打开位置信息,无法扫描蓝牙;仅在未开启蓝牙优化时出现
  174. };
  175. /**
  176. * 蓝牙开门加载时,显示的内容
  177. */
  178. const loading = {
  179. initBLE: '初始化手机蓝牙中...',
  180. discoveryBLE: '扫描门锁蓝牙中...',
  181. connectBLE: "连接门锁蓝牙中...",
  182. isOpeningDoor: '门锁开门中...'
  183. }
  184. // loading,开始进行蓝牙开门操作
  185. vueComponent.isOpeningDoor = true;
  186. vueComponent.allowedOpenDoor = 1;
  187. /**
  188. * 获取用户的当前设置(蓝牙的授权信息)。返回值中只会出现小程序已经向用户请求过的权限。
  189. */
  190. await uni.getSetting().then(res => {
  191. console.log("getSetting", res);
  192. if (bluetoothLifeData.hostName === 'WeChat') {
  193. bluetoothLifeData.bluetoothAuthorized =
  194. res[1].authSetting['scope.bluetooth'] ? res[1].authSetting['scope.bluetooth'] :
  195. false
  196. } else if (bluetoothLifeData.hostName === 'alipay') {
  197. bluetoothLifeData.bluetoothAuthorized =
  198. res[1].authSetting['bluetooth'] ? res[1].authSetting['bluetooth'] : false
  199. }
  200. console.log("bluetoothLifeData.bluetoothAuthorized", bluetoothLifeData
  201. .bluetoothAuthorized);
  202. }).catch(err => {
  203. console.log("getSetting catch err", err);
  204. })
  205. /**
  206. *
  207. * 开启蓝牙适配器,必须操作,并在Promise中处理授权相关的信息
  208. */
  209. bluetoothLifeData.initBluetoothAdapterTime = +new Date();
  210. await new Promise(async (resolve, reject) => {
  211. await uni.openBluetoothAdapter({
  212. mode: "central", //小程序作为中央设备
  213. }).then((res) => {
  214. console.log("openBluetoothAdapter success", res);
  215. if (bluetoothLifeData.hostName === 'WeChat') {
  216. // 初始化成功且蓝牙已授权
  217. if (res[1]) {
  218. if (!res[1]['errno']) {
  219. bluetoothLifeData.bluetoothAuthorized = true
  220. resolve('success')
  221. }
  222. } else if (res[0]) {
  223. // 初始化成功,但蓝牙未授权 微信错误码103为用户拒绝授权
  224. if (res[0].hasOwnProperty('errno') && res[0].errno === 103) {
  225. uni.showModal({
  226. title: "温馨提示",
  227. content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。",
  228. success: async (res) => {
  229. if (res.confirm) {
  230. await uni.openSetting().then(
  231. res => {
  232. console.log(
  233. "openSetting",
  234. res);
  235. bluetoothLifeData
  236. .bluetoothAuthorized =
  237. res[1].authSetting[
  238. 'scope.bluetooth'
  239. ]
  240. resolve()
  241. })
  242. }
  243. },
  244. });
  245. }
  246. }
  247. } else if (bluetoothLifeData.hostName === 'alipay') {
  248. // 初始化成功且蓝牙已授权
  249. if (res[1]) {
  250. if (res[1]['isSupportBLE']) {
  251. bluetoothLifeData.bluetoothAuthorized = true
  252. resolve('success')
  253. }
  254. } else if (res[0]) {
  255. if (res[0].hasOwnProperty('error') && res[0].error === 2001) {
  256. // 初始化成功,但蓝牙未授权,支付宝错误码为2001
  257. uni.showModal({
  258. title: "温馨提示",
  259. content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。",
  260. success: async (res) => {
  261. if (res.confirm) {
  262. await uni.openSetting().then(
  263. res => {
  264. console.log(
  265. "openSetting",
  266. res);
  267. bluetoothLifeData
  268. .bluetoothAuthorized =
  269. res[1].authSetting[
  270. 'bluetooth']
  271. resolve()
  272. })
  273. }
  274. },
  275. });
  276. }
  277. }
  278. }
  279. }).catch((err) => {
  280. console.log("openBluetoothAdapter catch err", err);
  281. reject(err)
  282. });
  283. }).catch(err => {
  284. console.log(err);
  285. })
  286. /**
  287. *
  288. * 判断蓝牙是否授权,未授权直接return
  289. */
  290. if (!bluetoothLifeData.bluetoothAuthorized) {
  291. vueComponent.isOpeningDoor = false;
  292. vueComponent.allowedOpenDoor = 0;
  293. console.log("手机蓝牙未授权,最外层函数。");
  294. bluetoothLifeData.endTime = new Date();
  295. bluetoothLifeData.totalTime =
  296. bluetoothLifeData.endTime -
  297. bluetoothLifeData.initBluetoothAdapterTime;
  298. bluetoothLifeData.result = ErrCode.bleNotOpened; // 蓝牙未打开其实为蓝牙未授权
  299. unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => {
  300. console.log(res);
  301. });
  302. uni.showModal({
  303. title: "温馨提示",
  304. content: "紧急门锁功能需要使用您的蓝牙,请授权后重新开门。",
  305. success: (res) => {
  306. if (res.confirm) {
  307. uni.openSetting().then(res => {
  308. console.log("openSetting", res);
  309. })
  310. }
  311. },
  312. });
  313. closeBluetoothAdapter();
  314. return;
  315. }
  316. /**
  317. *
  318. * 在Promise中调用onBluetoothDeviceFound,直到找到目的设备
  319. * 针对android设备且开启了安卓设备优化,直接跳过此流程
  320. */
  321. if (bluetoothLifeData.mobileOs === "ios" ||
  322. (bluetoothLifeData.mobileOs === "android" && vueComponent.openDoorInfo.androidOptimization ===
  323. false)) {
  324. await new Promise(async (resolve, reject) => {
  325. vueComponent.loadingContent = loading.discoveryBLE
  326. // console.log("enter onBluetoothDeviceFound Promise");
  327. // 设置发现设备的超时
  328. const timeout = setTimeout(() => {
  329. if (!bluetoothLifeData.targetBluetoothFoundTime) {
  330. console.log("onBluetoothDeviceFound Promise 发现设备超时");
  331. bluetoothLifeData.result = ErrCode.findDeviceTimeout;
  332. uni.offBluetoothDeviceFound();
  333. reject("发现设备超时");
  334. }
  335. }, timeoutValue.findDevice);
  336. uni.onBluetoothDeviceFound((res) => {
  337. let devices = res.devices.filter(
  338. (device) =>
  339. device.name === vueComponent.openDoorInfo.bluetoothName
  340. );
  341. // console.log("onBluetoothDeviceFound 发现设备数", devices.length);
  342. if (devices.length <= 0) return;
  343. let {
  344. deviceId,
  345. name
  346. } = devices[0];
  347. // console.log("deviceId", deviceId, "deviceName", name);
  348. bluetoothLifeData.deviceId = deviceId;
  349. bluetoothLifeData.targetBluetoothFoundTime = +new Date();
  350. stopBluetoothDevicesDiscovery();
  351. clearTimeout(timeout);
  352. resolve({
  353. bleFound: true
  354. });
  355. });
  356. // 调用扫描蓝牙API,扫描周边蓝牙
  357. bluetoothLifeData.startBluetoothDiscoveryTime = +new Date();
  358. let discovery = await uni.startBluetoothDevicesDiscovery({
  359. powerLevel: "high"
  360. });
  361. console.log("startBluetoothDevicesDiscovery", discovery);
  362. }).catch((err) => {
  363. console.log("onBluetoothDeviceFound Promise catch err", err);
  364. });
  365. /**
  366. *
  367. * 搜索蓝牙设备超时或安卓设备未打开位置信息时的处理逻辑
  368. */
  369. if (!bluetoothLifeData.targetBluetoothFoundTime) {
  370. console.log("发现设备超时,最外层函数");
  371. vueComponent.isOpeningDoor = false;
  372. vueComponent.allowedOpenDoor = 0;
  373. vueComponent.loadingContent = loading.initBLE
  374. uni.showModal({
  375. title: "未找到门锁",
  376. content: "未找到门锁设备,请确认您的位置是否正确。",
  377. });
  378. console.log("未找到门锁");
  379. await closeBluetoothAdapter();
  380. bluetoothLifeData.endTime = new Date();
  381. bluetoothLifeData.totalTime =
  382. bluetoothLifeData.endTime -
  383. bluetoothLifeData.initBluetoothAdapterTime;
  384. unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => {
  385. console.log(res);
  386. });
  387. return;
  388. }
  389. } else {
  390. //该分支不进行蓝牙扫描
  391. bluetoothLifeData.startBluetoothDiscoveryTime = +new Date();
  392. bluetoothLifeData.targetBluetoothFoundTime = +new Date();
  393. // 根据转换规则,置android设备的deviceId
  394. console.log(vueComponent.openDoorInfo.bluetoothDeviceId);
  395. bluetoothLifeData.deviceId = parseBELDeviceId(
  396. vueComponent.openDoorInfo.bluetoothDeviceId
  397. );
  398. console.log(
  399. "android设备连接蓝牙时的deviceId",
  400. bluetoothLifeData.deviceId
  401. );
  402. }
  403. /**
  404. *
  405. * 找到目标设备,开始进行蓝牙连接
  406. * 在Promise中连接蓝牙设备,超时就 关闭蓝牙适配器 ,第一次超时return是否会影响第二次的成功连接
  407. */
  408. await new Promise((resolve, reject) => {
  409. vueComponent.loadingContent = loading.connectBLE
  410. const timeout = setTimeout(() => {
  411. if (!bluetoothLifeData.targetBluetoothConnectedTime) {
  412. bluetoothLifeData.result = ErrCode.bleConnectFailed;
  413. console.log("蓝牙连接超时");
  414. reject("蓝牙连接超时");
  415. }
  416. }, timeoutValue.connectBLE)
  417. uni.createBLEConnection({
  418. deviceId: bluetoothLifeData.deviceId,
  419. })
  420. .then((res) => {
  421. clearTimeout(timeout)
  422. bluetoothLifeData.bluetoothConnected = true;
  423. bluetoothLifeData.targetBluetoothConnectedTime = +new Date();
  424. console.log("createBLEConnection res", res);
  425. resolve("蓝牙连接成功")
  426. })
  427. .catch((err) => {
  428. bluetoothLifeData.result = ErrCode.bleConnectFailed;
  429. console.log("createBLEConnection catch err", err);
  430. });
  431. }).catch(err => {
  432. console.log("createBLEConnection Promise catch err", err);
  433. })
  434. /**
  435. *
  436. * 判断蓝牙是否连接成功,若失败则直接return
  437. */
  438. if (!bluetoothLifeData.bluetoothConnected) {
  439. vueComponent.isOpeningDoor = false;
  440. vueComponent.allowedOpenDoor = 0;
  441. vueComponent.loadingContent = loading.initBLE
  442. uni.showModal({
  443. title: "蓝牙连接失败",
  444. content: "蓝牙连接失败,请确认位置正确后重试。",
  445. });
  446. await closeBluetoothAdapter();
  447. console.log("蓝牙连接失败,最外层函数");
  448. bluetoothLifeData.endTime = new Date();
  449. bluetoothLifeData.totalTime =
  450. bluetoothLifeData.endTime -
  451. bluetoothLifeData.initBluetoothAdapterTime;
  452. console.log("蓝牙连接失败");
  453. unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => {
  454. console.log(res);
  455. });
  456. return;
  457. }
  458. /**
  459. *
  460. * 蓝牙连接成功后,对iOS设备而言,需对服务及特征值进行扫描
  461. */
  462. if (bluetoothLifeData.mobileOs === "ios") {
  463. await uni
  464. .getBLEDeviceServices({
  465. deviceId: bluetoothLifeData.deviceId,
  466. })
  467. .then((res) => {
  468. console.log("getBLEDeviceServices", res);
  469. })
  470. .catch((err) => {
  471. console.log("getBLEDeviceServices err", err);
  472. });
  473. await uni
  474. .getBLEDeviceCharacteristics({
  475. deviceId: bluetoothLifeData.deviceId,
  476. serviceId: bluetoothLifeData.serviceId,
  477. })
  478. .then((res) => {
  479. console.log("getBLEDeviceCharacteristics", res);
  480. })
  481. .catch((err) => {
  482. console.log("getBLEDeviceCharacteristics err", err);
  483. });
  484. bluetoothLifeData.opForIosEndedTime = +new Date();
  485. } else {
  486. bluetoothLifeData.opForIosEndedTime = +new Date();
  487. }
  488. /**
  489. *
  490. * 对主服务特征值进行订阅;
  491. */
  492. const subscribe = await notifyBLECharacteristicValueChange();
  493. console.log("subscribe", subscribe);
  494. /**
  495. *
  496. * 向蓝牙发请求,请求会话秘钥(写特征值),解出会话秘钥
  497. */
  498. await new Promise(async (resolve, reject) => {
  499. vueComponent.loadingContent = loading.isOpeningDoor
  500. // console.log("onBLECharacteristicValueChange监听函数,请求会话秘钥");
  501. // 设置请求会话秘钥的超时
  502. const timeout = setTimeout(() => {
  503. if (!bluetoothLifeData.getSessionKeyPackageTime) {
  504. bluetoothLifeData.result = ErrCode.requestSessionKeyTimeout;
  505. console.log("请求会话秘钥超时");
  506. reject("请求会话秘钥超时");
  507. }
  508. }, timeoutValue.requestSessionKey);
  509. uni.offBLECharacteristicValueChange();
  510. const requestSessionKeyRes = onBLECharacteristicValueChange();
  511. bluetoothLifeData.requestSessionKeyTime = +new Date();
  512. const requestSecret = await writeBLECharacteristicValue(
  513. genReqSecretPkg()
  514. );
  515. console.log("requestSecret", requestSecret);
  516. requestSessionKeyRes.then((res) => {
  517. // console.log(res);
  518. if (res == 0x01) {
  519. clearTimeout(timeout);
  520. resolve({
  521. requestSessionKey: true
  522. });
  523. } else {
  524. clearTimeout(timeout);
  525. reject({
  526. message: "应答包获取错误,应为请求会话秘钥的应答包。",
  527. });
  528. }
  529. });
  530. }).catch((err) => {
  531. console.log(
  532. "onBLECharacteristicValueChange监听函数,请求会话秘钥。catch err",
  533. err
  534. );
  535. });
  536. /**
  537. *
  538. * 获取会话秘钥超时的处理逻辑
  539. */
  540. if (!bluetoothLifeData.getSessionKeyPackageTime) {
  541. vueComponent.isOpeningDoor = false;
  542. vueComponent.allowedOpenDoor = 0;
  543. vueComponent.loadingContent = loading.initBLE
  544. uni.showModal({
  545. title: "蓝牙开门失败",
  546. content: "蓝牙开门失败,请稍后重试!", // 带!号的蓝牙开门失败实际为请求会话秘钥超时
  547. });
  548. console.log("获取会话秘钥失败");
  549. bluetoothLifeData.endTime = new Date();
  550. bluetoothLifeData.totalTime =
  551. bluetoothLifeData.endTime -
  552. bluetoothLifeData.initBluetoothAdapterTime;
  553. console.log("会话秘钥超时");
  554. unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => {
  555. console.log(res);
  556. });
  557. return;
  558. }
  559. /**
  560. *
  561. * 解完请求会话秘钥后,发蓝牙开门报文,解蓝牙开门返回包
  562. */
  563. await new Promise(async (resolve, reject) => {
  564. // console.log("onBLECharacteristicValueChange监听函数,蓝牙开门");
  565. // 设置蓝牙开门的超时
  566. const timeout = setTimeout(() => {
  567. if (!bluetoothLifeData.getOpenDoorPackageTime) {
  568. bluetoothLifeData.result = ErrCode.openDoorTimeout;
  569. console.log("蓝牙开门超时");
  570. reject("蓝牙开门超时");
  571. }
  572. }, timeoutValue.openDoor);
  573. uni.offBLECharacteristicValueChange();
  574. const requestBLEOpenDoorRes = onBLECharacteristicValueChange();
  575. bluetoothLifeData.requestBleOpenDoorTime = +new Date();
  576. const openDoor = await writeBLECharacteristicValue(genOpDoorPkg());
  577. // console.log("openDoor", openDoor);
  578. requestBLEOpenDoorRes.then((res) => {
  579. // console.log(res);
  580. if (res == 0x11) {
  581. vueComponent.isOpeningDoor = false;
  582. vueComponent.allowedOpenDoor = 2;
  583. clearTimeout(timeout);
  584. uni.showModal({
  585. title: "开门成功",
  586. content: "蓝牙开门成功,祝您入住愉快。",
  587. });
  588. resolve({
  589. bluetoothOpenDoor: true
  590. });
  591. } else {
  592. clearTimeout(timeout);
  593. reject({
  594. message: "应答包获取错误,应为请求会话秘钥的应答包。",
  595. });
  596. }
  597. });
  598. }).catch((err) => {
  599. console.log(
  600. "onBLECharacteristicValueChange监听函数,蓝牙开门。catch err",
  601. err
  602. );
  603. });
  604. /**
  605. *
  606. * 判断蓝牙开门是否成功,如果失败,则return
  607. */
  608. if (!bluetoothLifeData.getOpenDoorPackageTime) {
  609. vueComponent.isOpeningDoor = false;
  610. vueComponent.allowedOpenDoor = 0;
  611. vueComponent.loadingContent = loading.initBLE
  612. uni.showModal({
  613. title: "蓝牙开门失败",
  614. content: "蓝牙开门失败,请稍后重试。",
  615. });
  616. console.log("蓝牙开门失败");
  617. bluetoothLifeData.endTime = new Date();
  618. bluetoothLifeData.totalTime =
  619. bluetoothLifeData.endTime -
  620. bluetoothLifeData.initBluetoothAdapterTime;
  621. unloadBLEOpenDoorLog(bluetoothLifeData).then((res) => {
  622. console.log(res);
  623. });
  624. return;
  625. }
  626. /**
  627. *
  628. * 断开蓝牙连接,关闭蓝牙适配器,并上传所有记录
  629. */
  630. await closeBLEConnection(bluetoothLifeData.deviceId);
  631. await closeBluetoothAdapter();
  632. bluetoothLifeData.endTime = new Date();
  633. bluetoothLifeData.totalTime =
  634. bluetoothLifeData.endTime - bluetoothLifeData.initBluetoothAdapterTime;
  635. unloadBLEOpenDoorLog(bluetoothLifeData)
  636. .then((res) => {
  637. console.log(res);
  638. vueComponent.allowedOpenDoor = 0;
  639. vueComponent.loadingContent = loading.initBLE
  640. })
  641. .catch((err) => {
  642. console.log("完成所有操作,上传开门记录", err);
  643. vueComponent.allowedOpenDoor = 0;
  644. vueComponent.loadingContent = loading.initBLE
  645. })
  646. .finally(() => {
  647. vueComponent.allowedOpenDoor = 0;
  648. vueComponent.loadingContent = loading.initBLE
  649. });
  650. console.log(bluetoothLifeData);
  651. /**
  652. *
  653. * 以下为函数声明
  654. */
  655. function onBLECharacteristicValueChange() {
  656. // console.log("onBLECharacteristicValueChange监听函数");
  657. return new Promise((resolve, reject) => {
  658. const BleStruct = {
  659. firstPacketReceived: false, //是否第一个报
  660. buffer: null, // 用于缓存数据流的 ArrayBuffer
  661. cmd: null, //返回数据包命令号
  662. length: 0, //数据包长度
  663. received: 0,
  664. errCode: 0,
  665. };
  666. uni.onBLECharacteristicValueChange(async function(res) {
  667. console.log("onBLECharacteristicValueChange", res); //打印特征值
  668. let receiveBuffer = bluetoothLifeData.hostName === 'alipay' ?
  669. hexStringToUint8Array(res.value) : new Uint8Array(res.value);
  670. // console.log("receiveBuffer", receiveBuffer);
  671. // 接收到的报文包含开始码等报文头数据
  672. if (
  673. receiveBuffer[0] == 0xa5 &&
  674. BleStruct.firstPacketReceived == false
  675. ) {
  676. // console.log("包含开始码等报文头数据的报文");
  677. BleStruct.firstPacketReceived = true;
  678. BleStruct.length = (receiveBuffer[5] << 8) + receiveBuffer[4] +
  679. 6; //算出数据长度
  680. BleStruct.received = 0;
  681. BleStruct.cmd = receiveBuffer[1]; //命令号赋值
  682. BleStruct.errCode = receiveBuffer[6];
  683. BleStruct.buffer = new Uint8Array(BleStruct.length);
  684. // 错误码!= 0,丢弃报文
  685. if (BleStruct.errCode != 0) {
  686. BleStruct.firstPacketReceived = false;
  687. bluetoothLifeData.result = BleStruct.errCode;
  688. reject({
  689. message: "errCode != 0",
  690. errCode: BleStruct.errCode
  691. });
  692. return;
  693. }
  694. }
  695. // 接收到的报文的长度不对,丢弃报文
  696. if (receiveBuffer.length > BleStruct.length - BleStruct.received) {
  697. BleStruct.firstPacketReceived = false;
  698. console.log(
  699. "onBLECharacteristicValueChange: length err",
  700. BleStruct,
  701. receiveBuffer.length
  702. );
  703. reject({
  704. message: "onBLECharacteristicValueChange: length err",
  705. bleStruct: BleStruct,
  706. length: receiveBuffer.length,
  707. });
  708. return;
  709. }
  710. BleStruct.buffer.set(receiveBuffer, BleStruct
  711. .received); // 将接收到的报文放入buffer容器
  712. BleStruct.received += receiveBuffer.length;
  713. // console.log("BleStruct", BleStruct);
  714. // 已经获取到全部报文
  715. if (BleStruct.received >= BleStruct.length) {
  716. BleStruct.firstPacketReceived = false;
  717. const returnCmd = analysisData(
  718. BleStruct.cmd,
  719. BleStruct.buffer.slice(7)
  720. );
  721. resolve(returnCmd);
  722. }
  723. });
  724. });
  725. }
  726. function analysisData(cmd, data) {
  727. // console.log("进入分析数据函数");
  728. const buffer2Str = (buffer) =>
  729. Array.from(buffer)
  730. .map((byte) => byte.toString(16).padStart(2, "0"))
  731. .join("");
  732. const buffer2Num = (buffer) => parseInt(buffer2Str(buffer), 16);
  733. //请求会话密钥的返回包,对其进行处理
  734. if (cmd == 0x01) {
  735. bluetoothLifeData.getSessionKeyPackageTime = +new Date();
  736. var originalKey = vueComponent.openDoorInfo.bluetoothKey;
  737. // var originalKey = "ffffffffffffffffffffffffffffffff";
  738. var encryptedData = data;
  739. var sessionKey = decryptAes(originalKey, encryptedData);
  740. const hexString = Array.from(sessionKey)
  741. .map((byte) => byte.toString(16).padStart(2, "0"))
  742. .join("");
  743. uni.setStorageSync("sessionKey", hexString);
  744. return 0x01;
  745. }
  746. //获取电量返回包,对其进行处理
  747. if (cmd == 0x04) {
  748. bluetoothLifeData.getPowerLogPackageTime = +new Date();
  749. console.log("power:", data);
  750. let powerString = Array.from(data)
  751. .map((byte) => byte.toString(16).padStart(2, "0"))
  752. .join("");
  753. powerString = parseInt(powerString, 16);
  754. uni.setStorageSync("power", powerString);
  755. return 0x04;
  756. }
  757. //蓝牙开门返回包,对其进行处理
  758. if (cmd == 0x11) {
  759. bluetoothLifeData.getOpenDoorPackageTime = +new Date();
  760. // console.log("蓝牙开门成功");
  761. // console.log(data[0]);
  762. // console.log(data[1]);
  763. return 0x11;
  764. }
  765. //开门记录返回包,对其进行处理
  766. if (cmd == 0x12) {
  767. bluetoothLifeData.getOpenDoorLogPackageTime = +new Date();
  768. var dataBuffer = data;
  769. var doorBufferLogs = [];
  770. while (dataBuffer.length > 0) {
  771. length = Math.min(17, dataBuffer.length);
  772. doorBufferLogs.push(dataBuffer.slice(0, length));
  773. // console.log(
  774. // "cardNum",
  775. // dataBuffer.slice(1, 5),
  776. // "roomNum",
  777. // dataBuffer.slice(5, 9)
  778. // );
  779. dataBuffer = dataBuffer.slice(length);
  780. }
  781. let doorLogs = doorBufferLogs.map((buffer) => ({
  782. type: buffer[0],
  783. cardNum: buffer2Num(buffer.slice(1, 5)),
  784. roomNum: buffer2Str(buffer.slice(5, 9)),
  785. time: new Date(buffer2Num(buffer.slice(9)) * 1000),
  786. }));
  787. // console.log("analysisData:", doorBufferLogs, doorLogs);
  788. uni.setStorageSync("doorLogs", doorLogs);
  789. return 0x12;
  790. }
  791. }
  792. // 启用蓝牙低功耗设备特征值变化时的 notify 功能,
  793. function notifyBLECharacteristicValueChange() {
  794. return new Promise((resolve, reject) => {
  795. // 连上蓝牙后再创建订阅
  796. console.log("创建订阅");
  797. uni.notifyBLECharacteristicValueChange({
  798. deviceId: bluetoothLifeData.deviceId, // 蓝牙设备 id
  799. serviceId: bluetoothLifeData.serviceId, // 蓝牙特征对应服务的 UUID
  800. characteristicId: bluetoothLifeData.characteristicId, // 蓝牙特征的 UUID
  801. state: true, // 是否启用 notify
  802. success: function(res) {
  803. console.log("success notifyBLECharacteristicValueChange:", res);
  804. resolve(res);
  805. },
  806. fail: function(res) {
  807. console.log("fail notifyBLECharacteristicValueChange:", res);
  808. resolve(res);
  809. },
  810. });
  811. });
  812. }
  813. function onBLEConnectionStateChange() {
  814. uni.onBLEConnectionStateChange((res) => {
  815. // console.log("onBLEConnectionStateChange", res);
  816. });
  817. }
  818. function stopBluetoothDevicesDiscovery() {
  819. uni.stopBluetoothDevicesDiscovery({
  820. success: (res) => {
  821. // console.log("关闭蓝牙停止搜索成功");
  822. },
  823. fail: (res) => {
  824. console.log("关闭蓝牙停止搜索失败");
  825. },
  826. });
  827. }
  828. // 向蓝牙低功耗设备特征值中写入二进制数据
  829. async function writeBLECharacteristicValue(packetData) {
  830. // 连上蓝牙后再写数据
  831. // console.log("进入写特征值函数,开始写入数据");
  832. // 向蓝牙设备发送一个0x00的16进制数据
  833. // let _Buffer = string2buffer(packetData)
  834. let count = 1;
  835. while (packetData.byteLength > 0) {
  836. let len = Math.min(16, packetData.byteLength);
  837. try {
  838. const res = await uni.writeBLECharacteristicValue({
  839. deviceId: bluetoothLifeData.deviceId,
  840. serviceId: bluetoothLifeData.serviceId,
  841. characteristicId: bluetoothLifeData.characteristicId,
  842. value: packetData.slice(0, len),
  843. });
  844. // console.log(`第${count}次写入`, res);
  845. count += 1;
  846. } catch (error) {
  847. console.log("writeBLECharacteristicValue err", error);
  848. }
  849. packetData = packetData.slice(len);
  850. await Delayed(500);
  851. }
  852. return;
  853. }
  854. // 关闭连接,也是异步操作
  855. function closeBLEConnection(deviceId = "") {
  856. return new Promise((resolve, reject) => {
  857. // console.log("关闭连接");
  858. uni.closeBLEConnection({
  859. deviceId: deviceId,
  860. success: (res) => {
  861. // console.log("断开蓝牙连接成功", res);
  862. resolve(res);
  863. },
  864. fail: (res) => {
  865. console.log("断开蓝牙连接失败", res);
  866. reject(res);
  867. },
  868. });
  869. });
  870. }
  871. // 关闭蓝牙模块
  872. function closeBluetoothAdapter() {
  873. return new Promise((resolve, reject) => {
  874. uni.closeBluetoothAdapter({
  875. success: function(res) {
  876. // console.log("success closeBluetoothAdapter:", res);
  877. resolve(res);
  878. },
  879. fail: function(res) {
  880. console.log("fail closeBluetoothAdapter:", res);
  881. resolve();
  882. },
  883. });
  884. });
  885. }
  886. },
  887. },
  888. async onLoad() {
  889. // console.log("currentHotelId", this.currentHotelId);
  890. // 先获取入住记录
  891. let info = await getCheckinInfo({
  892. userIdNumber: this.userInfo.idNumber,
  893. hotelId: this.currentHotel.hotelId,
  894. status: 1,
  895. pageNo: 1,
  896. pageSize: 1,
  897. })
  898. console.log("获取入住记录", info);
  899. if (!info.data.data.records.length) {
  900. console.log("获取入住记录失败", info.data.data);
  901. return
  902. }
  903. this.checkinInfo = info.data.data.records[0];
  904. this.checkinInfo.startTime = moment(this.checkinInfo.startTime).format("YYYY/MM/DD HH:mm:ss");
  905. this.checkinInfo.endTime = moment(this.checkinInfo.endTime).format("YYYY/MM/DD HH:mm:ss");
  906. // 然后获取门锁信息
  907. let checkinInfoAndLockInfo = await getCheckinInfoAndLockInfo({
  908. hotelId: this.currentHotel.hotelId,
  909. })
  910. console.log("获取入住记录和门锁记录", checkinInfoAndLockInfo);
  911. let {
  912. data
  913. } = checkinInfoAndLockInfo.data;
  914. // 没查询到入住
  915. if (!checkinInfoAndLockInfo.data.success) {
  916. if (checkinInfoAndLockInfo.data.msg === "账号的身份证号信息不全") {
  917. uni.showModal({
  918. title: "身份信息不全",
  919. content: "请至我的账户页面,输入您的身份证号信息。",
  920. success: (res) => {
  921. if (res.confirm) {
  922. uni.navigateTo({
  923. url: "/subpkg/myAccount/myAccount"
  924. })
  925. }
  926. },
  927. });
  928. } else {
  929. uni.showModal({
  930. title: "温馨提示",
  931. content: checkinInfoAndLockInfo.data.msg,
  932. });
  933. uni.switchTab({
  934. url: '/pages/home/home'
  935. })
  936. }
  937. return;
  938. }
  939. // 查询到入住,更新信息,同时后台直接查询门锁秘钥
  940. this.hotelId = data.hotelId;
  941. this.openDoorInfo.gatewayDeviceId = data.gatewayDeviceId;
  942. this.openDoorInfo.bluetoothDeviceId = data.deviceId;
  943. this.openDoorInfo.bluetoothName = data.name;
  944. this.openDoorInfo.androidOptimization =
  945. data.androidOptimization === false ? false : true;
  946. this.openDoorInfo.openDoorParam = JSON.parse(data.openDoorParam)
  947. // console.log("this.openDoorInfo.openDoorParam", this.openDoorInfo.openDoorParam);
  948. // 查询门锁蓝牙秘钥
  949. let bluetoothKey = await getBluetoothKey(data.hotelId)
  950. console.log("获取蓝牙秘钥", bluetoothKey);
  951. // 查询门锁蓝牙秘钥失败
  952. if (!bluetoothKey.data.success) {
  953. uni.showModal({
  954. title: "获取门锁信息失败",
  955. content: "获取门锁信息失败,请稍后重试。",
  956. });
  957. uni.switchTab({
  958. url: '/pages/home/home'
  959. })
  960. return;
  961. }
  962. // 查询门锁蓝牙秘钥成功
  963. this.openDoorInfo.bluetoothKey = bluetoothKey.data.data;
  964. this.allowedOpenDoor = 0;
  965. },
  966. };