HeartRate.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272
  1. <template>
  2. <view>
  3. <view class="flex flex-direction align-center" style="background-color: #F0F0E9;">
  4. <!-- 669 rpx -->
  5. <view class="flex flex-direction justify-center " style="height: 669rpx;">
  6. <view class="position-relative flex-direction align-center" style="color: #606162;">
  7. <view class="position-absolute-center" style="height: 96rpx;">
  8. <image style="height: 96rpx;width: 117rpx;" src="../../static/heart/heart@2x.png"></image>
  9. </view>
  10. <view class="flex align-end" style="width: 480rpx;padding-left: 117rpx;">
  11. <block v-if="heartRate === 0 || !bTimerPlay">
  12. <view class="text-center" style="font-size: 125rpx; width: 240rpx; ">--</view>
  13. </block>
  14. <block v-else>
  15. <view class="text-center" style="font-size: 125rpx; width: 240rpx; ">{{heartRate}}</view>
  16. <view style="font-size: 68rpx;">bpm</view>
  17. </block>
  18. </view>
  19. </view>
  20. <view style="text-align: center;margin-top: 60px;">
  21. <text style="font-size: 59rpx;color: #8D8D8D;">{{nums}}</text>
  22. </view>
  23. </view>
  24. <view class="cu-progress sm striped active">
  25. <view class="bg-green" :style="[{ width:loading}]"></view>
  26. </view>
  27. </view>
  28. <view class="bg-white padding-top-sm">
  29. <view class="heart-view flex justify-center align-center padding " style="margin-top: 0;">
  30. <view style="color: #606162;">播报状态,关闭后需要手动开启播报:</view>
  31. <switch :checked="treatmentStatu.checked" @change="switchChange"></switch>
  32. </view>
  33. <view class="flex align-center justify-around">
  34. <view class="margin-bottom-sm">最小值</view>
  35. <view class="margin-bottom-sm">最大值</view>
  36. </view>
  37. <view class="flex align-center justify-center">
  38. <button style="width: 300rpx; height: 98rpx; background-color: #F6F7F9;" class="cu-btn" @tap="showModal"
  39. data-target="showPickerModal" data-type="min">
  40. <span style="color:#F8A574; font-weight:bold;font-size: 20px; ">{{rateMin}} </span>
  41. </button>
  42. <view style="border: 2rpx solid #D8D8D8;width: 22rpx;height: 4rpx;margin: 50rpx 31rpx;"></view>
  43. <button style="width: 300rpx;height: 98rpx; background-color: #F6F7F9;" class="cu-btn" @tap="showModal"
  44. data-target="showPickerModal" data-type="max">
  45. <span style="color:#F8A574; font-weight:bold;font-size: 20px;">{{rateMax}}</span>
  46. </button>
  47. </view>
  48. <!-- background-color: #c6c6c6; -->
  49. <!-- <scroll-view scroll-y="true" style="height: 200rpx;">
  50. <view class="card-view padding-top padding-bottom" v-for="(item,index) in devicesList" :key="index"
  51. :id="'task_'+index" :class="item.bRatio?'hardware-border':''" :style="{'z-index': threeZIndex}">
  52. <view class="flex justify-between align-center">
  53. <view class="flex justify-start align-center">
  54. <image style="margin-left: 20rpx; width: 200rpx;height: 120rpx;" :src="item.icon"
  55. mode="aspectFit">
  56. </image>
  57. <view style="width: 350rpx;margin-left: 10rpx;">
  58. <view style="margin: 20rpx 0rpx; font-weight: bold; font-size: 18px; color: #565656;">
  59. {{item.cname}}
  60. </view>
  61. <view style="font-size: 12px; white-space:pre-wrap;" class="make-text-bPurple">
  62. {{item.describe}}{{item.bOldDevice?'(旧手柄)':''}}
  63. </view>
  64. </view>
  65. </view>
  66. <image style="margin-right: 60rpx; width: 60rpx;height: 60rpx;"
  67. :src="item.bRatio?'/static/devicesOther/radio-b.png':'/static/devicesOther/radio-g.png'"
  68. mode="aspectFit" @tap="_onRadio(item,$event)"></image>
  69. </view>
  70. </view>
  71. </scroll-view> -->
  72. <!-- :class="item.bRatio?'hardware-border':''" -->
  73. <view class="heart-view padding-top padding-bottom" v-for="(item,index) in devicesList" :key="index"
  74. :id="'task_'+index" :style="{'z-index': threeZIndex}">
  75. <view class="flex justify-between align-center">
  76. <view class="flex justify-start align-center">
  77. <image style="margin-left: 20rpx; width: 200rpx;height: 110rpx;" :src="item.icon"
  78. mode="aspectFit">
  79. </image>
  80. <view style="margin-left: 10rpx;">
  81. <view style="margin-bottom: 20rpx; font-weight: bold; font-size: 18px; color: #606162;">
  82. {{item.cname}}
  83. </view>
  84. <view v-if="item.bRatio" style="font-size: 12px; white-space:pre-wrap;" class="make-text-bPurple">
  85. 已连接
  86. </view>
  87. <view v-else style="color: #8D8D8D;">未连接</view>
  88. </view>
  89. </view>
  90. <view class="position-absolute-right-top">
  91. <image style="width: 239rpx;height: 173rpx;" :src="item.bRatio?'/static/heart/radio-r@2x.png':'/static/heart/radio-g@2x.png'"
  92. mode="aspectFit" @tap="_onRadio(item,$event)"></image>
  93. </view>
  94. <!-- <image style="margin-right: 60rpx; width: 60rpx;height: 60rpx;"
  95. :src="item.bRatio?'/static/devicesOther/radio-b.png':'/static/devicesOther/radio-g.png'"
  96. mode="aspectFit" @tap="_onRadio(item,$event)"></image> -->
  97. </view>
  98. </view>
  99. <view class="flex margin position-relative">
  100. <!-- 清零计时 -->
  101. <view class="position-relative" style="width: 282rpx ;height: 173rpx;" @click="closeTimer()">
  102. <image style="width: 282rpx ;height: 173rpx;" src="../../static/heart/b_clear@2x.png"></image>
  103. <view class="position-absolute-center flex text-18px text-bold">
  104. <view style="color: #BE7F59;width: 150rpx;">清零</view>
  105. </view>
  106. </view>
  107. <view class="position-absolute-right-top">
  108. <image style="width: 495rpx ;height: 173rpx;" src="../../static/heart/b_start@2x.png"></image>
  109. <view v-if="timer" class="position-absolute-center flex text-18px text-bold" @click="pauseTime()">
  110. <image style="width: 53rpx;height: 53rpx;" src="../../static/heart/pause@2x.png"></image>
  111. <view style="color: #4D857C;width: 150rpx;margin-left: 30rpx;">暂停计时</view>
  112. </view>
  113. <view v-else class="position-absolute-center flex text-18px text-bold" @click="startTimer()">
  114. <image style="width: 53rpx;height: 53rpx;" src="../../static/heart/play@2x.png"></image>
  115. <view style="color: #4D857C;width: 150rpx;margin-left: 30rpx;">开始计时</view>
  116. </view>
  117. </view>
  118. <!-- <button style="margin-top: 2px;" type="warn" @click="pauseTime()">
  119. 暂停计时
  120. </button> -->
  121. </view>
  122. </view>
  123. <view class="cu-modal bottom-modal" :class="modalName == 'showPickerModal' ? 'show' : ''"
  124. @touchmove.stop.prevent="moveHandle">
  125. <view class="cu-dialog" style="border-top-right-radius: 20rpx; border-top-left-radius: 20rpx;">
  126. <myPicker v-if="modalName == 'showPickerModal' ? true:false" :pickerObj="pickerObj"
  127. @confirmEvent="onConfirm" @cancelEvent="onCancel"></myPicker>
  128. </view>
  129. </view>
  130. </view>
  131. </template>
  132. <script>
  133. import config from '@/common/config.js';
  134. import reqUtil from '@/util/util-js/requstUtil.js';
  135. import ble from '@/util/util-js/BLE.js';
  136. import myPicker from '@/components/slambb-picker/slambb-picker.vue';
  137. import pickerData from '@/components/slambb-picker/picker.js';
  138. import {
  139. mapState,
  140. mapMutations
  141. } from 'vuex';
  142. // 获取 module
  143. var testModule = uni.requireNativePlugin("TestModule")
  144. export default {
  145. computed: mapState(['bOpenBluetooth', 'bOpenSuccess', 'bListenAdapterStateChange', 'bListenDeviceFound',
  146. 'BLEConnectDevice', 'BLEGetServices', 'cIndex', 'bConnection', 'bVerifiedConnection', 'BLEInfoList',
  147. 'BLEDeviceShowList', 'finallyUseDevice', 'systemInfo', 'guideUnlockState', 'currentModeIndex',
  148. "bListenerAccArray", 'LocationGameUrl', 'bGamePlaying'
  149. ]),
  150. components: {
  151. myPicker
  152. },
  153. data() {
  154. return {
  155. // 设备列表
  156. devicesList: [],
  157. currentItem: null,
  158. //设置一个旧的连接item
  159. oldItem: null,
  160. searchObj: null,
  161. //设置搜索超时时间
  162. searchTimeOut: null,
  163. option: null,
  164. saveObj: null,
  165. //搜索提示定时器
  166. searchMac: null,
  167. //
  168. getServicesTimeout: null,
  169. writeMacTimeout: null,
  170. //限制关闭连接
  171. bLimitClose: false,
  172. //是否需要检测发起的连接蓝牙是否匹配上
  173. bTestBondConnect: true,
  174. //是否显示
  175. bShow: true,
  176. //切换连接
  177. bSwitch: false,
  178. //提示
  179. circular: false,
  180. currentHeight: 0,
  181. threeTipHeight: 0,
  182. threeZIndex: 0,
  183. currentIndex: 5,
  184. /**
  185. * 模块
  186. */
  187. modalName: null,
  188. pikerType: '',
  189. pickerObj: {
  190. pickerType: 'dateItem',
  191. pickerTitle: '记时间'
  192. },
  193. //测试
  194. text: '',
  195. extraLine: [],
  196. bShowGame: false,
  197. bPlayText: false,
  198. playTime: 0,
  199. /**
  200. * 计时器部分
  201. */
  202. timer: null,
  203. nums: '0时0分0秒',
  204. bTimerPlay: false,
  205. bTimerPause: false,
  206. hour: 0,
  207. minute: 0,
  208. second: 0,
  209. millisecond: 0,
  210. loading: '0%',
  211. loadingCount: 0,
  212. bLoading: false,
  213. /**
  214. * 录音
  215. */
  216. speechEngine: 'baidu',
  217. searchText: '',
  218. /**
  219. * 当前心率值
  220. */
  221. // rateText: "0",
  222. heartRate: 0,
  223. bReach: false,
  224. rateMax: 140,
  225. rateMin: 120,
  226. /**
  227. * switch
  228. */
  229. treatmentStatu: {
  230. checked: false
  231. },
  232. //检测录音权限
  233. getRecordAudioPermissionInterval: null
  234. }
  235. },
  236. onLoad(op) {
  237. this.add("初始化");
  238. uni.$on('retryConnectBLESuccess', this.onRetryConnectBLESuccess);
  239. uni.$on('callbackCloseBLE', this.hardCallbackCloseBLE);
  240. uni.$on('listenerBLE', this.onListenerBLE);
  241. this.BLEInfoList.forEach((item, index, selfarr) => {
  242. if (
  243. // item.usageMode == 'CL809' ||
  244. // item.usageMode == 'CL806' ||
  245. // item.usageMode == 'CL831' ||
  246. //CL838
  247. item.usageMode == 'CL') {
  248. let item = Object.assign({}, selfarr[index], {
  249. bOldDevice: false
  250. });
  251. this.devicesList.push(item);
  252. }
  253. })
  254. //subview
  255. /**
  256. * $on 之后要调用 $off,不然会重复绑定
  257. */
  258. this.onGetLocationGameUrl();
  259. uni.$on("log", this.add)
  260. },
  261. onUnload() {
  262. uni.$off('retryConnectBLESuccess', this.onRetryConnectBLESuccess);
  263. uni.$off('callbackCloseBLE', this.hardCallbackCloseBLE);
  264. uni.$off('listenerBLE', this.onListenerBLE);
  265. //清除定时器
  266. this.onClearTimeout();
  267. uni.$off("log", this.add);
  268. //停止语音
  269. this.stopSpeak();
  270. //释放唤醒资源
  271. this.releaseToSpeech();
  272. },
  273. onShow() {
  274. this.bShow = true;
  275. uni.$on('updateBLEDeviceData', this.callbackUpdateBLEData);
  276. },
  277. onReady() {
  278. // #ifdef APP-PLUS
  279. //初始化语音
  280. this.initTextToSpeech();
  281. this.$set(this.treatmentStatu, 'checked', true);
  282. //如果没有开启麦克风。可能导致唤醒失败
  283. //轮询检测麦克风权限
  284. if (this.getRecordAudioPermissionInterval) {
  285. clearInterval(this.getRecordAudioPermissionInterval);
  286. this.getRecordAudioPermissionInterval = null;
  287. }
  288. //开始检测
  289. this.getRecordAudioPermissionInterval = setInterval(() => {
  290. var ret = testModule.getRecordAudioPermission();
  291. // console.log(ret);
  292. if (ret.bPermission) {
  293. if (this.getRecordAudioPermissionInterval) {
  294. clearInterval(this.getRecordAudioPermissionInterval);
  295. this.getRecordAudioPermissionInterval = null;
  296. }
  297. this.initWakeup();
  298. }
  299. }, 1000)
  300. // this.initWakeup();
  301. // #endif
  302. },
  303. onHide() {
  304. //如果蓝牙弹出匹配框,会触发onHide。这时候处理蓝牙连接流程检测应等onShow 时候,再检测
  305. this.bShow = false;
  306. uni.$off('updateBLEDeviceData', this.callbackUpdateBLEData);
  307. },
  308. methods: {
  309. ...mapMutations(['initAdapter', 'onCreateBLESuccess', 'onGetBLEDeviceServices', 'onOnlyCloseBLEConnection',
  310. 'onGetRSSITransDistance',
  311. 'addBLEDevice', 'onWriteBLEConnectionValue', 'deleteBLEDevice', 'B_GetBondedDevices',
  312. 'B_OpenBLESetting', 'setGuideUnlockState', 'switchLevelList', 'onSetLocationGameUrl',
  313. 'onGetLocationGameUrl', 'onTestEmit', 'B_CloseBLEConnection', 'B_OnSetMTU'
  314. ]),
  315. onEmit() {
  316. this.onTestEmit();
  317. },
  318. /**
  319. * 显示游戏
  320. */
  321. onShowGame() {
  322. // uni.$emit("showGame");
  323. // this.bShowGame = true;
  324. uni.navigateTo({
  325. url: "../game/game"
  326. })
  327. },
  328. //设备回调事件
  329. callbackUpdateBLEData(data) {
  330. //如果在监听状态时候隐藏页面,返回
  331. // console.log(data)
  332. function getIntValue(formatType, offset) {
  333. console.log("getIntValue:" + formatType + " = " + offset);
  334. let array = new Uint8Array(data.ab);
  335. console.log(array);
  336. console.log(array[offset]);
  337. switch (formatType) {
  338. case 17:
  339. return ble.unsignedByteToInt(array[offset]);
  340. case 18:
  341. return ble.unsignedBytesToInt(array[offset], array[offset + 1]);
  342. }
  343. }
  344. // console.log(res, res.serviceId.toLocaleLowerCase());
  345. let offset = 0;
  346. let flags = getIntValue(17, offset);
  347. console.log("flags:", flags);
  348. let hearRateType = (flags & 1) == 0 ? 17 : 18;
  349. console.log("hearRateType:" + hearRateType);
  350. offset = offset + 1;
  351. this.heartRate = getIntValue(hearRateType, offset);
  352. console.log("heartRate:" + this.heartRate);
  353. this.text = JSON.stringify(data.hex) + "\n" + "心率:" + this.heartRate + "bpm" + "\n";
  354. // this.rateText = this.heartRate;
  355. if (this.heartRate != 0) {
  356. if (!this.bLoading) {
  357. this.bLoading = true;
  358. this.loadingCount = 0;
  359. }
  360. if (!this.bReach) {
  361. if (this.heartRate >= this.rateMin && this.heartRate <= this.rateMax) {
  362. this.playSpeak("达到运动心率" + this.heartRate);
  363. console.log("达到运动心率");
  364. //达到一次目标后播报
  365. this.bReach = true;
  366. }
  367. } else {
  368. if (this.heartRate < this.rateMin || this.heartRate > this.rateMax) {
  369. this.playSpeak("当前心率" + this.heartRate);
  370. }
  371. }
  372. } else if (this.bTimerPlay) {
  373. this.bLoading = false;
  374. //暂停心率
  375. this.pauseTime();
  376. }
  377. },
  378. onClearTimeout() {
  379. // 退出后,清除计时器
  380. if (this.searchMac) {
  381. clearTimeout(this.searchMac);
  382. this.searchMac = null;
  383. }
  384. //servicesTimeout
  385. if (this.getServicesTimeout) {
  386. clearTimeout(this.getServicesTimeout);
  387. this.getServicesTimeout = null;
  388. }
  389. //写入指令writeMacTimeout
  390. if (this.writeMacTimeout) {
  391. clearTimeout(this.writeMacTimeout);
  392. this.writeMacTimeout = null;
  393. }
  394. if (this.searchTimeOut) {
  395. clearTimeout(this.searchTimeOut);
  396. this.searchTimeOut = null;
  397. }
  398. },
  399. //监听回调
  400. onListenerBLE(res) {
  401. console.log('onListenerBLE:', res, this.saveObj);
  402. this.add(res.value);
  403. },
  404. /**
  405. * 连接设备成功
  406. */
  407. ConnectionSuccess() {
  408. for (let i = 0; i < this.devicesList.length; i++) {
  409. let eq = this.devicesList[i];
  410. //CL806_Bandage
  411. if ((eq.ename.indexOf("CL806") > -1 && this.BLEConnectDevice.id == 0) ||
  412. (eq.ename.indexOf("CL831") > -1 && this.BLEConnectDevice.id == 1) ||
  413. (eq.ename.indexOf("CL809") > -1 && this.BLEConnectDevice.id == 2) ||
  414. // CL838
  415. (eq.ename.indexOf("CL") > -1 && this.BLEConnectDevice.id == 3)
  416. ) {
  417. eq.bRatio = true;
  418. this.currentItem = eq;
  419. console.log('连接成功ConnectionSuccess:', eq);
  420. uni.hideToast();
  421. }
  422. }
  423. },
  424. onCheckBondDevice() {
  425. console.log("onCheckBondDevice", this.bTestBondConnect);
  426. if (this.bTestBondConnect && this.bShow) {
  427. /**
  428. * 假如手机没有匹配,断开连接
  429. */
  430. console.log(2, this.finallyUseDevice);
  431. this.B_GetBondedDevices({
  432. deviceId: this.finallyUseDevice.deviceId,
  433. success: (bondedDevice) => {
  434. uni.hideToast();
  435. if (bondedDevice == null) {
  436. // if (plus.os.name == 'Android')
  437. {
  438. //此问题 华为手机容易出现
  439. //android手机已配对的设备 不存在,但是app 又直接连接成功了。提示,并且断开app连接
  440. //1.关闭当前连接
  441. this.onOnlyCloseBLEConnection({
  442. getSuccess: () => {
  443. this.currentItem.bRatio = false;
  444. this.currentItem = null;
  445. this.saveObj = null;
  446. }
  447. });
  448. //2.跳转蓝牙设置
  449. uni.showModal({
  450. title: '蓝牙配对失败',
  451. content: '请跳转后点击配对BGBox_2020,成功后手动跳转回哔蹦重新连接。',
  452. success: (res) => {
  453. if (res.confirm) {
  454. this.B_OpenBLESetting();
  455. }
  456. }
  457. })
  458. }
  459. }
  460. }
  461. });
  462. this.bTestBondConnect = false;
  463. } else {
  464. uni.hideToast();
  465. }
  466. },
  467. onRetryConnectBLESuccess() {
  468. this.ConnectionSuccess();
  469. },
  470. hardCallbackCloseBLE() {
  471. if (this.BLEConnectDevice == null && this.currentItem && !this.bSwitch) {
  472. this.currentItem.bRatio = false;
  473. this.currentItem = null;
  474. this.saveObj = null;
  475. }
  476. //如果限制不走重连
  477. if (this.bLimitClose) return;
  478. console.log(this.currentItem, this.oldItem);
  479. if (this.oldItem && this.currentItem && this.currentItem.id == this.oldItem.id) {
  480. // this.$store.state.bConnection = false;
  481. if (this.currentItem.bRatio) {
  482. uni.showToast({
  483. title: '设备断开连接!',
  484. icon: 'none',
  485. duration: 2000,
  486. mask: true
  487. })
  488. this.currentItem.bRatio = false;
  489. this.oldItem = null;
  490. this.currentItem = null;
  491. //断开连接
  492. this.$store.state.bVerifiedConnection = false;
  493. }
  494. } else if (this.bConnection && this.BLEConnectDevice) {
  495. //假如匹配过程中断开连接
  496. this.$store.state.BLEConnectDevice = null;
  497. this.$store.state.bConnection = false;
  498. // uni.hideToast();
  499. // console.log("//假如匹配过程中断开连接");
  500. uni.showToast({
  501. title: '连接失败,尝试重新连接。',
  502. icon: 'none',
  503. mask: true,
  504. duration: 2000
  505. })
  506. if (this.getServicesTimeout) {
  507. clearTimeout(this.getServicesTimeout);
  508. this.getServicesTimeout = null;
  509. }
  510. }
  511. },
  512. /**
  513. * 开始查找设备
  514. * */
  515. startBluetoothDeviceDiscovery() {
  516. //在页面显示的时候判断是都已经初始化完成蓝牙适配器若成功,则开始查找设备
  517. let _self = this;
  518. if (_self.bOpenBluetooth) {
  519. //1.第一步还是先进行设备搜索
  520. _self.onCanStart();
  521. } else {
  522. _self.initAdapter(() => {
  523. _self.startBluetoothDeviceDiscovery();
  524. });
  525. }
  526. },
  527. /**
  528. * 通过检测手机连接的设备进行连接,null 的话进行搜索操作
  529. */
  530. onBondedDeviceConnect(data) {
  531. let _self = this;
  532. //获取手机本身已连接的硬件,
  533. //示例
  534. // {
  535. // "deviceId": "C5:5C:19:04:00:30",
  536. // "name": "BGBox_202012",
  537. // "RSSI": -74,
  538. // "localName": "BGBox_20201",
  539. // "advertisServiceUUIDs": ["00001812-0000-1000-8000-00805F9B34FB", "0000FFF0-0000-1000-8000-00805F9B34FB"]
  540. // }
  541. console.log("onBoded ***************:", _self.finallyUseDevice);
  542. _self.B_GetBondedDevices({
  543. deviceId: _self.finallyUseDevice == null ? null : _self.finallyUseDevice.deviceId,
  544. success: (bondedDevice) => {
  545. console.log("bondedDevice:", bondedDevice);
  546. //获取已和蓝牙连接的设备
  547. if (bondedDevice != null) {
  548. //如果用户匹配了对应的设备,直接用对应的设备来连接
  549. let setDevice = {
  550. "deviceId": bondedDevice.address,
  551. "name": bondedDevice.name,
  552. "RSSI": -74,
  553. "localName": "",
  554. "advertisServiceUUIDs": ["00001812-0000-1000-8000-00805F9B34FB",
  555. "0000FFF0-0000-1000-8000-00805F9B34FB"
  556. ]
  557. }
  558. let obj = Object.assign({}, setDevice, _self.currentItem);
  559. //finallyUserDevice 就是最后一次使用搜索到的设备信息
  560. _self.saveObj = Object.assign({}, setDevice, {
  561. id: _self.currentItem.id
  562. });
  563. uni.showToast({
  564. title: '设备连接中...',
  565. icon: 'loading',
  566. duration: 10000,
  567. mask: true
  568. })
  569. console.log("GetBondedDevices:::===", setDevice, _self.currentItem, obj);
  570. // 先直连,然后判断版本
  571. _self._onConnectDevice(obj);
  572. //getBond后发起的连接,不需要检测了
  573. _self.bTestBondConnect = false;
  574. } else {
  575. console.log("没有获取到绑定的设备");
  576. uni.showToast({
  577. title: '获取匹配设备失败',
  578. icon: 'none',
  579. mask: true
  580. })
  581. }
  582. }
  583. });
  584. },
  585. //开始搜索
  586. onCanStart() {
  587. let _self = this;
  588. uni.showToast({
  589. title: '设备连接中...',
  590. icon: 'loading',
  591. duration: 15000,
  592. mask: true
  593. })
  594. // 1.已经搜索到的,根据蓝牙回调的mac 地址判断合法性。
  595. uni.startBluetoothDevicesDiscovery({
  596. allowDuplicatesKey: true,
  597. success: res => {
  598. // console.log("startBluetoothDevicesDiscovery:", res);
  599. _self.bSwitch = false;
  600. _self.onBluetoothDeviceFound();
  601. },
  602. fail: res => {
  603. console.log("搜索失败!");
  604. _self.initAdapter(() => {
  605. _self.startBluetoothDeviceDiscovery();
  606. });
  607. }
  608. });
  609. // 2.没有搜索到的,一段时间后处理。比如手机已经连接了设备,但是app 里面搜索不到,也没记录使用过的。
  610. if (_self.searchTimeOut) {
  611. clearTimeout(_self.searchTimeOut);
  612. _self.searchTimeOut = null;
  613. }
  614. //搜索一段时间后,停止搜索
  615. if (plus.os.name == 'iOS') {
  616. _self.searchTimeOut = setTimeout(() => {
  617. _self.stopBluetoothDevicesDiscovery();
  618. //搜索失败后,再检查是否和手机配对的设备连接
  619. _self.onBondedDeviceConnect();
  620. }, 4000)
  621. } else {
  622. _self.searchTimeOut = setTimeout(() => {
  623. _self.stopBluetoothDevicesDiscovery();
  624. }, 8000)
  625. }
  626. },
  627. /**
  628. * 停止搜索蓝牙设备
  629. */
  630. stopBluetoothDevicesDiscovery() {
  631. uni.stopBluetoothDevicesDiscovery({
  632. success: e => {
  633. // console.log('停止搜索蓝牙设备:' + e.errMsg);
  634. },
  635. fail: e => {
  636. console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
  637. }
  638. });
  639. },
  640. /**
  641. * 发现外围设备
  642. */
  643. onBluetoothDeviceFound() {
  644. let _self = this;
  645. _self.searchObj = null;
  646. uni.onBluetoothDeviceFound(res => {
  647. /**
  648. * 获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备。
  649. */
  650. // console.log("onBluetoothDeviceFound:", res); Fitcent_
  651. res.devices.forEach(device => {
  652. if (
  653. // device.name.indexOf('CL809') > -1 ||
  654. // device.name.indexOf('CL806') > -1 ||
  655. // device.name.indexOf('CL831') > -1 ||
  656. //CL838
  657. device.name.indexOf('CL') > -1
  658. ) {
  659. //如果搜索的设备名 不是对应当前设备类型,过滤
  660. // if (_self.currentItem.deviceName.indexOf('CL806') == -1 || _self.currentItem.deviceName.indexOf('CL831') == -1) return;
  661. //寻找到对应设备时候,其余的返回
  662. if (_self.searchObj) return;
  663. _self.searchObj = device;
  664. if (_self.searchTimeOut) {
  665. clearTimeout(_self.searchTimeOut);
  666. _self.searchTimeOut = null;
  667. }
  668. //currentItem 是mode 页面选中的item
  669. let obj = Object.assign({}, device, _self.currentItem);
  670. _self.saveObj = Object.assign({}, {
  671. id: _self.currentItem.id
  672. }, device);
  673. console.log(device, "****", obj, _self.saveObj, _self.currentItem)
  674. // 先直连,然后判断版本
  675. _self._onConnectDevice(obj);
  676. _self.stopBluetoothDevicesDiscovery();
  677. }
  678. })
  679. })
  680. },
  681. onBack() {
  682. uni.navigateBack({
  683. delta: 1
  684. })
  685. },
  686. // 提示点击连接设备
  687. _onConnectDevice(item) {
  688. //servicesTimeout
  689. if (this.getServicesTimeout) {
  690. clearTimeout(this.getServicesTimeout);
  691. this.getServicesTimeout = null;
  692. }
  693. //写入指令writeMacTimeout
  694. if (this.writeMacTimeout) {
  695. clearTimeout(this.writeMacTimeout);
  696. this.writeMacTimeout = null;
  697. }
  698. //创建一个连接,需要对应close
  699. this.onCreateBLESuccess({
  700. item: item,
  701. getSuccess: () => {
  702. console.log("****创建一个连接*******");
  703. this.getServicesTimeout = setTimeout(() => {
  704. this.onGetBLEDeviceServices({
  705. item: item,
  706. success: (res) => {
  707. console.log("******getBLEDeviceServices************",
  708. res);
  709. //连接成功了,设置旧的item
  710. this.oldItem = this.currentItem;
  711. setTimeout(() => {
  712. this.saveObj = Object.assign({}, this
  713. .saveObj, {
  714. deviceMac: '',
  715. bOldDevice: false
  716. });
  717. this.addBLEDevice(this.saveObj);
  718. this.ConnectionSuccess();
  719. }, 2000)
  720. }
  721. });
  722. }, 2500);
  723. }
  724. })
  725. },
  726. _onRadio(item, event) {
  727. console.log(this.bConnection, this.BLEGetServices);
  728. if (this.bConnection && this.BLEGetServices && this.BLEGetServices.length == 0) {
  729. console.log("***直接获取服务*******");
  730. this.onGetBLEDeviceServices({
  731. item: item,
  732. success: (res) => {
  733. console.log("*****getBLEDeviceServices************");
  734. //连接成功了,设置旧的item
  735. this.oldItem = this.currentItem;
  736. setTimeout(() => {
  737. this.saveObj = Object.assign({}, this
  738. .saveObj, {
  739. deviceMac: '',
  740. bOldDevice: false
  741. });
  742. this.addBLEDevice(this.saveObj);
  743. this.ConnectionSuccess();
  744. }, 2000)
  745. }
  746. });
  747. return;
  748. }
  749. if (!item.bRatio) {
  750. //设置默认值
  751. this.bTestBondConnect = true;
  752. if (this.BLEConnectDevice) {
  753. this.onOnlyCloseBLEConnection({
  754. getSuccess: () => {
  755. if (this.currentItem)
  756. this.currentItem.bRatio = false;
  757. this.currentItem = null;
  758. this.currentItem = item;
  759. this.bSwitch = true;
  760. console.log("this.currentItem1 ==:", this.currentItem, this
  761. .bOpenBluetooth,
  762. this.BLEConnectDevice);
  763. this.startBluetoothDeviceDiscovery();
  764. }
  765. });
  766. return;
  767. }
  768. this.currentItem = null;
  769. this.currentItem = item;
  770. // console.log("this.currentItem ==2:", this.currentItem, this.bOpenBluetooth, this.BLEConnectDevice);
  771. this.startBluetoothDeviceDiscovery();
  772. }
  773. },
  774. onClise() {
  775. this.B_CloseBLEConnection({
  776. deviceId: "C5:5C:19:04:01:22"
  777. });
  778. },
  779. //跳转进入升级页面,
  780. onNavUpdateDevice() {
  781. //需要连接设备后,才能进入升级
  782. if (!this.currentItem || !this.currentItem.bRatio || !this.BLEConnectDevice) {
  783. uni.showToast({
  784. title: '请先连接硬件!',
  785. icon: 'none'
  786. })
  787. return;
  788. }
  789. this.bLimitClose = true;
  790. uni.navigateTo({
  791. // url: "../devices-update/devices-update?deviceType=" + this.option.deviceType
  792. url: "../devices-update/devices-update"
  793. })
  794. },
  795. add: function(data) {
  796. // console.log(data)
  797. if (this.extraLine.length > 500) {
  798. this.extraLine = [];
  799. this.text = this.extraLine.join('\n');
  800. }
  801. this.extraLine.push(data);
  802. this.text = this.extraLine.join('\n');
  803. },
  804. remove: function(e) {
  805. if (this.extraLine.length > 0) {
  806. // this.extraLine.pop();
  807. this.extraLine = [];
  808. this.text = this.extraLine.join('\n');
  809. }
  810. },
  811. writeBLEValue(str) {
  812. this.onWriteBLEConnectionValue({
  813. value: str
  814. })
  815. },
  816. onKeyInput: function(event) {
  817. this.onSetLocationGameUrl(event.target.value);
  818. },
  819. initTextToSpeech() {
  820. // 调用同步方法
  821. var ret = testModule.initTextToSpeech();
  822. console.log("initTextToSpeech:", ret);
  823. // uni.showToast({
  824. // title: "initTextToSpeech"
  825. // })
  826. },
  827. onGetRecordAudioPermission() {
  828. var ret = testModule.getRecordAudioPermission();
  829. // console.log(ret);
  830. // console.log(ret.bPermission)
  831. },
  832. initWakeup() {
  833. console.log("initWakeup!");
  834. // 调用同步方法
  835. testModule.initWakeup(null, (data) => {
  836. if (data.code === 200) {
  837. console.log("开启唤醒失败");
  838. uni.showToast({
  839. title: "初始化唤醒失败!",
  840. icon: 'none'
  841. })
  842. //停止语音
  843. this.stopSpeak();
  844. this.$set(this.treatmentStatu, 'checked', false);
  845. } else if (data.code === 0) {
  846. uni.showToast({
  847. title: "初始化唤醒成功!",
  848. icon: 'none'
  849. })
  850. this.$set(this.treatmentStatu, 'checked', true);
  851. //记录一下回调
  852. plus.globalEvent.addEventListener("onWakeup", this.onWakeupCallback);
  853. setTimeout(() => {
  854. this.recordSpeakDown();
  855. }, 1000)
  856. }
  857. });
  858. },
  859. onWakeupCallback(data) {
  860. // this.text = JSON.stringify(data);
  861. console.log("onWakeupCallback:" + JSON.stringify(data));
  862. if (data.word == "实时心率" || data.word == "播放") {
  863. uni.showToast({
  864. icon: 'none',
  865. title: data.word
  866. })
  867. this.playSpeak('当前心率' + this.heartRate);
  868. } else if (data.word == "停止播报") {
  869. this.$set(this.treatmentStatu, 'checked', false) // 手动修改switch的状态,视图会同步更新
  870. //停止语音
  871. this.stopSpeak();
  872. //停止唤醒
  873. this.releaseToSpeech();
  874. }
  875. },
  876. releaseToSpeech() {
  877. // 调用同步方法
  878. var ret = testModule.releaseToSpeech();
  879. // console.log(ret);
  880. uni.showToast({
  881. title: "releaseToSpeech",
  882. icon:'none'
  883. })
  884. },
  885. playSpeak(text) {
  886. let currentTime = new Date().getTime() - this.playTime;
  887. if (currentTime > 3000) {
  888. // 调用同步方法
  889. var ret = testModule.playSpeak(text);
  890. // console.log(ret);
  891. this.playTime = new Date().getTime();
  892. }
  893. },
  894. stopSpeak() {
  895. // 调用同步方法
  896. var ret = testModule.stopSpeak();
  897. console.log(ret);
  898. uni.showToast({
  899. title: "stopSpeak",
  900. icon:'none'
  901. })
  902. },
  903. startLuyin() {
  904. console.log('语音输入')
  905. let _this = this;
  906. let options = {};
  907. options.engine = _this.speechEngine
  908. options.punctuation = false; // 是否需要标点符号
  909. options.timeout = 10 * 1000; //超时时间
  910. plus.speech.startRecognize(options, function(s) {
  911. console.log(s) //识别的结果
  912. _this.searchText = s
  913. plus.speech.stopRecognize(); // 关闭
  914. });
  915. },
  916. recordSpeakDown() {
  917. // 调用同步方法
  918. var ret = testModule.recordSpeakDown();
  919. console.log("recordSpeakDown" + JSON.stringify(ret));
  920. // uni.showToast({
  921. // title: "recordSpeakDown"
  922. // })
  923. },
  924. recordSpeakUp() {
  925. // 调用同步方法
  926. var ret = testModule.recordSpeakUp();
  927. console.log("recordSpeakUp" + JSON.stringify(ret));
  928. // uni.showToast({
  929. // title: "recordSpeakUp"
  930. // })
  931. },
  932. /**
  933. * 计时器
  934. */
  935. startTimer() {
  936. //用心率判断是否佩戴上
  937. if (this.heartRate === 0) {
  938. uni.showToast({
  939. title: "清连接并佩戴后开始",
  940. icon: 'none'
  941. })
  942. return;
  943. }
  944. if (this.timer) {
  945. clearInterval(this.timer);
  946. this.timer = null;
  947. }
  948. this.timer = setInterval(() => {
  949. this.millisecond = this.millisecond + 50;
  950. if (this.millisecond >= 1000) {
  951. this.millisecond = 0;
  952. this.second = this.second + 1;
  953. //计算进度条数据
  954. if (this.bLoading && this.loadingCount < 60) {
  955. this.loadingCount++;
  956. this.loading = Math.round((this.loadingCount / 60) * 100) + "%";
  957. }
  958. }
  959. if (this.second >= 60) {
  960. this.second = 0;
  961. this.minute = this.minute + 1;
  962. }
  963. if (this.minute >= 60) {
  964. this.minute = 0;
  965. this.hour = this.hour + 1;
  966. }
  967. this.nums = this.hour + '时' + this.minute + '分' + this.second + '秒';
  968. }, 50);
  969. this.bTimerPlay = true;
  970. },
  971. pauseTime() {
  972. if (this.timer) {
  973. clearInterval(this.timer);
  974. this.timer = null;
  975. }
  976. },
  977. closeTimer() {
  978. this.hour = this.minute = this.second = 0; //初始化
  979. this.millisecond = 0; //毫秒
  980. if (this.timer) {
  981. clearInterval(this.timer);
  982. this.timer = null;
  983. }
  984. this.nums = 0 + '时' + 0 + '分' + 0 + '秒';
  985. this.bTimerPlay = false;
  986. },
  987. onRateChange(type, value) {
  988. switch (type) {
  989. case "min":
  990. if ((value < 0 && this.rateMin > 0) || (value > 0 && this.rateMin + 1 < this.rateMax)) {
  991. this.rateMin += value;
  992. } else {
  993. uni.showToast({
  994. icon: 'none',
  995. title: 'rateMin 不能超过最大值或者小于0',
  996. })
  997. }
  998. break;
  999. case "max":
  1000. if ((value < 0 && this.rateMin + 1 < this.rateMax) || value > 0) {
  1001. this.rateMax += value;
  1002. } else {
  1003. uni.showToast({
  1004. icon: 'none',
  1005. title: 'rateMax 不能小于最小值',
  1006. })
  1007. }
  1008. break;
  1009. }
  1010. },
  1011. onConfirm(args) {
  1012. this.hasUpdate = true;
  1013. console.log('onConfirm', args);
  1014. let tempValue = parseInt(args.value);
  1015. if (this.pikerType == 'min') {
  1016. if (tempValue < this.rateMax) {
  1017. this.rateMin = parseInt(args.value);
  1018. } else {
  1019. uni.showToast({
  1020. icon: 'none',
  1021. title: '不能大于或等于最大值',
  1022. })
  1023. }
  1024. } else if (this.pikerType == 'max') {
  1025. if (tempValue > this.rateMin) {
  1026. this.rateMax = parseInt(args.value);
  1027. } else {
  1028. uni.showToast({
  1029. icon: 'none',
  1030. title: '不能小于或等于最小值',
  1031. })
  1032. }
  1033. }
  1034. this.hideModal();
  1035. },
  1036. onCancel(value) {
  1037. console.log('onCancel', value);
  1038. this.modalName = null;
  1039. },
  1040. showModal(e) {
  1041. // console.log(e.currentTarget.type);
  1042. this.modalName = e.currentTarget.dataset.target;
  1043. this.pikerType = e.currentTarget.dataset.type;
  1044. switch (this.pikerType) {
  1045. case 'min':
  1046. this.$set(this.pickerObj, 'pickerLeftList', pickerData.getRateList().rateList);
  1047. this.$set(this.pickerObj, 'pickerRightList', []);
  1048. this.$set(this.pickerObj, 'pickerUnit', '');
  1049. this.$set(this.pickerObj, 'pickerType', 'singleItem');
  1050. this.$set(this.pickerObj, 'pickerTitle', '最小心率');
  1051. this.$set(this.pickerObj, 'defaultValue', this.rateMin);
  1052. this.$set(this.pickerObj, 'showInput', true);
  1053. break;
  1054. case 'max':
  1055. this.$set(this.pickerObj, 'pickerLeftList', pickerData.getRateList().rateList);
  1056. this.$set(this.pickerObj, 'pickerRightList', []);
  1057. this.$set(this.pickerObj, 'pickerUnit', '');
  1058. this.$set(this.pickerObj, 'pickerType', 'singleItem');
  1059. this.$set(this.pickerObj, 'pickerTitle', '最大心率');
  1060. this.$set(this.pickerObj, 'defaultValue', this.rateMax);
  1061. this.$set(this.pickerObj, 'showInput', true);
  1062. break;
  1063. }
  1064. },
  1065. hideModal(e) {
  1066. this.modalName = null;
  1067. this.pikerType = '';
  1068. },
  1069. moveHandle() {
  1070. return;
  1071. },
  1072. switchChange(e) {
  1073. let value = e.target.value
  1074. let that = this
  1075. this.$set(this.treatmentStatu, 'checked', value) // 将点击改变的状态赋给treatmentStatu.checked
  1076. if (value) {
  1077. uni.showModal({
  1078. title: '提示',
  1079. content: '是否开启语音播报?',
  1080. success: function(res) {
  1081. if (res.confirm) {
  1082. //todo 打开语音播报
  1083. console.log('用户点击确定')
  1084. that.initTextToSpeech();
  1085. that.initWakeup();
  1086. } else if (res.cancel) {
  1087. that.$set(that.treatmentStatu, 'checked', false) // 手动修改switch的状态,视图会同步更新
  1088. }
  1089. }
  1090. });
  1091. } else {
  1092. uni.showModal({
  1093. title: '提示',
  1094. content: '是否关闭语音播报?',
  1095. success: function(res) {
  1096. if (res.confirm) {
  1097. //todo 关闭语音播报
  1098. console.log('用户点击确定')
  1099. //停止语音
  1100. that.stopSpeak();
  1101. that.releaseToSpeech();
  1102. } else if (res.cancel) {
  1103. that.$set(that.treatmentStatu, 'checked', true) // 手动修改switch的状态,视图会同步更新
  1104. }
  1105. }
  1106. });
  1107. }
  1108. }
  1109. }
  1110. }
  1111. </script>
  1112. <style>
  1113. page{
  1114. background-color: #FFFFFF;
  1115. }
  1116. .hardware-border {
  1117. border: 1rpx solid #9898FF;
  1118. box-sizing: border-box;
  1119. }
  1120. .swiper {
  1121. flex: 1;
  1122. background-color: rgba(0, 0, 0, 0.32);
  1123. }
  1124. .swiper-item {
  1125. flex: 1;
  1126. }
  1127. .bluetooth-guide-number {
  1128. border-radius: 18px;
  1129. border-width: 1rpx;
  1130. width: 30px;
  1131. height: 30px;
  1132. text-align: center;
  1133. line-height: 30px;
  1134. color: #000000;
  1135. /* border-color: #FFFFFF; */
  1136. background-color: #FFFFFF;
  1137. margin-right: 13px;
  1138. }
  1139. .text-box {
  1140. margin: 20rpx;
  1141. display: flex;
  1142. width: 95%;
  1143. min-height: 300rpx;
  1144. max-height: 600rpx;
  1145. background-color: #c6c6c6;
  1146. justify-content: center;
  1147. align-items: center;
  1148. text-align: left;
  1149. font-size: 30upx;
  1150. color: #353535;
  1151. line-height: 1.8;
  1152. border: 1rpx solid #555555;
  1153. overflow-y: hidden;
  1154. }
  1155. .content {
  1156. align-content: center;
  1157. height: 300rpx;
  1158. background-color: #F4F5F6;
  1159. }
  1160. .uni-input {
  1161. border: 1rpx solid #000000;
  1162. }
  1163. .recordingStyle {
  1164. border-radius: 20px;
  1165. text-align: center;
  1166. color: #fff;
  1167. font-size: 15px;
  1168. background-color: #409eff;
  1169. margin-bottom: 15px;
  1170. }
  1171. </style>