Browse Source

v2.3.1 增加蓝牙接收

wzx 4 years ago
parent
commit
cf84ef3780

+ 11 - 4
package-lock.json

@@ -2859,6 +2859,14 @@
       "dev": true,
       "requires": {
         "pako": "~1.0.5"
+      },
+      "dependencies": {
+        "pako": {
+          "version": "1.0.11",
+          "resolved": "https://registry.nlark.com/pako/download/pako-1.0.11.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpako%2Fdownload%2Fpako-1.0.11.tgz",
+          "integrity": "sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=",
+          "dev": true
+        }
       }
     },
     "browserslist": {
@@ -8207,10 +8215,9 @@
       "dev": true
     },
     "pako": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npm.taobao.org/pako/download/pako-1.0.11.tgz?cache=0&sync_timestamp=1610208910632&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpako%2Fdownload%2Fpako-1.0.11.tgz",
-      "integrity": "sha1-bJWZ00DVTf05RjgCUqNXBaa5kr8=",
-      "dev": true
+      "version": "2.0.4",
+      "resolved": "https://registry.nlark.com/pako/download/pako-2.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpako%2Fdownload%2Fpako-2.0.4.tgz",
+      "integrity": "sha1-bOvEu7C2xzsNW41+hHbisvvqV20="
     },
     "parallel-transform": {
       "version": "1.2.0",

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "mockjs": "^1.1.0",
     "moment": "^2.27.0",
     "nprogress": "^0.2.0",
+    "pako": "^2.0.4",
     "postcss-loader": "^4.0.4",
     "postcss-pxtorem": "^5.1.1",
     "qrcodejs2": "^0.0.2",

+ 7 - 1
src/App.vue

@@ -7,7 +7,13 @@
 <script>
     export default {
         mounted() {
-        }
+        },
+        beforeDestroy() {
+            console.log('[beforeDestroy] APP即将退出');
+        },
+        destroyed() {            
+            console.log('[destroyed] APP退出');
+        },
     }
 </script>
 <style>

+ 275 - 0
src/ble.js

@@ -0,0 +1,275 @@
+import store from './store'
+import * as ws from './webSocket'
+import pako from 'pako'
+
+export var scanflag = false;
+// export var bleDiscovery = false;
+		
+export var BluetoothAdapter = null;
+export var btAdapter = null;
+export var Toast = null;
+export var activity = null;
+export var Intent = null;
+export var lastRcvTime = 0;     // 最近接收心率带数据时间
+
+var devSaveTimeArr = [];        // 各ble设备数据保存时间
+var dataRcvArr = [];            // 数据接收数组
+var upDataArr = [];             // 上传数据数组
+var upData = null;              // 上传数据
+// var upTime = 0;                 // 数据上传时间
+
+export function getBleDiscovery() {
+    // return bleDiscovery;
+    return store.state.bleDiscovery;
+}
+
+export function setBleDiscovery(bleDiscovery) {
+    store.commit('setBleDiscovery', bleDiscovery);
+}
+
+// 获取蓝牙状态
+/* function getBluetoothState() {
+    plus.bluetooth.getBluetoothAdapterState({
+        success:function(e){
+            console.log('获取蓝牙状态 success: '+JSON.stringify(e));
+        },
+        fail:function(e){
+            console.log('获取蓝牙状态 failed: '+JSON.stringify(e));
+        }
+    });
+} */
+
+/**
+ * 获取蓝牙的状态
+ * @return {boolean} 是否已开启
+ */
+export function getBluetoothStatus() {
+    if(btAdapter != null) {
+        var bleEnabled = btAdapter.isEnabled();
+        console.log("getBluetoothStatus: " + bleEnabled);
+        return bleEnabled;
+    }
+    else {
+        if (getBleDiscovery()) {
+            setBleDiscovery(false);
+        }
+        shortToast("本设备不支持蓝牙");
+        return false;
+    }
+}
+
+export function turnOnBluetooth() {
+    if(!btAdapter.isEnabled()) {
+        if(activity == null) {
+            shortToast("未获取到activity");
+            return;
+        } else {
+            var intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            var requestCode = 1;
+            activity.startActivityForResult(intent, requestCode);
+            return;
+        }
+    } else {
+        shortToast("蓝牙已经打开");
+    }
+}
+
+function shortToast(msg) {
+    Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show();
+}
+
+// 监听状态变化
+// 回调函数参数event对象包括以下属性: 
+// available - Boolean,蓝牙适配器是否可用; 
+// discovering - Boolean,蓝牙适配器是否处于搜索状态。
+export function listenerStateChange(){
+	plus.bluetooth.onBluetoothAdapterStateChange(function(e){
+		console.log('listenerStateChange: ' + JSON.stringify(e));
+        if (e.available && !e.discovering && store.state.classOn > 0) {
+            if (getBluetoothStatus()) {
+                console.log('onBluetoothAdapterStateChange -> startBluetoothDiscovery');
+                startBluetoothDiscovery();  // 开始搜索蓝牙
+                console.log('onBluetoothAdapterStateChange: 已上课,开始接收心率数据');
+                shortToast('接收心率数据中...');
+            }
+            else {
+                console.log('onBluetoothAdapterStateChange: 蓝牙未开启,无法接收心率数据(2)');
+            }
+        }
+        else if (!e.available && e.discovering) {
+            console.log('onBluetoothAdapterStateChange -> stopBluetoothDiscovery');
+            setBleDiscovery(false);
+            stopBluetoothDiscovery();  // 停止搜索蓝牙
+            console.log('onBluetoothAdapterStateChange: 蓝牙未开启,无法接收心率数据(1)');
+            shortToast('蓝牙未开启,无法接收心率数据');
+        }
+	});
+}
+
+// 开始搜索蓝牙
+export function startBluetoothDiscovery() {
+    console.log("startBluetoothDiscovery");
+    if (!ws.getWsState()) {
+        ws.connect(store.state.wsUrl, store.state.eqSn);
+    }
+
+    plus.bluetooth.openBluetoothAdapter({
+        success:function(e){
+            console.log('openBluetoothAdapter success: '+JSON.stringify(e));
+            plus.bluetooth.startBluetoothDevicesDiscovery({
+                //services: null, 			// 可选 要获取设备的uuid列表
+                allowDuplicatesKey: true,	// ( Boolean ) 可选 是否允许重复上报同一设备
+                interval: 0,		 		// 可选 上报设备的间隔  0表示找到新设备立即上报,其他数值根据传入的间隔上报。
+                success:function(e){
+                    console.log('startBluetoothDevicesDiscovery success: '+JSON.stringify(e));
+                    setBleDiscovery(true);
+                },
+                fail:function(e){
+                    console.log('startBluetoothDevicesDiscovery failed: '+JSON.stringify(e));
+                    setBleDiscovery(false);
+                },
+                // complete:function(e){    // 调用成功或失败都会触发此回调
+                //     console.log('startBluetoothDevicesDiscovery complete: '+JSON.stringify(e));
+                //     setBleDiscovery(false);
+                // }
+            });
+        },
+        fail:function(e){
+            console.log('openBluetoothAdapter failed: '+JSON.stringify(e));
+            setBleDiscovery(false);
+        }
+    });
+}
+
+// 停止搜索蓝牙
+export function stopBluetoothDiscovery(){
+    console.log("stopBluetoothDiscovery");
+    if (ws.getWsState()) {        
+        ws.disconnect();
+    }
+
+    plus.bluetooth.stopBluetoothDevicesDiscovery({
+        success:function(e){
+            console.log('stopBluetoothDevicesDiscovery success: '+JSON.stringify(e));
+            plus.bluetooth.closeBluetoothAdapter({
+                success:function(e){
+                    console.log('closeBluetoothAdapter success: '+JSON.stringify(e));
+                },
+                fail:function(e){
+                    console.log('closeBluetoothAdapter failed: '+JSON.stringify(e));
+                }
+            });
+        },
+        fail:function(e){
+            console.log('stopBluetoothDevicesDiscovery failed: '+JSON.stringify(e));
+        }
+    });
+}
+
+// 监听发现新设备
+export function listenerDeviceFound() {
+    plus.bluetooth.onBluetoothDeviceFound(function(e){
+        let devices = e.devices;
+        // console.log('device found: ' + devices.length);
+
+        let Power = 0;
+        let HeartRate = 0;
+        let Sn = 0;
+        let now = 0;
+        let waitTime = 0;
+        let data = '';
+
+        for(var i in devices) {
+            if (devices[i].name.substr(0,5) == 'CL831') {
+                // console.log("deviceName: " + devices[i].name);
+                // console.log(i+': '+JSON.stringify(devices[i]));
+                // console.log('advertisServiceUUIDs: ' + devices[i].advertisServiceUUIDs);
+                // console.log('localName: ' + devices[i].localName);
+                // console.log('advertisData: ' + buf2hex(devices[i].advertisData));
+                // console.log('advertisData length: ' + devices[i].advertisData.length);
+                // console.log('serviceData: ' + buf2hex(devices[i].serviceData));
+                
+                Power = parseInt(new Uint8Array(devices[i].advertisData, 3, 1));
+                HeartRate = parseInt(new Uint8Array(devices[i].advertisData, 5, 1));
+                Sn = parseInt(devices[i].localName.substr(-5));
+                // console.log('Sn: ' + Sn + '  localName: ' + devices[i].localName + '  HeartRate: ' + HeartRate + '  Power: ' + Power + '  advertisData: ' + buf2hex(devices[i].advertisData));
+
+                now = new Date().getTime(); //毫秒
+                lastRcvTime = now;  // 最近接收心率带数据时间
+
+                if (devSaveTimeArr[Sn] == undefined)
+                    devSaveTimeArr[Sn] = 0;
+                
+                waitTime = now - devSaveTimeArr[Sn];
+                // console.log("devSaveTimeArr["+ Sn + "]: " + devSaveTimeArr[Sn] + " waitTime: " + waitTime);
+                if (waitTime >= 1500) { // 同一个设备,上报时间间隔为1.5秒
+                    // console.log("[upData] waitTime: " + waitTime);
+                    devSaveTimeArr[Sn] = now;
+
+                    data = {
+                        "sn": Sn,
+                        "hr": HeartRate,
+                        "pw": Power
+                    };
+                    dataRcvArr.push(data);
+                }                
+            }
+            /* else {
+                console.log(i+': '+JSON.stringify(devices[i]));
+            } */
+        }
+    });
+}
+
+export function uploadBleData() {
+    if (dataRcvArr.length == 0)
+        return;
+    
+    upDataArr = deepClone(dataRcvArr);
+    dataRcvArr = [];   //清空缓冲区
+    upData = JSON.stringify(upDataArr);
+    // console.log("[uploadBleData] Data (len: " + upData.length+ "): " + upData);
+    upData = dealUpData(upData);
+    // console.log("[uploadBleData] Data GZip len: " + upData.length);
+    if (ws.getWsState()) {
+        ws.sendData(upData);
+    }
+    else {
+        ws.connect(store.state.wsUrl, store.state.eqSn);
+    }
+}
+
+function dealUpData(data) {
+    if (data == "") 
+        return;
+    // var binaryString = pako.gzip(data, { to: 'string' });   //压缩
+    var binaryString = pako.gzip(data);   //压缩
+    return binaryString;
+}
+
+export function test() {
+    // ws.connect(store.state.wsUrl, store.state.eqSn);
+    ws.connect('ws://localhost:8080/websocket', '7962ad082220d997');
+}
+
+// function turnOnBleAdv(obj) {
+//     if(!getBluetoothStatus()) {
+//         turnOnBluetooth();
+//         return;
+//     }
+    
+//     if (!scanflag) {
+//         obj.innerHTML = "关闭蓝牙广播"
+//         scanflag = true;
+//         startBluetoothDiscovery();
+//     }
+//     else {
+//         obj.innerHTML = "打开蓝牙广播"
+//         scanflag = false;
+//         stopBluetoothDiscovery();
+//     }    
+// }
+
+export function buf2hex(buffer) { // buffer is an ArrayBuffer
+    return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
+}

+ 63 - 26
src/components/Headside.vue

@@ -2,12 +2,17 @@
   <div class="headerContainer">
     <div class="lt">
       <img @click="showConsole()" src="../assets/imgs/headside/logo.svg" alt="">
+      <span>{{ this.$store.state.shopName }}</span>
+      <img v-show="this.$store.state.bleDiscovery" src="../static/img/bleDiscovery.svg">
+      <img v-show="this.$store.state.wsState" src="../static/img/bleUpload.svg">
     </div>
     <div class="rt">
       <p @click="showUuid()">版本 {{ version }}</p>
       <span>{{ nowTime }}</span>
-      <span @click="refreshPage()">{{ nowWeeks }}</span>
-      <span @click="showfullScreen()">{{ nowDay }}</span>
+      <span>{{ nowWeeks }}</span>
+      <span>{{ nowDay }}</span>
+      <!-- <span @click="refreshPage()">{{ nowWeeks }}</span> -->
+      <!-- <span @click="showfullScreen()">{{ nowDay }}</span> -->
     </div>
 
     <el-dialog
@@ -194,37 +199,69 @@ export default {
 
 .headerContainer {
   @include cube;
-}
-
-.lt {
-  float: left;
 
-  img {
-    width: 1.5rem;
+  .lt {
     float: left;
-    margin-top: 0.2rem;
-  }
-}
+    color: #fff;
 
-.rt {
-  float: right;
-  color: #fff;
+    img {
+      width: 1.5rem;
+      float: left;
+      margin-top: 0.2rem;
+      
+      &:nth-of-type(2) {
+        width: 0.16rem;
+        margin-top: 0.25rem;
+        margin-left: 0.2rem;
+      }
+      &:nth-of-type(3) {
+        width: 0.18rem;
+        margin-top: 0.27rem;
+        margin-left: 0.1rem;
+      }
+    }
 
-  span {
-    float: right;
-    font-size: 0.28rem;
-    line-height: 0.4rem;
-    margin-right: 0.2rem;
-    margin-top: 0.2rem;
+    span {
+      float: left;
+      font-size: 0.28rem;
+      line-height: 0.4rem;
+      margin-left: 0.2rem;
+      margin-top: 0.2rem;
+    }
   }
 
-  p {
+  .rt {
     float: right;
-    margin: 0;
-    margin-top: 0.2rem;
-    text-align: right;
-    font-size: 0.28rem;
-    line-height: 0.4rem;
+    color: #fff;
+
+    span {
+      float: right;
+      font-size: 0.28rem;
+      line-height: 0.4rem;
+      margin-right: 0.2rem;
+      margin-top: 0.2rem;
+    }
+
+    p {
+      float: right;
+      margin: 0;
+      margin-top: 0.2rem;
+      text-align: right;
+      font-size: 0.28rem;
+      line-height: 0.4rem;
+    }
+
+    img {
+      float: right;
+      width: 0.39rem;
+      margin: 0;
+      margin-top: 0.19rem;
+      margin-right: 0.03rem;
+
+      &:nth-of-type(1) {
+        margin-right: 0.2rem;
+      }
+    }
   }
 }
 

+ 5 - 4
src/main.js

@@ -6,7 +6,7 @@ import VConsole from 'vconsole/dist/vconsole.min.js' //import vconsole
 import animated from 'animate.css'
 import './plugins/axios'
 import 'default-passive-events'
-// import store from './store'
+import store from './store'
 
 import 'element-ui/lib/theme-chalk/index.css';
 // import axios from 'axios'
@@ -16,7 +16,7 @@ import 'element-ui/lib/theme-chalk/index.css';
 if (process.env.NODE_ENV === 'development') {
     console.log("process.env.NODE_ENV: development");
     // 显示控制台
-    // let vConsole = new VConsole();
+    let vConsole = new VConsole();
     // 演示版本数据
     require('./Mock/index.js');
 } else {
@@ -29,15 +29,16 @@ Vue.config.productionTip = false;
 Vue.use(ElementUI);
 Vue.use(animated);
 
+
 // 建立grpc连接服务
 //let client = new ApiClient("http://192.168.0.3:20010", null, null);
 
 // 挂载为全局方法
 //Vue.prototype.client = client;
 
-// store,
-new Vue({
+new Vue({     
     router,
+    store,
     render: h => h(App)
 }).$mount('#app');
 

+ 3 - 0
src/static/img/bleDiscovery.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="76" viewBox="0 0 40 76">
+  <path id="蓝牙" d="M35.389,60.35l7.331-7.295-7.331-7.295Zm0-30.112,7.331-7.295L35.389,15.65ZM36.745,38,51.83,53.1,28.991,76V45.846L16.406,58.4l-4.576-4.58L27.592,38,11.83,22.181l4.576-4.58L28.991,30.154V0L51.83,22.9Z" transform="translate(-11.83)" fill="#fff"/>
+</svg>

+ 6 - 0
src/static/img/bleUpload.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="49" height="76" viewBox="0 0 49 76">
+  <g id="上传" transform="translate(-1331.25 -38)">
+    <path id="Long_Arrow_UP" data-name="Long Arrow UP" d="M35.628,16.293a.852.852,0,0,1-.809.643H28.57l.017,42.676h-7.55V16.935H15.178c-.391,0-1.225-.044-1.39-.508s.273-1.192.273-1.192L24.273,2.124a.818.818,0,0,1,.642-.338.842.842,0,0,1,.67.338l9.9,12.985A1.27,1.27,0,0,1,35.628,16.293Z" transform="translate(1330.822 36.214)" fill="#fff"/>
+    <rect id="矩形_268" data-name="矩形 268" width="49" height="9" rx="4.5" transform="translate(1331.25 105)" fill="#fff"/>
+  </g>
+</svg>

+ 1 - 1
src/static/img/blue.svg

@@ -1,6 +1,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 35 35">
   <g id="信息" transform="translate(10 682.574)">
-    <rect id="矩形_55" data-name="矩形 55" width="35" height="35" transform="translate(-10 -682.574)" opacity="0.35"/>
+    <rect id="矩形_55" data-name="矩形 55" width="35" height="35" transform="translate(-10 -682.574)" opacity="0"/>
     <g id="组_72" data-name="组 72" transform="translate(-5 0)">
       <path id="路径_31" data-name="路径 31" d="M306.653-542.309a2.734,2.734,0,0,0-2.731,2.731,2.717,2.717,0,0,0,1.772,2.557l-.666,12.347h3.263l-.666-12.347a2.723,2.723,0,0,0,1.772-2.557A2.754,2.754,0,0,0,306.653-542.309Z" transform="translate(-294.039 -129.899)" fill="#fff"/>
       <path id="路径_32" data-name="路径 32" d="M6.061-669.091a6.462,6.462,0,0,1,1.052-3.556.964.964,0,0,0-.28-1.332.964.964,0,0,0-1.332.28,8.438,8.438,0,0,0-1.359,4.609,8.434,8.434,0,0,0,1.452,4.742.958.958,0,0,0,.8.426.96.96,0,0,0,.533-.16.948.948,0,0,0,.253-1.332A6.628,6.628,0,0,1,6.061-669.091Zm12.241-5a.949.949,0,0,0-.253,1.332,6.489,6.489,0,0,1,1.119,3.663,6.5,6.5,0,0,1-1.092,3.623.964.964,0,0,0,.266,1.332.961.961,0,0,0,.533.16.958.958,0,0,0,.8-.426,8.362,8.362,0,0,0,1.412-4.689,8.433,8.433,0,0,0-1.452-4.742A.949.949,0,0,0,18.3-674.086Zm4.769-2.065a.949.949,0,0,0-1.332-.253.949.949,0,0,0-.253,1.332,10.617,10.617,0,0,1,1.825,5.981,10.605,10.605,0,0,1-1.785,5.914.963.963,0,0,0,.266,1.332.96.96,0,0,0,.533.16.959.959,0,0,0,.8-.426,12.494,12.494,0,0,0,2.1-6.98A12.561,12.561,0,0,0,23.07-676.151Zm-21.152,7.06A10.581,10.581,0,0,1,3.636-674.9a.964.964,0,0,0-.28-1.332.967.967,0,0,0-1.332.28A12.491,12.491,0,0,0,0-669.091a12.562,12.562,0,0,0,2.158,7.06.959.959,0,0,0,.8.426.96.96,0,0,0,.533-.16.948.948,0,0,0,.253-1.332A10.8,10.8,0,0,1,1.918-669.091Z" transform="translate(0 0)" fill="#fff"/>

+ 1 - 1
src/static/img/wifi.svg

@@ -1,6 +1,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 35 35">
   <g id="wifi" transform="translate(-1759 -33)">
-    <rect id="矩形_54" data-name="矩形 54" width="35" height="35" transform="translate(1759 33)" opacity="0.35"/>
+    <rect id="矩形_54" data-name="矩形 54" width="35" height="35" transform="translate(1759 33)" opacity="0"/>
     <g id="无线" transform="translate(1762 857.555)">
       <path id="路径_28" data-name="路径 28" d="M313.715-379.825a2.513,2.513,0,0,0-2.513,2.513,2.513,2.513,0,0,0,2.513,2.513,2.513,2.513,0,0,0,2.513-2.513,2.513,2.513,0,0,0-.736-1.777A2.513,2.513,0,0,0,313.715-379.825Zm-5.594-3.057a1.22,1.22,0,0,0,.029,1.694,1.22,1.22,0,0,0,1.694.03,5.489,5.489,0,0,1,7.741,0,1.211,1.211,0,0,0,.862.358,1.22,1.22,0,0,0,1.126-.753,1.22,1.22,0,0,0-.264-1.329A7.912,7.912,0,0,0,308.12-382.882Z" transform="translate(-299.483 -421.656)" fill="#fff"/>
       <path id="路径_29" data-name="路径 29" d="M162.5-602.056a13.676,13.676,0,0,0-9.758,4.066,1.211,1.211,0,0,0-.36.862,1.211,1.211,0,0,0,.36.862,1.228,1.228,0,0,0,1.724,0,11.327,11.327,0,0,1,16.019,0,1.22,1.22,0,0,0,1.724,0,1.211,1.211,0,0,0,.36-.862,1.211,1.211,0,0,0-.36-.862A13.677,13.677,0,0,0,162.5-602.056Z" transform="translate(-148.273 -210.654)" fill="#fff"/>

+ 45 - 12
src/store/index.js

@@ -4,16 +4,49 @@ import Vuex from 'vuex'
 Vue.use(Vuex);
 
 export default new Vuex.Store({
-  state: {
-      userLevel:0
-  },
-  mutations: {
-      setUserLevel(userLevel) {
-          console.log(userLevel);
-      }
-  },
-  actions: {
-  },
-  modules: {
-  }
+    state: {
+        eqSn: "",
+        wsUrl: "",
+        shopName: "",
+        bleState: false,    // BLE状态 false:关闭 true:开启
+        bleDiscovery: false,// BLE设备搜索状态 false:关闭 true:开启
+        wsState: false,     // webSocket状态 false:关闭 true:开启
+        classOn: 0,         // 上课状态 0: 下课 1:团课/私教 2:竞技课2PK 3:竞技课3pk
+    },
+    mutations: {
+        setEqSn(state, eqSn) {
+            state.eqSn = eqSn;
+            console.log("[setEqSn] state.eqSn = " + eqSn);
+        },
+        setWsUrl(state, wsUrl) {
+            state.wsUrl = wsUrl;
+            console.log("[setWsUrl] state.wsUrl = " + wsUrl);
+        },
+        setShopName(state, shopName) {
+            state.shopName = shopName;
+            console.log("[setShopName] state.shopName = " + shopName);
+        },
+        setBleState(state, bleState) {
+            state.bleState = bleState;
+            console.log("[setBleState] state.bleState = " + bleState);
+        },
+        setBleDiscovery(state, bleDiscovery) {
+            state.bleDiscovery = bleDiscovery;
+            console.log("[setBleDiscovery] state.bleDiscovery = " + bleDiscovery);
+        },
+        setWsState(state, wsState) {
+            state.wsState = wsState;
+            console.log("[setWsState] state.wsState = " + wsState);
+        },
+        setClassOn(state, classOn) {
+            if (state.classOn != classOn) {
+                state.classOn = classOn;
+                console.log("[setClassOn] state.classOn = " + classOn);
+            }
+        }
+    },
+    actions: {
+    },
+    modules: {
+    }
 })

+ 111 - 19
src/views/Index.vue

@@ -16,6 +16,9 @@ import {
 
 import "../../public/mui.js"
 import '../Global'
+import * as ble from '../ble'
+import * as ws from '../webSocket'
+
 
 let qs = require('qs');
 
@@ -23,8 +26,11 @@ export default {
   name: 'Home',
   data() {
     return {
-      testMode: false,    //是否测试模式
+      testMode: false,      //是否测试模式
       aSideState: false,
+      bleEnable: false,     //是否开启BLE广播监听
+      wsUrl: "ws://192.168.0.3:9000/websocket",            //webSocket服务器URL
+      shopName: "",         //店铺名称 英泓小飞龙
       thisClick: 0,
       tabwildState: 1,
       handleTabsList: [{
@@ -36,34 +42,42 @@ export default {
       curVersion: '2.0',
       isPad: true,// pad 展示版本
       ClentHeight: document.documentElement.clientHeight + 'px',
+      testEqsnArr: [
+        '939a94d3044f1078', // WZX 手机基座测试
+        '7962ad082220d997', // PAD 基座测试
+        '1277fcb4c81e29d2', // ?
+        'e1b37235010cf637', // PAD 正式版
+        'd104bd6ffec3d5ba', // 公司电视 正式版
+        '30:9C:23:0C:8B:1E', // 默认
+      ]
     }
-  },
+  }, 
   mounted() {
-    //浏览器默认的
-    //localStorage.eqSn = '30:9C:23:0C:8B:1E';
-    //localStorage.version = '2.0';
     this.getCurVersion();
-  },
-  methods: {
+  },  
+  methods: {    
     // 获取当前版本号
     getCurVersion() {
       let that = this;
       //浏览器默认的
       localStorage.eqSn = '30:9C:23:0C:8B:1E';
       // localStorage.eqSn = 'd104bd6ffec3d5ba';
-      localStorage.version = '2.2.0';
+      that.$store.commit('setEqSn', localStorage.eqSn);
+      localStorage.version = '2.3.1';
 
       if (window.plus) {
         plusReady();
       } else {
-        console.log('mui is not ready');
+        console.log('>>> plus is not ready');
         // 调试时候关闭
-        this.getServeIpAddress(localStorage.eqSn);
+        this.getConfigParam(localStorage.eqSn);
         // plusReady();
         document.addEventListener('plusready', plusReady, false);
       }
 
       function plusReady() {
+        console.log('>>> plus is ready');
+
         if (typeof plus == 'undefined') return false;
         // 获取本地应用资源版本号
         plus.runtime.getProperty(plus.runtime.appid, function (inf) {
@@ -77,15 +91,16 @@ export default {
             let uuid = JSON.stringify(e.uuid).toString().substr(1);
             uuid = uuid.substring(0, uuid.length - 1);
             localStorage.eqSn = uuid;
-            console.log('===== uuid =====:' + uuid);
+            that.$store.commit('setEqSn', uuid);
+            
+            console.log('>>> [uuid]: ' + uuid);
             console.log('getDeviceInfo success: ' + JSON.stringify(e.uuid));
             // 正式版打开
-            // 获取转发端口地址
-            that.getServeIpAddress(e.uuid);
+            // 获取配置参数
+            that.getConfigParam(e.uuid);
 
             // 公司测试机用公司版本升级 
-            // if (uuid == "1277fcb4c81e29d2" || uuid == "8e501b0bde9ce600") {
-            if (uuid == "d104bd6ffec3d5ba" || uuid == "30:9C:23:0C:8B:1E") { // 内测版
+            if (that.testEqsnArr.includes(uuid)) {
               that.testMode = true;                 
             } else {  // 正式版
               that.testMode = false;            
@@ -174,22 +189,37 @@ export default {
         plus.nativeUI.toast("升级失败[" + e.code + "]:" + e.message);
       });
     },
-    // 获取转发端口地址
-    getServeIpAddress(eqsn) {
+    // 获取配置参数
+    getConfigParam(eqsn) {
       const that = this;
       let url = '';
       if (runVersion == 'test') {   //演示模式
         headapi = "http://cal.beswell.com:85/DataTransfer/";
         console.log("runVersion = " + runVersion + " headapi = " + headapi);
+        that.shopName = "演示模式";
+        that.$store.commit('setShopName', that.shopName);
+        that.wsUrl = "ws://192.168.0.3:9000/websocket";
+        that.$store.commit('setWsUrl', that.wsUrl);
         return false
       }
       if (runVersion == 'localtest') {   //本机测试 TV端映射到本机进行测试
         headapi = "http://127.0.0.1/";
         console.log("runVersion = " + runVersion + " headapi = " + headapi);
+        that.shopName = "本机测试";
+        that.$store.commit('setShopName', that.shopName);
+        that.wsUrl = "ws://192.168.0.3:9000/websocket";
+        that.$store.commit('setWsUrl', that.wsUrl);
+
+        that.bleEnable = true;
+        setTimeout(function() {
+          that.bleRcv(that.bleEnable);
+        }, 3000);
+        
         return false
       }
       // 测试使用0.3心率系统
-      if (eqsn == '1277fcb4c81e29d2' || eqsn == '30:9C:23:0C:8B:1E') {
+      // if (eqsn == '7962ad082220d997' || eqsn == '1277fcb4c81e29d2' || eqsn == '30:9C:23:0C:8B:1E') {
+      if (that.testEqsnArr.includes(eqsn)) {
         url = "http://192.168.0.3:19096/v1/Sensors/GetShopConfigParam";
       } else {
         url = 'http://cal.beswell.com:85/v1/Sensors/GetShopConfigParam'
@@ -203,6 +233,12 @@ export default {
         let json = data.data;
         console.log(json);
         if (json.Code == 0) {
+          that.bleEnable = json.BltHubSwitch;
+          that.shopName = json.ShopName;
+          that.$store.commit('setShopName', that.shopName);
+          that.wsUrl = json.HubReportIp;
+          that.$store.commit('setWsUrl', that.wsUrl);
+
           // 户外版使用HotsPotDataServiceIP
           if (runVersion == 'outDoor') {
             headapi = json.Rs.HotsPotDataServiceIP;
@@ -216,15 +252,71 @@ export default {
             headapi = json.Rs.DataServiceIP;
             // console.log("正式版 headapi = " + headapi);
           }
+
+          that.bleRcv(that.bleEnable);
         } else {
           headapi = 'http://192.168.0.10:8080/';  //树莓派数据转发
+          console.log("error: " + json.Memo);
+          that.shopName = "设备未授权";
+          that.$store.commit('setShopName', that.shopName);
+          that.wsUrl = "ws://192.168.0.3:9000/websocket";
+          that.$store.commit('setWsUrl', that.wsUrl);
         }
         console.log("headapi = " + headapi);
       }, function (response) {
         console.info(response);
       })
-    }
+    },
+    bleRcv(bleEnable) {
+      const that = this;
+      if (window.plus && bleEnable == 1) {
+        console.log('BLE SET Enabled');
+        
+        ble.BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
+        ble.btAdapter = ble.BluetoothAdapter.getDefaultAdapter();
+        ble.Toast = plus.android.importClass("android.widget.Toast");
+        ble.activity = plus.android.runtimeMainActivity();
+        ble.Intent = plus.android.importClass("android.content.Intent");
+      
+        if (!ble.getBluetoothStatus()) {
+          ble.turnOnBluetooth();
+        }        
+        ble.listenerStateChange();      // 监听状态变化
+        ble.listenerDeviceFound();      // 监听发现新设备
+
+        let now = 0;
+        let diffTime = 0;
+        let bleIntvTime = setInterval(() => {
+          //已上课
+          if (that.$store.state.classOn > 0) {
+            if (!ble.getBleDiscovery() && ble.getBluetoothStatus()) {
+              ble.startBluetoothDiscovery();  // 开始搜索蓝牙
+              console.log('已上课,开始接收心率数据');
+              plus.nativeUI.toast("已上课,开始接收心率数据");
+            }            
+            else if (ble.getBleDiscovery()) {   //接收心率数据时发生异常
+              now = new Date().getTime();       //毫秒
+              diffTime = now - ble.lastRcvTime;
+              if (diffTime >= 3000) {
+                console.log('接收心率数据时间超时,重置蓝牙搜索');
+                ble.stopBluetoothDiscovery();   // 停止搜索蓝牙
+                ble.setBleDiscovery(false);
+                ble.lastRcvTime += 3000;
+              }
+            }
 
+            ble.uploadBleData();
+          }          
+          //已下课,停止接收心率数据
+          else if (ble.getBleDiscovery() && that.$store.state.classOn == 0) {
+            console.log('已下课,停止接收心率数据');
+            ble.setBleDiscovery(false);
+            ble.stopBluetoothDiscovery();  // 停止搜索蓝牙
+            plus.nativeUI.toast("已下课,停止接收心率数据");
+          }
+        }, 1500);
+      }
+    }
   },
   components: {}
 }

+ 2 - 1
src/views/Main.vue

@@ -91,7 +91,7 @@ export default {
     '$route': function (val) {
       if (val.path == '/main') {
         if (this.trueDate) {
-          console.log("使用真实数据");
+          // console.log("使用真实数据");
           this.init();
         } else {
           console.log("使用假数据");
@@ -277,6 +277,7 @@ export default {
         let json = res;
         // console.log("[Main] getClassStat: " + JSON.stringify(json));
         if (json.Code == 0) {
+          that.$store.commit('setClassOn', json.ClassOn);
           // 没开课
           if (json.ClassOn == 0) {
             that.ClassOn = 0;

+ 2 - 2
src/views/Rank.vue

@@ -186,9 +186,9 @@ export default {
           }
           // 根据人数多少显示停留时间
           if (Studenlength > 3) {
-            totalTime = 120
+            totalTime = 135
           } else {
-            totalTime = 110
+            totalTime = 125
           }
           this.jumpWait(totalTime);
 

+ 2 - 1
src/views/Wait.vue

@@ -359,6 +359,7 @@ export default {
         let json = res;
         // console.log("[Wait] getClassStat: " + JSON.stringify(json));
         if (json.Code == 0) {
+          that.$store.commit('setClassOn', json.ClassOn);
           // 上课了
           if (json.ClassOn == 1) {
             // 0: 下课 团课/私教 排名
@@ -543,7 +544,7 @@ $yellow: #FFEB50;
 
         .num {
           position: relative;
-          left: -0.1rem;
+          left: -0.15rem;
           width: 0.9rem;
           height: 0.65rem;
           float: left;

+ 1 - 0
src/views/pk.vue

@@ -448,6 +448,7 @@ export default {
       getClassStat(postdata).then(res => {
         let json = res;
         if (json.Code == 0) {
+          that.$store.commit('setClassOn', json.ClassOn);
           // 没开课
           if (json.ClassOn == 0) {
             that.ClassOn = 0;

+ 1 - 0
src/views/threepk.vue

@@ -499,6 +499,7 @@ export default {
       getClassStat(postdata).then(res => {
         let json = res;
         if (json.Code == 0) {
+          that.$store.commit('setClassOn', json.ClassOn);
           // 没开课
           if (json.ClassOn == 0) {
             that.ClassOn = 0;

+ 76 - 0
src/webSocket.js

@@ -0,0 +1,76 @@
+import store from './store'
+
+var ws = null;
+// var wsState = false;
+
+export function getWsState() {
+    // return wsState;
+    return store.state.wsState;
+}
+
+function setWsState(wsState) {
+    store.commit('setWsState', wsState);
+}
+
+export function connect(wsUrl, wsId) {
+    if (ws != null) {
+        return true;
+    }
+
+    if (wsUrl == '' || wsId == '') {
+        console.log("[WebSocket] Connection err: param err"); 
+        return false;
+    }
+
+    let url = wsUrl + '/' + wsId;
+    ws = new WebSocket(url);
+    console.log("[WebSocket] exec connect(" + url + ")");
+
+    ws.onopen = function(evt) {
+        setWsState(true);
+        console.log("[WebSocket] Connection open: " + JSON.stringify(evt)); 
+        // ws.send("Hello WebSockets!");
+    };
+
+    ws.onmessage = (evt) => {
+        // var data = evt.data;
+        console.log("[WebSocket] message: " + JSON.stringify(evt));
+        // ws.send('ping');
+      };
+
+    ws.onclose = function(evt) {
+        ws = null;
+        setWsState(false);
+        console.log("[WebSocket] Connection closed.");
+    };
+
+    ws.onerror = function(evt) {
+        console.log("[WebSocket] error: " + JSON.stringify(evt));
+        disconnect();
+        setWsState(false);
+    };
+
+    return true;
+}
+
+export function disconnect() {
+    console.log("[WebSocket] exec disconnect()");
+    if (ws != null && ws.readyState == WebSocket.OPEN) {
+        ws.close();
+        console.log("[WebSocket] exec ws.close()");
+        ws = null;
+    }
+}
+
+export function sendData(data) {
+    if (getWsState()) {
+        ws.send(data);
+        // console.log("[WebSocket] sendData: " + data);
+        return true;
+    }
+    else {
+        console.log("[WebSocket] sendData err: " + data);
+        return false;
+    }
+}
+

+ 9 - 0
vue.config.js

@@ -19,6 +19,15 @@ module.exports = {
     devServer: {
         port: 202, // 端口
         proxy: {
+            // '/websocket': {
+            //     target: 'ws://192.168.0.22:8080/',
+            //     // target: 'http://82.157.123.54:9010',
+            //     ws: true,
+            //     changeOrigin: true,
+            //     // pathRewrite: {
+            //     //     '^/websocket': '/',
+            //     // }
+            // },
             '/api': {
                 target: 'http://221.214.111.254:9000/',//小飞龙的
                 // target: 'http://192.168.0.162:9000/',//PC的