bluetoothMixin.js 32 KB

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