bluetoothMixin.js 32 KB

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