wzh 2 lat temu
rodzic
commit
e7c6f159ae

+ 1 - 1
config.js

@@ -1,7 +1,7 @@
 // 应用全局配置
 module.exports = {
   //baseUrl: 'https://vue.ruoyi.vip/prod-api',
-  baseUrl: 'http://120.55.183.139:8090',
+  baseUrl: 'http://127.0.0.1:8090',
   socketUrl: 'ws://120.55.183.139:8090/websocket',
   imgUrl:'https://saomawzz.oss-cn-hangzhou.aliyuncs.com/chargerforeign',
   // 应用信息

+ 25 - 11
pages.json

@@ -1,12 +1,26 @@
 {
   "pages": [
-
+   
     {
       "path": "pages/login",
       "style": {
         "navigationBarTitleText": "登录"
       }
     },
+	{
+	  "path": "pages/bluetooth/index/index",
+	  "style": {
+	    "navigationBarTitleText": "蓝牙连接",
+		"navigationStyle": "default"
+	  }
+	},
+	{
+	  "path": "pages/bluetooth/device/device",
+	  "style": {
+	    "navigationBarTitleText": "发送指令",
+		"navigationStyle": "default"
+	  }
+	},
      {
   "path": "pages/weitiandi/device/plan",
   "style": {
@@ -105,15 +119,15 @@
       }
 
     }
-      ,{
-            "path" : "pages/weitiandi/device/scan",
-            "style" :                                                                                    
-            {
-                "navigationBarTitleText": "扫码",
-                "enablePullDownRefresh": false
-            }
-            
-        }
+  ,{
+      "path" : "pages/weitiandi/device/scan",
+      "style" :
+      {
+        "navigationBarTitleText": "扫码",
+        "enablePullDownRefresh": false
+      }
+
+    }
     ],
   "tabBar": {
     "color": "#000000",
@@ -136,7 +150,7 @@
   "globalStyle": {
     "navigationBarTextStyle": "black",
     "navigationBarTitleText": "充电助手",
-    "navigationBarBackgroundColor": "rgb(249,252,255)",
+    "navigationBarBackgroundColor": "#F9FCFF",
     "navigationStyle":"custom"
   }
 }

+ 285 - 0
pages/bluetooth/device/device.vue

@@ -0,0 +1,285 @@
+<template>
+	<view class="device-container">
+		<text class="title-rev">数据接收 : </text>
+		<button class="bt-clear" type="primary" @click="btClearTap" hover-start-time="0">清空</button>
+		<checkbox-group @change="checkScroll" class="checkbox-scroll">
+			<checkbox checked="true"></checkbox>
+			<text>滚动</text>
+		</checkbox-group>
+		<checkbox-group @change="checkRevHex" class="checkbox-rev-hex">
+			<checkbox></checkbox>
+			<text>Hex</text>
+		</checkbox-group>
+
+
+		<view class="view-bt-send">
+			<button class="bt-send" type="primary" @click="btSendTap" hover-start-time="0">发送</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP
+	import ecUI from '@/utils/ecUI.js'
+	import ecBLE from '@/utils/ecBLE/ecBLE.js'
+	// #endif   
+	// #ifdef MP
+	const ecUI = require('@/utils/ecUI.js')
+	const ecBLE = require('@/utils/ecBLE/ecBLE.js')
+	// #endif
+	let ctx
+	let isCheckScroll = true
+	let isCheckRevHex = false
+	let isCheckSendHex = false
+	let sendData = ''
+	export default {
+		data() {
+			return {
+				textRevData: '',
+				scrollIntoView: 'scroll-view-bottom',
+			}
+		},
+		onLoad() {
+			ctx = this
+			isCheckScroll = true
+			isCheckRevHex = false
+			isCheckSendHex = false
+			sendData = ''
+
+			//on disconnect
+			ecBLE.onBLEConnectionStateChange(() => {
+				ecUI.showModal('提示', '设备断开连接')
+			})
+			//receive data
+			ecBLE.onBLECharacteristicValueChange((str, strHex) => {
+				let data =
+					ctx.textRevData +
+					'[' +
+					ctx.dateFormat('hh:mm:ss,S', new Date()) +
+					']: ' +
+					(isCheckRevHex ? strHex.replace(/[0-9a-fA-F]{2}/g, ' $&') : str) +
+					'\r\n'
+				// console.log(data)
+				ctx.textRevData = data
+				if (isCheckScroll) {
+					if (ctx.scrollIntoView === "scroll-view-bottom") {
+						ctx.scrollIntoView = "scroll-view-bottom2"
+					} else {
+						ctx.scrollIntoView = "scroll-view-bottom"
+					}
+				}
+			})
+		},
+		onUnload() {
+			ecBLE.onBLEConnectionStateChange(() => {})
+			ecBLE.onBLECharacteristicValueChange(() => {})
+			ecBLE.closeBLEConnection()
+		},
+		methods: {
+			checkScroll(e) {
+				if (e.detail.value.length) isCheckScroll = true
+				else isCheckScroll = false
+			},
+			checkRevHex(e) {
+				if (e.detail.value.length) isCheckRevHex = true
+				else isCheckRevHex = false
+			},
+			checkSendHex(e) {
+				if (e.detail.value.length) isCheckSendHex = true
+				else isCheckSendHex = false
+			},
+			inputSendData(e) {
+				sendData = e.detail.value
+			},
+			btClearTap() {
+				ctx.textRevData = ''
+			},
+			btSendTap() {
+				if (isCheckSendHex) {
+					let data = sendData
+						.replace(/\s*/g, '')
+						.replace(/\n/g, '')
+						.replace(/\r/g, '')
+					if (data.length === 0) {
+						ecUI.showModal('提示', '请输入要发送的数据')
+						return
+					}
+					if (data.length % 2 != 0) {
+						ecUI.showModal('提示', '数据长度只能是双数')
+						return
+					}
+					if (data.length > 488) {
+						ecUI.showModal('提示', '最多只能发送244字节')
+						return
+					}
+					if (!new RegExp('^[0-9a-fA-F]*$').test(data)) {
+						ecUI.showModal('提示', '数据格式错误,只能是0-9,a-f,A-F')
+						return
+					}
+					ecBLE.writeBLECharacteristicValue(data, true)
+				} else {
+					if (sendData.length === 0) {
+						ecUI.showModal('提示', '请输入要发送的数据')
+						return
+					}
+					let tempSendData = sendData.replace(/\n/g, '\r\n')
+					if (tempSendData.length > 244) {
+						ecUI.showModal('提示', '最多只能发送244字节')
+						return
+					}
+					ecBLE.writeBLECharacteristicValue(tempSendData, false)
+				}
+			},
+			dateFormat(fmt, date) {
+				var o = {
+					'M+': date.getMonth() + 1, //月份
+					'd+': date.getDate(), //日
+					'h+': date.getHours(), //小时
+					'm+': date.getMinutes(), //分
+					's+': date.getSeconds(), //秒
+					'q+': Math.floor((date.getMonth() + 3) / 3), //季度
+					S: date.getMilliseconds(), //毫秒
+				}
+				if (/(y+)/.test(fmt))
+					fmt = fmt.replace(
+						RegExp.$1,
+						(date.getFullYear() + '').substr(4 - RegExp.$1.length)
+					)
+				for (var k in o)
+					if (new RegExp('(' + k + ')').test(fmt)) {
+						// console.log(RegExp.$1.length)
+						// console.log(o[k])
+						fmt = fmt.replace(
+							RegExp.$1,
+							RegExp.$1.length == 1 ?
+							(o[k] + '').padStart(3, '0') :
+							('00' + o[k]).substr(('' + o[k]).length)
+						)
+					}
+				return fmt
+			},
+		}
+	}
+</script>
+
+<style>
+	.device-container {
+		height: 100vh;
+		position: relative;
+	}
+
+	.title-rev {
+		position: absolute;
+		top: 0px;
+		left: 20px;
+		line-height: 45px;
+		font-size: 17px;
+	}
+
+	.bt-clear {
+		position: absolute;
+		top: 8px;
+		right: 165px;
+		width: 55px !important;
+		height: 29px;
+		font-size: 14px;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		padding: 0;
+	}
+
+	.checkbox-scroll {
+		position: absolute;
+		top: 0;
+		right: 90px;
+		height: 45px;
+		font-size: 15px;
+		display: flex;
+		align-items: center;
+	}
+
+	.checkbox-rev-hex {
+		position: absolute;
+		top: 0px;
+		right: 20px;
+		height: 45px;
+		font-size: 15px;
+		display: flex;
+		align-items: center;
+	}
+
+	.scroll-view-container {
+		position: absolute;
+		top: 45px;
+		left: 20px;
+		right: 20px;
+		padding: 0 3px 0 5px;
+		background-color: #E5E5E5;
+	}
+
+	.scroll-view-rev {
+		height: 150px;
+		background-color: #E5E5E5;
+	}
+
+	.view-rev-gap {
+		height: 5px;
+	}
+
+	.text-rev {
+		font-size: 14px;
+		word-break: break-all;
+		font-family: Monospace;
+	}
+
+	.title-send {
+		position: absolute;
+		top: 200px;
+		left: 20px;
+		font-size: 17px;
+		line-height: 45px;
+	}
+
+	.checkbox-send-hex {
+		position: absolute;
+		top: 200px;
+		right: 20px;
+		height: 45px;
+		font-size: 15px;
+		display: flex;
+		align-items: center;
+	}
+
+	.view-input-send {
+		position: absolute;
+		top: 245px;
+		left: 20px;
+		right: 20px;
+		padding: 2px 3px;
+		background-color: #E5E5E5;
+		overflow-x: hidden;
+	}
+
+	.input-send {
+		height: 84px;
+		width: 100%;
+		background-color: #E5E5E5;
+	}
+
+	.view-bt-send {
+		position: absolute;
+		top: 370px;
+		left: 20px;
+		right: 20px;
+		display: flex;
+	}
+
+	.bt-send {
+		flex: 1;
+		height: 45px;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+</style>

+ 189 - 0
pages/bluetooth/index/index.vue

@@ -0,0 +1,189 @@
+<template>
+	<scroll-view class="main-container" scroll-y="true"
+		:refresher-enabled="false">
+		<view v-for="(item, index) in deviceListDataShow" :key="item.id" class="list-item" hover-class="list-item-hover"
+			hover-start-time="0" hover-stay-time="100" @click="listViewTap(item.id)">
+			<image v-if="item.manufacturer==='eciot'" src="/static/img/ecble.png" class="list-item-img"></image>
+			<image v-else src="/static/img/ble.png" class="list-item-img"></image>
+			<text class="list-item-name">{{item.name}}</text> 
+			<image v-if="item.rssi >= -41" src="/static/img/s5.png" mode="aspectFit" class="list-item-rssi-img"></image>
+			<image v-else-if="item.rssi >= -55" src="/static/img/s4.png" mode="aspectFit" class="list-item-rssi-img"></image>
+			<image v-else-if="item.rssi >= -65" src="/static/img/s3.png" mode="aspectFit" class="list-item-rssi-img"></image>
+			<image v-else-if="item.rssi >= -75" src="/static/img/s2.png" mode="aspectFit" class="list-item-rssi-img"></image>
+			<image v-else="item.rssi < -75" src="/static/img/s1.png" mode="aspectFit" class="list-item-rssi-img"></image>
+			<text class="list-item-rssi">{{item.rssi}}</text>
+			<view class="list-item-line"></view>
+		</view>
+		<view v-if="deviceListDataShow.length==0" class="notice"> - 未发现设备,请确认蓝牙是否打开 -</view>
+		<view class="gap"></view>
+	</scroll-view>
+</template>
+
+<script> 
+// #ifdef APP
+import ecUI from '@/utils/ecUI.js'
+import ecBLE from '@/utils/ecBLE/ecBLE.js'
+// #endif   
+// #ifdef MP
+const ecUI = require('@/utils/ecUI.js')     
+const ecBLE = require('@/utils/ecBLE/ecBLE.js')
+// #endif
+	let ctx
+	let deviceListData = []
+	export default {  
+		data() {
+			return {
+        timer:"",
+				deviceListDataShow: []
+			}
+		},
+		onLoad() {
+			ctx = this
+      clearInterval(this.timer);
+			this.timer = setInterval(() => {
+				ctx.deviceListDataShow = JSON.parse(JSON.stringify(deviceListData))
+			}, 800)
+		},
+		onShow() {
+			setTimeout(() => {
+				ctx.openBluetoothAdapter()
+			}, 100)
+		},
+		methods: {
+			listViewTap(id){
+				ecUI.showLoading('设备连接中')
+				ecBLE.onBLEConnectionStateChange(res => {
+					ecUI.hideLoading()
+					if (res.ok) {
+						ecBLE.stopBluetoothDevicesDiscovery()
+						uni.navigateTo({ url: '../device/device' })
+					} else {
+						ecUI.showModal(
+							'提示',
+							'连接失败,errCode=' + res.errCode + ',errMsg=' + res.errMsg
+						)
+					}
+				})
+				ecBLE.createBLEConnection(id)
+			},
+			openBluetoothAdapter() {
+				ecBLE.onBluetoothAdapterStateChange(res => {
+					if (res.ok) {
+						console.log('Bluetooth adapter ok')
+						ctx.startBluetoothDevicesDiscovery()
+					} else {
+						ecUI.showModal(
+							'提示',
+							`Bluetooth adapter error | ${res.errCode} | ${res.errMsg}`,
+							() => {
+
+							}
+						)
+					}
+				})
+				ecBLE.openBluetoothAdapter()
+			},
+			startBluetoothDevicesDiscovery() {
+				console.log('start search')
+				ecBLE.onBluetoothDeviceFound(res => {
+					// if(res.id==="EC:22:05:13:78:49")
+					// console.log(`id:${res.id},name:${res.name},rssi:${res.rssi}`)
+					for (const item of deviceListData) {
+						if (item.id === res.id) {
+							item.name = res.name
+							item.rssi = res.rssi
+							return
+						}
+					}
+					let manufacturer = ''
+					if (res.name.length === 11 && res.name.startsWith('@')) {
+						manufacturer = 'eciot'
+					}
+					if (res.name.length === 15 && res.name.startsWith('BT_')) {
+						manufacturer = 'eciot'
+					}
+					deviceListData.push({
+						id: res.id,
+						name: res.name,
+						rssi: res.rssi,
+						manufacturer,
+					})
+				})
+				ecBLE.startBluetoothDevicesDiscovery()
+			},
+		}
+	}
+</script>
+
+<style>
+	.main-container {
+		height: 100vh;
+	}
+
+	.list-item {
+		height: 57px;
+		position: relative;
+	}
+
+	.list-item-hover {
+		background-color: #e5e4e9;
+	}
+
+	.list-item-img {
+		position: absolute;
+		width: 36px;
+		height: 36px;
+		left: 20px;
+		top: 10px;
+	}
+
+	.list-item-name {
+		position: absolute;
+		font-size: 22px;
+		left: 76px;
+		top: 0px;
+		line-height: 56px;
+	}
+
+	.list-item-rssi-img {
+		position: absolute;
+		width: 20px;
+		height: 20px;
+		right: 20px;
+		top: 13px;
+	}
+
+	.list-item-rssi {
+		position: absolute;
+		width: 40px;
+		height: 20px;
+		right: 10px;
+		top: 33px;
+		font-size: 12px;
+		font-weight: bold;
+		display: flex;
+		justify-content: center;
+	}
+
+	.list-item-line {
+		position: absolute;
+		height: 1px;
+		width: 100vw;
+		left: 20px;
+		top: 56px;
+		background-color: #c6c6c8;
+	}
+
+	.notice {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		margin-top: 10px;
+		font-size: 13px;
+		color: #909399;
+	}
+
+	.gap {
+		height: 57px;
+	}
+</style>

+ 9 - 1
pages/login.vue

@@ -133,7 +133,15 @@
     },
     methods: {
       buleTeeth(){
-        this.$modal.showToast('功能开发中~')
+        uni.navigateTo({
+          url: '/pages/buletooth/bluetooth',
+          complete:function (){
+            debugger;
+          },
+          fail:function (res){
+            console.log(res)
+          }
+        });
       },
       checkLogin(){
         let token = getToken()

BIN
static/img/back.png


BIN
static/img/ble.png


BIN
static/img/ecble.png


BIN
static/img/logo.png


BIN
static/img/s1.png


BIN
static/img/s2.png


BIN
static/img/s3.png


BIN
static/img/s4.png


BIN
static/img/s5.png


+ 12 - 0
utils/ecBLE/ecBLE.js

@@ -0,0 +1,12 @@
+// #ifdef APP
+export {default} from './ecBLEApp.js'
+// #endif
+// #ifdef MP-WEIXIN
+module.exports = require('./ecBLEWX.js')
+// #endif
+// #ifdef MP-ALIPAY
+module.exports = require('./ecBLEALI.js')
+// #endif
+// #ifdef MP-JD
+module.exports = require('./ecBLEJD.js')
+// #endif

+ 497 - 0
utils/ecBLE/ecBLEALI.js

@@ -0,0 +1,497 @@
+const logEnable = false
+let isAndroid = false
+let ecBluetoothAdapterStateChangeCallback = () => { }
+let ecBLEConnectionStateChangeCallback = () => { }
+let ecDeviceId = ''
+let ecGattServerUUID = ''
+const ecGattServerUUIDOption1 = '0000FFF0-0000-1000-8000-00805F9B34FB'
+const ecGattServerUUIDOption2 = 'FFF0'
+let ecGattCharacteristicWriteUUID = ''
+const ecGattCharacteristicWriteUUIDOption1 = '0000FFF2-0000-1000-8000-00805F9B34FB'
+const ecGattCharacteristicWriteUUIDOption2 = 'FFF2'
+const log = data => {
+    if (logEnable) {
+        console.log('[eciot]:' + JSON.stringify(data))
+    }
+}
+const onBluetoothAdapterStateChange = cb => {
+    ecBluetoothAdapterStateChangeCallback = cb
+}
+const getSetting = () => {
+    return new Promise(function (resolve, reject) {
+        my.getSetting({
+            success(res) {
+                log(res)
+                if (res.authSetting && res.authSetting.bluetooth) {
+                    resolve({ ok: true, errCode: 0, errMsg: '' })
+                } else {
+                    resolve({
+                        ok: false,
+                        errCode: 30001,
+                        errMsg: 'getSetting fail',
+                    })
+                }
+            },
+            fail(res) {
+                log(res)
+                resolve({
+                    ok: false,
+                    errCode: res.error ? res.error : 30000,
+                    errMsg: res.errorMessage ? res.errorMessage : 'getSetting fail',
+                })
+            },
+        })
+    })
+}
+const authorize = async () => {
+    // return new Promise(function (resolve, reject) {
+    //     my.showBLEPermissionGuide({
+    //         success(res) {
+    //             log(res)
+    //             resolve({ ok: true, errCode: 0, errMsg: '' })
+    //         },
+    //         fail(res) {
+    //             log(res)
+    //             resolve({ ok: false, errCode: 30000, errMsg: res.errorMessage })
+    //         },
+    //     })
+    // })
+    return await _openBluetoothAdapter()
+}
+const _openBluetoothAdapter = () => {
+    return new Promise(function (resolve, reject) {
+        my.openBluetoothAdapter({
+            success(res) {
+                log(res)
+                if (res.isSupportBLE) {
+                    resolve({ ok: true, errCode: 0, errMsg: '' })
+                } else {
+                    resolve({ ok: false, errCode: 30001, errMsg: 'isSupportBLE is false' })
+                }
+            },
+            fail(res) {
+                log(res)
+                resolve({
+                    ok: false,
+                    errCode: res.error ? res.error : 30000,
+                    errMsg: res.errorMessage,
+                })
+            },
+        })
+    })
+}
+const openBluetoothAdapter = async () => {
+    await _openBluetoothAdapter()
+    const systemInfo = my.getSystemInfoSync()
+    log(systemInfo)
+    if (systemInfo.platform.toLowerCase() === 'android') {
+        isAndroid = true
+    }
+    if (isAndroid && !systemInfo.bluetoothEnabled) {
+        ecBluetoothAdapterStateChangeCallback({
+            ok: false,
+            errCode: 30001,
+            errMsg: '请打开系统蓝牙开关',
+        })
+        return
+    }
+    if (isAndroid && !systemInfo.locationEnabled) {
+        ecBluetoothAdapterStateChangeCallback({
+            ok: false,
+            errCode: 30002,
+            errMsg: '请打开系统定位开关',
+        })
+        return
+    }
+    if (isAndroid && !systemInfo.locationAuthorized) {
+        ecBluetoothAdapterStateChangeCallback({
+            ok: false,
+            errCode: 30003,
+            errMsg: '请打开支付宝定位权限,允许支付宝使用您的位置信息',
+        })
+        return
+    }
+    const setting = await getSetting() //小程序蓝牙权限
+    if (!setting.ok) {
+        const authRes = await authorize()
+        if (!authRes.ok) {
+            ecBluetoothAdapterStateChangeCallback({
+                ok: false,
+                errCode: 30004,
+                errMsg: '请打开小程序蓝牙开关,点击右上角三个点,然后点击设置',
+            })
+            return
+        }
+    }
+    my.offBluetoothAdapterStateChange()
+    my.onBluetoothAdapterStateChange(res => {
+        log(res)
+        // {"available":false,"discovering":false,"NBPageUrl":"https://2021002131657266.hybrid.alipay-eco.com/index.html#pages/index/index"}
+        if (!res.available) {
+            ecBluetoothAdapterStateChangeCallback({
+                ok: false,
+                errCode: 30005,
+                errMsg: '蓝牙适配器不可用',
+            })
+        }
+    })
+    const openRes = await _openBluetoothAdapter()
+    ecBluetoothAdapterStateChangeCallback(openRes)
+}
+
+const onBluetoothDeviceFound = cb => {
+    my.offBluetoothDeviceFound()
+    my.onBluetoothDeviceFound(res => {
+        log(res)
+        const device = res.devices[0]
+        const name = device.name ? device.name : device.localName
+        if (!name) {
+            return
+        }
+        let id = device.deviceId
+        let rssi = device.RSSI
+        cb({ id, name, rssi })
+    })
+}
+const startBluetoothDevicesDiscovery = () => {
+    my.startBluetoothDevicesDiscovery({
+        //services: [ecServerId],
+        allowDuplicatesKey: true,
+        // powerLevel: 'high',
+        complete(res) {
+            log(res)
+        },
+    })
+}
+const stopBluetoothDevicesDiscovery = () => {
+    my.stopBluetoothDevicesDiscovery({
+        complete(res) {
+            log(res)
+        },
+    })
+}
+
+const onBLEConnectionStateChange = cb => {
+    ecBLEConnectionStateChangeCallback = cb
+}
+const _createBLEConnection = () => {
+    return new Promise(function (resolve, reject) {
+        my.connectBLEDevice({
+            deviceId: ecDeviceId,
+            success(res) {
+                log(res)
+                resolve({ ok: true, errCode: 0, errMsg: '' })
+            },
+            fail(res) {
+                log(res)
+                resolve({
+                    ok: false,
+                    errCode: res.error,
+                    errMsg: res.errorMessage,
+                })
+            },
+        })
+    })
+}
+const getBLEDeviceServices = () => {
+    return new Promise(function (resolve, reject) {
+        my.getBLEDeviceServices({
+            deviceId: ecDeviceId,
+            success(res) {
+                log(res)
+                resolve({
+                    ok: true,
+                    errCode: 0,
+                    errMsg: '',
+                    services: res.services,
+                })
+            },
+            fail(res) {
+                log(res)
+                resolve({ ok: false, errCode: res.error, errMsg: res.errorMessage })
+            },
+        })
+    })
+}
+const getBLEDeviceCharacteristics = serviceId => {
+    return new Promise(function (resolve, reject) {
+        my.getBLEDeviceCharacteristics({
+            deviceId: ecDeviceId,
+            serviceId,
+            success(res) {
+                log(res)
+                resolve({
+                    ok: true,
+                    errCode: 0,
+                    errMsg: '',
+                    characteristics: res.characteristics,
+                })
+            },
+            fail(res) {
+                log(res)
+                resolve({ ok: false, errCode: res.error, errMsg: res.errorMessage })
+            },
+        })
+    })
+}
+const notifyBLECharacteristicValueChange = (serviceId, characteristicId) => {
+    return new Promise(function (resolve, reject) {
+        my.notifyBLECharacteristicValueChange({
+            state: true,
+            deviceId: ecDeviceId,
+            serviceId,
+            characteristicId,
+            success(res) {
+                log(res)
+                resolve({ ok: true, errCode: 0, errMsg: '' })
+            },
+            fail(res) {
+                log(res)
+                resolve({ ok: false, errCode: res.error, errMsg: res.errorMessage })
+            },
+        })
+    })
+}
+const setBLEMTU = mtu => {
+    return new Promise(function (resolve, reject) {
+        my.setBLEMTU({
+            deviceId: ecDeviceId,
+            mtu,
+            success(res) {
+                log(res)
+                resolve({ ok: true, errCode: 0, errMsg: '' })
+            },
+            fail(res) {
+                log(res)
+                resolve({ ok: false, errCode: res.error, errMsg: res.errorMessage })
+            },
+        })
+    })
+}
+const createBLEConnection = async id => {
+    ecDeviceId = id
+    my.offBLEConnectionStateChanged()
+    my.onBLEConnectionStateChanged(async res => {
+        log(res)
+        // {"deviceId":"EC:22:05:13:78:49","connected":true}
+        if (res.connected) {
+            const servicesResult = await getBLEDeviceServices()
+            if (!servicesResult.ok) {
+                ecBLEConnectionStateChangeCallback(servicesResult)
+                closeBLEConnection()
+                return
+            }
+            for (const service of servicesResult.services) {
+                if ((service.serviceId.toUpperCase() === ecGattServerUUIDOption1) ||
+                    (service.serviceId.toUpperCase() === ecGattServerUUIDOption2)) {
+                    ecGattServerUUID = service.serviceId
+                }
+                const characteristicsResult = await getBLEDeviceCharacteristics(
+                    service.serviceId
+                )
+                if (!characteristicsResult.ok) {
+                    ecBLEConnectionStateChangeCallback(characteristicsResult)
+                    closeBLEConnection()
+                    return
+                }
+                for (const characteristic of characteristicsResult.characteristics) {
+                    if (
+                        characteristic.properties &&
+                        characteristic.properties.notify
+                    ) {
+                        const notifyResult =
+                            await notifyBLECharacteristicValueChange(
+                                service.serviceId,
+                                characteristic.characteristicId
+                            )
+                        if (!notifyResult.ok) {
+                            ecBLEConnectionStateChangeCallback({
+                                ok: false,
+                                errCode: 30000,
+                                errMsg: 'notify error',
+                            })
+                            closeBLEConnection()
+                            return
+                        }
+                    }
+
+                    if ((characteristic.characteristicId.toUpperCase() === ecGattCharacteristicWriteUUIDOption1) ||
+                        (characteristic.characteristicId.toUpperCase() === ecGattCharacteristicWriteUUIDOption2)) {
+                        ecGattCharacteristicWriteUUID = characteristic.characteristicId
+                    }
+                }
+            }
+            if (isAndroid) {
+                await setBLEMTU(247)
+            }
+            ecBLEConnectionStateChangeCallback({
+                ok: true,
+                errCode: 0,
+                errMsg: '',
+            })
+        } else {
+            ecBLEConnectionStateChangeCallback({
+                ok: false,
+                errCode: 0,
+                errMsg: 'disconnect',
+            })
+        }
+    })
+    const res = await _createBLEConnection()
+    if (!res.ok) {
+        ecBLEConnectionStateChangeCallback(res)
+    }
+}
+const closeBLEConnection = () => {
+    my.disconnectBLEDevice({
+        deviceId: ecDeviceId,
+        complete(res) {
+            log(res)
+        },
+    })
+}
+
+const onBLECharacteristicValueChange = cb => {
+    my.offBLECharacteristicValueChange()
+    my.onBLECharacteristicValueChange(res => {
+        log(res)
+        let bytes = []
+        for (let i = 0; i < (res.value.length / 2); i++) {
+            bytes.push(parseInt(res.value.substr(i * 2, 2), 16))
+        }
+        let str = utf8BytesToStr(bytes)
+        let strHex = res.value
+        log(str)
+        log(strHex)
+        cb(str, strHex)
+    })
+}
+
+const _writeBLECharacteristicValue = buffer => {
+    return new Promise(function (resolve, reject) {
+        my.writeBLECharacteristicValue({
+            deviceId: ecDeviceId,
+            serviceId: ecGattServerUUID,
+            characteristicId: ecGattCharacteristicWriteUUID,
+            value: buffer,
+            // writeType: 'writeNoResponse',
+            success(res) {
+                log(res)
+                resolve({ ok: true, errCode: 0, errMsg: '' })
+            },
+            fail(res) {
+                log(res)
+                resolve({ ok: false, errCode: res.error, errMsg: res.errorMessage })
+            },
+        })
+    })
+}
+const writeBLECharacteristicValue = async (str, isHex) => {
+    if (str.length === 0)
+        return { ok: false, errCode: 30000, errMsg: 'data is null' }
+    let buffer
+    if (isHex) {
+        buffer = new ArrayBuffer(str.length / 2)
+        let x = new Uint8Array(buffer)
+        for (let i = 0; i < x.length; i++) {
+            x[i] = parseInt(str.substr(2 * i, 2), 16)
+        }
+    } else {
+        buffer = new Uint8Array(strToUtf8Bytes(str)).buffer
+    }
+
+    return await _writeBLECharacteristicValue(buffer)
+}
+
+const utf8BytesToStr = utf8Bytes => {
+    let unicodeStr = ''
+    for (let pos = 0; pos < utf8Bytes.length;) {
+        let flag = utf8Bytes[pos]
+        let unicode = 0
+        if (flag >>> 7 === 0) {
+            unicodeStr += String.fromCharCode(utf8Bytes[pos])
+            pos += 1
+        }
+        // else if ((flag & 0xFC) === 0xFC) {
+        //     unicode = (utf8Bytes[pos] & 0x3) << 30
+        //     unicode |= (utf8Bytes[pos + 1] & 0x3F) << 24
+        //     unicode |= (utf8Bytes[pos + 2] & 0x3F) << 18
+        //     unicode |= (utf8Bytes[pos + 3] & 0x3F) << 12
+        //     unicode |= (utf8Bytes[pos + 4] & 0x3F) << 6
+        //     unicode |= (utf8Bytes[pos + 5] & 0x3F)
+        //     unicodeStr += String.fromCharCode(unicode)
+        //     pos += 6
+        // }
+        // else if ((flag & 0xF8) === 0xF8) {
+        //     unicode = (utf8Bytes[pos] & 0x7) << 24
+        //     unicode |= (utf8Bytes[pos + 1] & 0x3F) << 18
+        //     unicode |= (utf8Bytes[pos + 2] & 0x3F) << 12
+        //     unicode |= (utf8Bytes[pos + 3] & 0x3F) << 6
+        //     unicode |= (utf8Bytes[pos + 4] & 0x3F)
+        //     unicodeStr += String.fromCharCode(unicode)
+        //     pos += 5
+        // }
+        else if ((flag & 0xf0) === 0xf0) {
+            unicode = (utf8Bytes[pos] & 0xf) << 18
+            unicode |= (utf8Bytes[pos + 1] & 0x3f) << 12
+            unicode |= (utf8Bytes[pos + 2] & 0x3f) << 6
+            unicode |= utf8Bytes[pos + 3] & 0x3f
+            unicodeStr += String.fromCharCode(unicode)
+            pos += 4
+        } else if ((flag & 0xe0) === 0xe0) {
+            unicode = (utf8Bytes[pos] & 0x1f) << 12
+            unicode |= (utf8Bytes[pos + 1] & 0x3f) << 6
+            unicode |= utf8Bytes[pos + 2] & 0x3f
+            unicodeStr += String.fromCharCode(unicode)
+            pos += 3
+        } else if ((flag & 0xc0) === 0xc0) {
+            //110
+            unicode = (utf8Bytes[pos] & 0x3f) << 6
+            unicode |= utf8Bytes[pos + 1] & 0x3f
+            unicodeStr += String.fromCharCode(unicode)
+            pos += 2
+        } else {
+            unicodeStr += String.fromCharCode(utf8Bytes[pos])
+            pos += 1
+        }
+    }
+    return unicodeStr
+}
+const strToUtf8Bytes = str => {
+    let bytes = []
+    for (let i = 0; i < str.length; ++i) {
+        let code = str.charCodeAt(i)
+        if (code >= 0x10000 && code <= 0x10ffff) {
+            bytes.push((code >> 18) | 0xf0) // 第一个字节
+            bytes.push(((code >> 12) & 0x3f) | 0x80)
+            bytes.push(((code >> 6) & 0x3f) | 0x80)
+            bytes.push((code & 0x3f) | 0x80)
+        } else if (code >= 0x800 && code <= 0xffff) {
+            bytes.push((code >> 12) | 0xe0)
+            bytes.push(((code >> 6) & 0x3f) | 0x80)
+            bytes.push((code & 0x3f) | 0x80)
+        } else if (code >= 0x80 && code <= 0x7ff) {
+            bytes.push((code >> 6) | 0xc0)
+            bytes.push((code & 0x3f) | 0x80)
+        } else {
+            bytes.push(code)
+        }
+    }
+    return bytes
+}
+
+module.exports = {
+    onBluetoothAdapterStateChange,
+    openBluetoothAdapter,
+
+    onBluetoothDeviceFound,
+    startBluetoothDevicesDiscovery,
+    stopBluetoothDevicesDiscovery,
+
+    onBLEConnectionStateChange,
+    createBLEConnection,
+    closeBLEConnection,
+
+    onBLECharacteristicValueChange,
+    writeBLECharacteristicValue,
+}

+ 518 - 0
utils/ecBLE/ecBLEApp.js

@@ -0,0 +1,518 @@
+const logEnable = false
+let isAndroid = false
+let ecBluetoothAdapterStateChangeCallback = () => {}
+let ecBluetoothDeviceFoundCallback = () => {}
+let ecBLEConnectionStateChangeCallback = () => {}
+let ecBLECharacteristicValueChangeCallback = () => {}
+let ecDeviceId = ''
+let ecGattServerUUID = ''
+const ecGattServerUUIDOption1 = '0000FFF0-0000-1000-8000-00805F9B34FB'
+const ecGattServerUUIDOption2 = 'FFF0'
+let ecGattCharacteristicWriteUUID = ''
+const ecGattCharacteristicWriteUUIDOption1 = '0000FFF2-0000-1000-8000-00805F9B34FB'
+const ecGattCharacteristicWriteUUIDOption2 = 'FFF2'
+
+const log = data => {
+	if (logEnable) {
+		console.log('[eciot]:' + JSON.stringify(data))
+	}
+}
+
+const onBluetoothAdapterStateChange = cb => {
+	ecBluetoothAdapterStateChangeCallback = cb
+}
+
+const _openBluetoothAdapter = () => {
+	return new Promise(function(resolve, reject) {
+		uni.openBluetoothAdapter({
+			success(res) {
+				log(res)
+				resolve({
+					ok: true,
+					errCode: 0,
+					errMsg: ''
+				})
+			},
+			fail(res) {
+				log(res)
+				// {"errMsg":"openBluetoothAdapter:fail not available","code":10001}
+				resolve({
+					ok: false,
+					errCode: res.code,
+					errMsg: res.errMsg,
+				})
+			},
+		})
+	})
+}
+
+uni.onBluetoothAdapterStateChange(res => {
+	log(res)
+	// {"discovering":true,"available":true}
+	if (!res.available) {
+		ecBluetoothAdapterStateChangeCallback({
+			ok: false,
+			errCode: 30005,
+			errMsg: '蓝牙适配器不可用',
+		})
+	}
+})
+
+const openBluetoothAdapter = async () => {
+	await _openBluetoothAdapter()
+	const systemInfo = uni.getSystemInfoSync()
+	log(systemInfo)
+	if (systemInfo.platform.toLowerCase() === 'android') {
+		isAndroid = true
+	}
+	const systemSetting = uni.getSystemSetting()
+	log(systemSetting)
+	const appAuthorizeSetting = uni.getAppAuthorizeSetting()
+	log(appAuthorizeSetting) 
+	if (!systemSetting.bluetoothEnabled) {
+	    ecBluetoothAdapterStateChangeCallback({
+	        ok: false,
+	        errCode: 30001,
+	        errMsg: '请打开系统蓝牙开关',
+	    })
+	    return
+	}
+	if (isAndroid && !systemSetting.locationEnabled) {
+	    ecBluetoothAdapterStateChangeCallback({
+	        ok: false,
+	        errCode: 30002,
+	        errMsg: '请打开系统定位开关',
+	    })
+	    return
+	}	
+	if (isAndroid && (appAuthorizeSetting.locationAuthorized!=='authorized')) {
+	    ecBluetoothAdapterStateChangeCallback({
+	        ok: false,
+	        errCode: 30003,
+	        errMsg: '请打开应用定位权限,允许应用使用您的位置信息', 
+	    })
+	    return
+	}
+	const openRes = await _openBluetoothAdapter()
+	ecBluetoothAdapterStateChangeCallback(openRes)
+}
+
+uni.onBluetoothDeviceFound(res => {
+	// log(res)
+	const device = res.devices[0]
+	const name = device.name ? device.name : device.localName
+	if (!name) {
+		return
+	} 
+	let id = device.deviceId
+	let rssi = device.RSSI
+	ecBluetoothDeviceFoundCallback({
+		id,
+		name,
+		rssi
+	})
+})
+
+const onBluetoothDeviceFound = cb => {
+	ecBluetoothDeviceFoundCallback = cb
+}
+
+const startBluetoothDevicesDiscovery = () => {
+	uni.startBluetoothDevicesDiscovery({
+		//services: [ecServerId],
+		allowDuplicatesKey: true,
+		// powerLevel: 'high',
+		complete(res) {
+			log(res)
+		},
+	})
+}
+
+const stopBluetoothDevicesDiscovery = () => {
+	uni.stopBluetoothDevicesDiscovery({
+		complete(res) {
+			log(res)
+		},
+	})
+}
+
+const onBLEConnectionStateChange = cb => {
+	ecBLEConnectionStateChangeCallback = cb
+}
+
+const _createBLEConnection = () => {
+	return new Promise(function(resolve, reject) {
+		uni.createBLEConnection({
+			deviceId: ecDeviceId,
+			success(res) {
+				log(res)
+				resolve({
+					ok: true,
+					errCode: 0,
+					errMsg: ''
+				})
+			},
+			fail(res) {
+				log(res)
+				resolve({
+					ok: false,
+					errCode: res.code,
+					errMsg: res.errMsg,
+				})
+			},
+		})
+	})
+}
+
+const getBLEDeviceServices = () => {
+	return new Promise(function(resolve, reject) {
+		setTimeout(()=>{
+			uni.getBLEDeviceServices({
+				deviceId: ecDeviceId,
+				success(res) {
+					log(res)
+					resolve({
+						ok: true,
+						errCode: 0,
+						errMsg: '',
+						services: res.services,
+					})
+				},
+				fail(res) {
+					log(res)
+					resolve({
+						ok: false,
+						errCode: res.code,
+						errMsg: res.errMsg
+					})
+				},
+			})
+		},800)
+		
+	})
+}
+
+const getBLEDeviceCharacteristics = serviceId => {
+	return new Promise(function(resolve, reject) {
+		uni.getBLEDeviceCharacteristics({
+			deviceId: ecDeviceId,
+			serviceId,
+			success(res) {
+				log(res)
+				resolve({
+					ok: true,
+					errCode: 0,
+					errMsg: '',
+					characteristics: res.characteristics,
+				})
+			},
+			fail(res) {
+				log(res)
+				resolve({
+					ok: false,
+					errCode: res.code,
+					errMsg: res.errMsg
+				})
+			},
+		})
+	})
+}
+
+const notifyBLECharacteristicValueChange = (serviceId, characteristicId) => {
+	return new Promise(function(resolve, reject) {
+		uni.notifyBLECharacteristicValueChange({
+			state: true,
+			deviceId: ecDeviceId,
+			serviceId,
+			characteristicId,
+			success(res) {
+				log(res)
+				resolve({
+					ok: true,
+					errCode: 0,
+					errMsg: ''
+				})
+			},
+			fail(res) {
+				log(res)
+				resolve({
+					ok: false,
+					errCode: res.code,
+					errMsg: res.errMsg
+				})
+			},
+		})
+	})
+}
+
+const setBLEMTU = mtu => {
+	return new Promise(function(resolve, reject) {
+		setTimeout(()=>{
+			uni.setBLEMTU({
+				deviceId: ecDeviceId,
+				mtu,
+				success(res) {
+					log(res)
+					resolve({
+						ok: true,
+						errCode: 0,
+						errMsg: ''
+					})
+				},
+				fail(res) {
+					log(res)
+					resolve({
+						ok: false,
+						errCode: res.code,
+						errMsg: res.errMsg
+					})
+				},
+			})
+		},500)
+	})
+}
+
+uni.onBLEConnectionStateChange(async res => {
+	log(res)
+	// {"deviceId":"EC:22:05:13:78:49","connected":true}
+	if (res.connected) {
+		const servicesResult = await getBLEDeviceServices()
+		if (!servicesResult.ok) {
+			ecBLEConnectionStateChangeCallback(servicesResult)
+			closeBLEConnection()
+			return
+		}
+		for (const service of servicesResult.services) {
+			if ((service.uuid.toUpperCase() === ecGattServerUUIDOption1) ||
+				(service.uuid.toUpperCase() === ecGattServerUUIDOption2)) {
+				ecGattServerUUID = service.uuid
+			}
+			const characteristicsResult = await getBLEDeviceCharacteristics(
+				service.uuid
+			)
+			if (!characteristicsResult.ok) {
+				ecBLEConnectionStateChangeCallback(characteristicsResult)
+				closeBLEConnection()
+				return
+			}
+			for (const characteristic of characteristicsResult.characteristics) {
+				if (
+					characteristic.properties &&
+					characteristic.properties.notify
+				) {
+					const notifyResult =
+						await notifyBLECharacteristicValueChange(
+							service.uuid,
+							characteristic.uuid
+						)
+					if (!notifyResult.ok) {
+						ecBLEConnectionStateChangeCallback({
+							ok: false,
+							errCode: 30000,
+							errMsg: 'notify error',
+						})
+						closeBLEConnection()
+						return
+					}
+				}
+
+				if ((characteristic.uuid.toUpperCase() ===
+						ecGattCharacteristicWriteUUIDOption1) ||
+					(characteristic.uuid.toUpperCase() ===
+						ecGattCharacteristicWriteUUIDOption2)) {
+					ecGattCharacteristicWriteUUID = characteristic.uuid
+				}
+			}
+		}
+		if (isAndroid) {
+			await setBLEMTU(247)
+		}
+		ecBLEConnectionStateChangeCallback({
+			ok: true,
+			errCode: 0,
+			errMsg: '',
+		})
+	} else {
+		ecBLEConnectionStateChangeCallback({
+			ok: false,
+			errCode: 0,
+			errMsg: 'disconnect',
+		})
+	}
+})
+	
+const createBLEConnection = async id => {
+	ecDeviceId = id
+	const res = await _createBLEConnection()
+	if (!res.ok) {
+		ecBLEConnectionStateChangeCallback(res)
+	}
+}
+
+const closeBLEConnection = () => {
+	uni.closeBLEConnection({
+		deviceId: ecDeviceId,
+		complete(res) {
+			log(res)
+		},
+	})
+}
+
+uni.onBLECharacteristicValueChange(res => {
+	log(res)
+	let x = new Uint8Array(res.value)
+	log(x)
+	let str = utf8BytesToStr(x)
+	let strHex = ''
+	for (let i = 0; i < x.length; i++) {
+		strHex = strHex + x[i].toString(16).padStart(2, '0').toUpperCase()
+	}
+	log(str)
+	log(strHex)
+	ecBLECharacteristicValueChangeCallback(str, strHex)
+})
+	
+const onBLECharacteristicValueChange = cb => {
+	ecBLECharacteristicValueChangeCallback = cb
+}
+
+const _writeBLECharacteristicValue = buffer => {
+	return new Promise(function(resolve, reject) {
+		uni.writeBLECharacteristicValue({
+			deviceId: ecDeviceId,
+			serviceId: ecGattServerUUID,
+			characteristicId: ecGattCharacteristicWriteUUID,
+			value: buffer,
+			// writeType: 'writeNoResponse',
+			success(res) {
+				log(res)
+				resolve({
+					ok: true,
+					errCode: 0,
+					errMsg: ''
+				})
+			},
+			fail(res) {
+				log(res)
+				resolve({
+					ok: false,
+					errCode: res.code,
+					errMsg: res.errMsg
+				})
+			},
+		})
+	})
+}
+const writeBLECharacteristicValue = async (str, isHex) => {
+	if (str.length === 0)
+		return {
+			ok: false,
+			errCode: 30000,
+			errMsg: 'data is null'
+		}
+	let buffer
+	if (isHex) {
+		buffer = new ArrayBuffer(str.length / 2)
+		let x = new Uint8Array(buffer)
+		for (let i = 0; i < x.length; i++) {
+			x[i] = parseInt(str.substr(2 * i, 2), 16)
+		}
+	} else {
+		buffer = new Uint8Array(strToUtf8Bytes(str)).buffer
+	}
+
+	return await _writeBLECharacteristicValue(buffer)
+}
+
+const utf8BytesToStr = utf8Bytes => {
+	let unicodeStr = ''
+	for (let pos = 0; pos < utf8Bytes.length;) {
+		let flag = utf8Bytes[pos]
+		let unicode = 0
+		if (flag >>> 7 === 0) {
+			unicodeStr += String.fromCharCode(utf8Bytes[pos])
+			pos += 1
+		}
+		// else if ((flag & 0xFC) === 0xFC) {
+		//     unicode = (utf8Bytes[pos] & 0x3) << 30
+		//     unicode |= (utf8Bytes[pos + 1] & 0x3F) << 24
+		//     unicode |= (utf8Bytes[pos + 2] & 0x3F) << 18
+		//     unicode |= (utf8Bytes[pos + 3] & 0x3F) << 12
+		//     unicode |= (utf8Bytes[pos + 4] & 0x3F) << 6
+		//     unicode |= (utf8Bytes[pos + 5] & 0x3F)
+		//     unicodeStr += String.fromCharCode(unicode)
+		//     pos += 6
+		// }
+		// else if ((flag & 0xF8) === 0xF8) {
+		//     unicode = (utf8Bytes[pos] & 0x7) << 24
+		//     unicode |= (utf8Bytes[pos + 1] & 0x3F) << 18
+		//     unicode |= (utf8Bytes[pos + 2] & 0x3F) << 12
+		//     unicode |= (utf8Bytes[pos + 3] & 0x3F) << 6
+		//     unicode |= (utf8Bytes[pos + 4] & 0x3F)
+		//     unicodeStr += String.fromCharCode(unicode)
+		//     pos += 5
+		// }
+		else if ((flag & 0xf0) === 0xf0) {
+			unicode = (utf8Bytes[pos] & 0xf) << 18
+			unicode |= (utf8Bytes[pos + 1] & 0x3f) << 12
+			unicode |= (utf8Bytes[pos + 2] & 0x3f) << 6
+			unicode |= utf8Bytes[pos + 3] & 0x3f
+			unicodeStr += String.fromCharCode(unicode)
+			pos += 4
+		} else if ((flag & 0xe0) === 0xe0) {
+			unicode = (utf8Bytes[pos] & 0x1f) << 12
+			unicode |= (utf8Bytes[pos + 1] & 0x3f) << 6
+			unicode |= utf8Bytes[pos + 2] & 0x3f
+			unicodeStr += String.fromCharCode(unicode)
+			pos += 3
+		} else if ((flag & 0xc0) === 0xc0) {
+			//110
+			unicode = (utf8Bytes[pos] & 0x3f) << 6
+			unicode |= utf8Bytes[pos + 1] & 0x3f
+			unicodeStr += String.fromCharCode(unicode)
+			pos += 2
+		} else {
+			unicodeStr += String.fromCharCode(utf8Bytes[pos])
+			pos += 1
+		}
+	}
+	return unicodeStr
+}
+const strToUtf8Bytes = str => {
+	let bytes = []
+	for (let i = 0; i < str.length; ++i) {
+		let code = str.charCodeAt(i)
+		if (code >= 0x10000 && code <= 0x10ffff) {
+			bytes.push((code >> 18) | 0xf0) // 第一个字节
+			bytes.push(((code >> 12) & 0x3f) | 0x80)
+			bytes.push(((code >> 6) & 0x3f) | 0x80)
+			bytes.push((code & 0x3f) | 0x80)
+		} else if (code >= 0x800 && code <= 0xffff) {
+			bytes.push((code >> 12) | 0xe0)
+			bytes.push(((code >> 6) & 0x3f) | 0x80)
+			bytes.push((code & 0x3f) | 0x80)
+		} else if (code >= 0x80 && code <= 0x7ff) {
+			bytes.push((code >> 6) | 0xc0)
+			bytes.push((code & 0x3f) | 0x80)
+		} else {
+			bytes.push(code)
+		}
+	}
+	return bytes
+}
+
+export default {
+	onBluetoothAdapterStateChange,
+	openBluetoothAdapter,
+
+	onBluetoothDeviceFound,
+	startBluetoothDevicesDiscovery,
+	stopBluetoothDevicesDiscovery,
+
+	onBLEConnectionStateChange,
+	createBLEConnection,
+	closeBLEConnection,
+
+	onBLECharacteristicValueChange,
+	writeBLECharacteristicValue,
+}

Plik diff jest za duży
+ 573 - 0
utils/ecBLE/ecBLEJD.js


Plik diff jest za duży
+ 514 - 0
utils/ecBLE/ecBLEWX.js


+ 35 - 0
utils/ecBLE/regenerator/runtime-module.js

@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+// This method of obtaining a reference to the global object needs to be
+// kept identical to the way it is obtained in runtime.js
+var g = (function() { return this })() || Function("return this")();
+
+// Use `getOwnPropertyNames` because not all browsers support calling
+// `hasOwnProperty` on the global `self` object in a worker. See #183.
+var hadRuntime = g.regeneratorRuntime &&
+  Object.getOwnPropertyNames(g).indexOf("regeneratorRuntime") >= 0;
+
+// Save the old regeneratorRuntime in case it needs to be restored later.
+var oldRuntime = hadRuntime && g.regeneratorRuntime;
+
+// Force reevalutation of runtime.js.
+g.regeneratorRuntime = undefined;
+
+module.exports = require("./runtime");
+
+if (hadRuntime) {
+  // Restore the original runtime.
+  g.regeneratorRuntime = oldRuntime;
+} else {
+  // Remove the global property added by runtime.js.
+  try {
+    delete g.regeneratorRuntime;
+  } catch(e) {
+    g.regeneratorRuntime = undefined;
+  }
+}

+ 727 - 0
utils/ecBLE/regenerator/runtime.js

@@ -0,0 +1,727 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+!(function(global) {
+  "use strict";
+
+  var Op = Object.prototype;
+  var hasOwn = Op.hasOwnProperty;
+  var undefined; // More compressible than void 0.
+  var $Symbol = typeof Symbol === "function" ? Symbol : {};
+  var iteratorSymbol = $Symbol.iterator || "@@iterator";
+  var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
+  var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
+
+  var inModule = typeof module === "object";
+  var runtime = global.regeneratorRuntime;
+  if (runtime) {
+    if (inModule) {
+      // If regeneratorRuntime is defined globally and we're in a module,
+      // make the exports object identical to regeneratorRuntime.
+      module.exports = runtime;
+    }
+    // Don't bother evaluating the rest of this file if the runtime was
+    // already defined globally.
+    return;
+  }
+
+  // Define the runtime globally (as expected by generated code) as either
+  // module.exports (if we're in a module) or a new, empty object.
+  runtime = global.regeneratorRuntime = inModule ? module.exports : {};
+
+  function wrap(innerFn, outerFn, self, tryLocsList) {
+    // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
+    var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
+    var generator = Object.create(protoGenerator.prototype);
+    var context = new Context(tryLocsList || []);
+
+    // The ._invoke method unifies the implementations of the .next,
+    // .throw, and .return methods.
+    generator._invoke = makeInvokeMethod(innerFn, self, context);
+
+    return generator;
+  }
+  runtime.wrap = wrap;
+
+  // Try/catch helper to minimize deoptimizations. Returns a completion
+  // record like context.tryEntries[i].completion. This interface could
+  // have been (and was previously) designed to take a closure to be
+  // invoked without arguments, but in all the cases we care about we
+  // already have an existing method we want to call, so there's no need
+  // to create a new function object. We can even get away with assuming
+  // the method takes exactly one argument, since that happens to be true
+  // in every case, so we don't have to touch the arguments object. The
+  // only additional allocation required is the completion record, which
+  // has a stable shape and so hopefully should be cheap to allocate.
+  function tryCatch(fn, obj, arg) {
+    try {
+      return { type: "normal", arg: fn.call(obj, arg) };
+    } catch (err) {
+      return { type: "throw", arg: err };
+    }
+  }
+
+  var GenStateSuspendedStart = "suspendedStart";
+  var GenStateSuspendedYield = "suspendedYield";
+  var GenStateExecuting = "executing";
+  var GenStateCompleted = "completed";
+
+  // Returning this object from the innerFn has the same effect as
+  // breaking out of the dispatch switch statement.
+  var ContinueSentinel = {};
+
+  // Dummy constructor functions that we use as the .constructor and
+  // .constructor.prototype properties for functions that return Generator
+  // objects. For full spec compliance, you may wish to configure your
+  // minifier not to mangle the names of these two functions.
+  function Generator() {}
+  function GeneratorFunction() {}
+  function GeneratorFunctionPrototype() {}
+
+  // This is a polyfill for %IteratorPrototype% for environments that
+  // don't natively support it.
+  var IteratorPrototype = {};
+  IteratorPrototype[iteratorSymbol] = function () {
+    return this;
+  };
+
+  var getProto = Object.getPrototypeOf;
+  var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
+  if (NativeIteratorPrototype &&
+      NativeIteratorPrototype !== Op &&
+      hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
+    // This environment has a native %IteratorPrototype%; use it instead
+    // of the polyfill.
+    IteratorPrototype = NativeIteratorPrototype;
+  }
+
+  var Gp = GeneratorFunctionPrototype.prototype =
+    Generator.prototype = Object.create(IteratorPrototype);
+  GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
+  GeneratorFunctionPrototype.constructor = GeneratorFunction;
+  GeneratorFunctionPrototype[toStringTagSymbol] =
+    GeneratorFunction.displayName = "GeneratorFunction";
+
+  // Helper for defining the .next, .throw, and .return methods of the
+  // Iterator interface in terms of a single ._invoke method.
+  function defineIteratorMethods(prototype) {
+    ["next", "throw", "return"].forEach(function(method) {
+      prototype[method] = function(arg) {
+        return this._invoke(method, arg);
+      };
+    });
+  }
+
+  runtime.isGeneratorFunction = function(genFun) {
+    var ctor = typeof genFun === "function" && genFun.constructor;
+    return ctor
+      ? ctor === GeneratorFunction ||
+        // For the native GeneratorFunction constructor, the best we can
+        // do is to check its .name property.
+        (ctor.displayName || ctor.name) === "GeneratorFunction"
+      : false;
+  };
+
+  runtime.mark = function(genFun) {
+    if (Object.setPrototypeOf) {
+      Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
+    } else {
+      genFun.__proto__ = GeneratorFunctionPrototype;
+      if (!(toStringTagSymbol in genFun)) {
+        genFun[toStringTagSymbol] = "GeneratorFunction";
+      }
+    }
+    genFun.prototype = Object.create(Gp);
+    return genFun;
+  };
+
+  // Within the body of any async function, `await x` is transformed to
+  // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
+  // `hasOwn.call(value, "__await")` to determine if the yielded value is
+  // meant to be awaited.
+  runtime.awrap = function(arg) {
+    return { __await: arg };
+  };
+
+  function AsyncIterator(generator) {
+    function invoke(method, arg, resolve, reject) {
+      var record = tryCatch(generator[method], generator, arg);
+      if (record.type === "throw") {
+        reject(record.arg);
+      } else {
+        var result = record.arg;
+        var value = result.value;
+        if (value &&
+            typeof value === "object" &&
+            hasOwn.call(value, "__await")) {
+          return Promise.resolve(value.__await).then(function(value) {
+            invoke("next", value, resolve, reject);
+          }, function(err) {
+            invoke("throw", err, resolve, reject);
+          });
+        }
+
+        return Promise.resolve(value).then(function(unwrapped) {
+          // When a yielded Promise is resolved, its final value becomes
+          // the .value of the Promise<{value,done}> result for the
+          // current iteration. If the Promise is rejected, however, the
+          // result for this iteration will be rejected with the same
+          // reason. Note that rejections of yielded Promises are not
+          // thrown back into the generator function, as is the case
+          // when an awaited Promise is rejected. This difference in
+          // behavior between yield and await is important, because it
+          // allows the consumer to decide what to do with the yielded
+          // rejection (swallow it and continue, manually .throw it back
+          // into the generator, abandon iteration, whatever). With
+          // await, by contrast, there is no opportunity to examine the
+          // rejection reason outside the generator function, so the
+          // only option is to throw it from the await expression, and
+          // let the generator function handle the exception.
+          result.value = unwrapped;
+          resolve(result);
+        }, reject);
+      }
+    }
+
+    var previousPromise;
+
+    function enqueue(method, arg) {
+      function callInvokeWithMethodAndArg() {
+        return new Promise(function(resolve, reject) {
+          invoke(method, arg, resolve, reject);
+        });
+      }
+
+      return previousPromise =
+        // If enqueue has been called before, then we want to wait until
+        // all previous Promises have been resolved before calling invoke,
+        // so that results are always delivered in the correct order. If
+        // enqueue has not been called before, then it is important to
+        // call invoke immediately, without waiting on a callback to fire,
+        // so that the async generator function has the opportunity to do
+        // any necessary setup in a predictable way. This predictability
+        // is why the Promise constructor synchronously invokes its
+        // executor callback, and why async functions synchronously
+        // execute code before the first await. Since we implement simple
+        // async functions in terms of async generators, it is especially
+        // important to get this right, even though it requires care.
+        previousPromise ? previousPromise.then(
+          callInvokeWithMethodAndArg,
+          // Avoid propagating failures to Promises returned by later
+          // invocations of the iterator.
+          callInvokeWithMethodAndArg
+        ) : callInvokeWithMethodAndArg();
+    }
+
+    // Define the unified helper method that is used to implement .next,
+    // .throw, and .return (see defineIteratorMethods).
+    this._invoke = enqueue;
+  }
+
+  defineIteratorMethods(AsyncIterator.prototype);
+  AsyncIterator.prototype[asyncIteratorSymbol] = function () {
+    return this;
+  };
+  runtime.AsyncIterator = AsyncIterator;
+
+  // Note that simple async functions are implemented on top of
+  // AsyncIterator objects; they just return a Promise for the value of
+  // the final result produced by the iterator.
+  runtime.async = function(innerFn, outerFn, self, tryLocsList) {
+    var iter = new AsyncIterator(
+      wrap(innerFn, outerFn, self, tryLocsList)
+    );
+
+    return runtime.isGeneratorFunction(outerFn)
+      ? iter // If outerFn is a generator, return the full iterator.
+      : iter.next().then(function(result) {
+          return result.done ? result.value : iter.next();
+        });
+  };
+
+  function makeInvokeMethod(innerFn, self, context) {
+    var state = GenStateSuspendedStart;
+
+    return function invoke(method, arg) {
+      if (state === GenStateExecuting) {
+        throw new Error("Generator is already running");
+      }
+
+      if (state === GenStateCompleted) {
+        if (method === "throw") {
+          throw arg;
+        }
+
+        // Be forgiving, per 25.3.3.3.3 of the spec:
+        // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
+        return doneResult();
+      }
+
+      context.method = method;
+      context.arg = arg;
+
+      while (true) {
+        var delegate = context.delegate;
+        if (delegate) {
+          var delegateResult = maybeInvokeDelegate(delegate, context);
+          if (delegateResult) {
+            if (delegateResult === ContinueSentinel) continue;
+            return delegateResult;
+          }
+        }
+
+        if (context.method === "next") {
+          // Setting context._sent for legacy support of Babel's
+          // function.sent implementation.
+          context.sent = context._sent = context.arg;
+
+        } else if (context.method === "throw") {
+          if (state === GenStateSuspendedStart) {
+            state = GenStateCompleted;
+            throw context.arg;
+          }
+
+          context.dispatchException(context.arg);
+
+        } else if (context.method === "return") {
+          context.abrupt("return", context.arg);
+        }
+
+        state = GenStateExecuting;
+
+        var record = tryCatch(innerFn, self, context);
+        if (record.type === "normal") {
+          // If an exception is thrown from innerFn, we leave state ===
+          // GenStateExecuting and loop back for another invocation.
+          state = context.done
+            ? GenStateCompleted
+            : GenStateSuspendedYield;
+
+          if (record.arg === ContinueSentinel) {
+            continue;
+          }
+
+          return {
+            value: record.arg,
+            done: context.done
+          };
+
+        } else if (record.type === "throw") {
+          state = GenStateCompleted;
+          // Dispatch the exception by looping back around to the
+          // context.dispatchException(context.arg) call above.
+          context.method = "throw";
+          context.arg = record.arg;
+        }
+      }
+    };
+  }
+
+  // Call delegate.iterator[context.method](context.arg) and handle the
+  // result, either by returning a { value, done } result from the
+  // delegate iterator, or by modifying context.method and context.arg,
+  // setting context.delegate to null, and returning the ContinueSentinel.
+  function maybeInvokeDelegate(delegate, context) {
+    var method = delegate.iterator[context.method];
+    if (method === undefined) {
+      // A .throw or .return when the delegate iterator has no .throw
+      // method always terminates the yield* loop.
+      context.delegate = null;
+
+      if (context.method === "throw") {
+        if (delegate.iterator.return) {
+          // If the delegate iterator has a return method, give it a
+          // chance to clean up.
+          context.method = "return";
+          context.arg = undefined;
+          maybeInvokeDelegate(delegate, context);
+
+          if (context.method === "throw") {
+            // If maybeInvokeDelegate(context) changed context.method from
+            // "return" to "throw", let that override the TypeError below.
+            return ContinueSentinel;
+          }
+        }
+
+        context.method = "throw";
+        context.arg = new TypeError(
+          "The iterator does not provide a 'throw' method");
+      }
+
+      return ContinueSentinel;
+    }
+
+    var record = tryCatch(method, delegate.iterator, context.arg);
+
+    if (record.type === "throw") {
+      context.method = "throw";
+      context.arg = record.arg;
+      context.delegate = null;
+      return ContinueSentinel;
+    }
+
+    var info = record.arg;
+
+    if (! info) {
+      context.method = "throw";
+      context.arg = new TypeError("iterator result is not an object");
+      context.delegate = null;
+      return ContinueSentinel;
+    }
+
+    if (info.done) {
+      // Assign the result of the finished delegate to the temporary
+      // variable specified by delegate.resultName (see delegateYield).
+      context[delegate.resultName] = info.value;
+
+      // Resume execution at the desired location (see delegateYield).
+      context.next = delegate.nextLoc;
+
+      // If context.method was "throw" but the delegate handled the
+      // exception, let the outer generator proceed normally. If
+      // context.method was "next", forget context.arg since it has been
+      // "consumed" by the delegate iterator. If context.method was
+      // "return", allow the original .return call to continue in the
+      // outer generator.
+      if (context.method !== "return") {
+        context.method = "next";
+        context.arg = undefined;
+      }
+
+    } else {
+      // Re-yield the result returned by the delegate method.
+      return info;
+    }
+
+    // The delegate iterator is finished, so forget it and continue with
+    // the outer generator.
+    context.delegate = null;
+    return ContinueSentinel;
+  }
+
+  // Define Generator.prototype.{next,throw,return} in terms of the
+  // unified ._invoke helper method.
+  defineIteratorMethods(Gp);
+
+  Gp[toStringTagSymbol] = "Generator";
+
+  // A Generator should always return itself as the iterator object when the
+  // @@iterator function is called on it. Some browsers' implementations of the
+  // iterator prototype chain incorrectly implement this, causing the Generator
+  // object to not be returned from this call. This ensures that doesn't happen.
+  // See https://github.com/facebook/regenerator/issues/274 for more details.
+  Gp[iteratorSymbol] = function() {
+    return this;
+  };
+
+  Gp.toString = function() {
+    return "[object Generator]";
+  };
+
+  function pushTryEntry(locs) {
+    var entry = { tryLoc: locs[0] };
+
+    if (1 in locs) {
+      entry.catchLoc = locs[1];
+    }
+
+    if (2 in locs) {
+      entry.finallyLoc = locs[2];
+      entry.afterLoc = locs[3];
+    }
+
+    this.tryEntries.push(entry);
+  }
+
+  function resetTryEntry(entry) {
+    var record = entry.completion || {};
+    record.type = "normal";
+    delete record.arg;
+    entry.completion = record;
+  }
+
+  function Context(tryLocsList) {
+    // The root entry object (effectively a try statement without a catch
+    // or a finally block) gives us a place to store values thrown from
+    // locations where there is no enclosing try statement.
+    this.tryEntries = [{ tryLoc: "root" }];
+    tryLocsList.forEach(pushTryEntry, this);
+    this.reset(true);
+  }
+
+  runtime.keys = function(object) {
+    var keys = [];
+    for (var key in object) {
+      keys.push(key);
+    }
+    keys.reverse();
+
+    // Rather than returning an object with a next method, we keep
+    // things simple and return the next function itself.
+    return function next() {
+      while (keys.length) {
+        var key = keys.pop();
+        if (key in object) {
+          next.value = key;
+          next.done = false;
+          return next;
+        }
+      }
+
+      // To avoid creating an additional object, we just hang the .value
+      // and .done properties off the next function object itself. This
+      // also ensures that the minifier will not anonymize the function.
+      next.done = true;
+      return next;
+    };
+  };
+
+  function values(iterable) {
+    if (iterable) {
+      var iteratorMethod = iterable[iteratorSymbol];
+      if (iteratorMethod) {
+        return iteratorMethod.call(iterable);
+      }
+
+      if (typeof iterable.next === "function") {
+        return iterable;
+      }
+
+      if (!isNaN(iterable.length)) {
+        var i = -1, next = function next() {
+          while (++i < iterable.length) {
+            if (hasOwn.call(iterable, i)) {
+              next.value = iterable[i];
+              next.done = false;
+              return next;
+            }
+          }
+
+          next.value = undefined;
+          next.done = true;
+
+          return next;
+        };
+
+        return next.next = next;
+      }
+    }
+
+    // Return an iterator with no values.
+    return { next: doneResult };
+  }
+  runtime.values = values;
+
+  function doneResult() {
+    return { value: undefined, done: true };
+  }
+
+  Context.prototype = {
+    constructor: Context,
+
+    reset: function(skipTempReset) {
+      this.prev = 0;
+      this.next = 0;
+      // Resetting context._sent for legacy support of Babel's
+      // function.sent implementation.
+      this.sent = this._sent = undefined;
+      this.done = false;
+      this.delegate = null;
+
+      this.method = "next";
+      this.arg = undefined;
+
+      this.tryEntries.forEach(resetTryEntry);
+
+      if (!skipTempReset) {
+        for (var name in this) {
+          // Not sure about the optimal order of these conditions:
+          if (name.charAt(0) === "t" &&
+              hasOwn.call(this, name) &&
+              !isNaN(+name.slice(1))) {
+            this[name] = undefined;
+          }
+        }
+      }
+    },
+
+    stop: function() {
+      this.done = true;
+
+      var rootEntry = this.tryEntries[0];
+      var rootRecord = rootEntry.completion;
+      if (rootRecord.type === "throw") {
+        throw rootRecord.arg;
+      }
+
+      return this.rval;
+    },
+
+    dispatchException: function(exception) {
+      if (this.done) {
+        throw exception;
+      }
+
+      var context = this;
+      function handle(loc, caught) {
+        record.type = "throw";
+        record.arg = exception;
+        context.next = loc;
+
+        if (caught) {
+          // If the dispatched exception was caught by a catch block,
+          // then let that catch block handle the exception normally.
+          context.method = "next";
+          context.arg = undefined;
+        }
+
+        return !! caught;
+      }
+
+      for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+        var entry = this.tryEntries[i];
+        var record = entry.completion;
+
+        if (entry.tryLoc === "root") {
+          // Exception thrown outside of any try block that could handle
+          // it, so set the completion value of the entire function to
+          // throw the exception.
+          return handle("end");
+        }
+
+        if (entry.tryLoc <= this.prev) {
+          var hasCatch = hasOwn.call(entry, "catchLoc");
+          var hasFinally = hasOwn.call(entry, "finallyLoc");
+
+          if (hasCatch && hasFinally) {
+            if (this.prev < entry.catchLoc) {
+              return handle(entry.catchLoc, true);
+            } else if (this.prev < entry.finallyLoc) {
+              return handle(entry.finallyLoc);
+            }
+
+          } else if (hasCatch) {
+            if (this.prev < entry.catchLoc) {
+              return handle(entry.catchLoc, true);
+            }
+
+          } else if (hasFinally) {
+            if (this.prev < entry.finallyLoc) {
+              return handle(entry.finallyLoc);
+            }
+
+          } else {
+            throw new Error("try statement without catch or finally");
+          }
+        }
+      }
+    },
+
+    abrupt: function(type, arg) {
+      for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+        var entry = this.tryEntries[i];
+        if (entry.tryLoc <= this.prev &&
+            hasOwn.call(entry, "finallyLoc") &&
+            this.prev < entry.finallyLoc) {
+          var finallyEntry = entry;
+          break;
+        }
+      }
+
+      if (finallyEntry &&
+          (type === "break" ||
+           type === "continue") &&
+          finallyEntry.tryLoc <= arg &&
+          arg <= finallyEntry.finallyLoc) {
+        // Ignore the finally entry if control is not jumping to a
+        // location outside the try/catch block.
+        finallyEntry = null;
+      }
+
+      var record = finallyEntry ? finallyEntry.completion : {};
+      record.type = type;
+      record.arg = arg;
+
+      if (finallyEntry) {
+        this.method = "next";
+        this.next = finallyEntry.finallyLoc;
+        return ContinueSentinel;
+      }
+
+      return this.complete(record);
+    },
+
+    complete: function(record, afterLoc) {
+      if (record.type === "throw") {
+        throw record.arg;
+      }
+
+      if (record.type === "break" ||
+          record.type === "continue") {
+        this.next = record.arg;
+      } else if (record.type === "return") {
+        this.rval = this.arg = record.arg;
+        this.method = "return";
+        this.next = "end";
+      } else if (record.type === "normal" && afterLoc) {
+        this.next = afterLoc;
+      }
+
+      return ContinueSentinel;
+    },
+
+    finish: function(finallyLoc) {
+      for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+        var entry = this.tryEntries[i];
+        if (entry.finallyLoc === finallyLoc) {
+          this.complete(entry.completion, entry.afterLoc);
+          resetTryEntry(entry);
+          return ContinueSentinel;
+        }
+      }
+    },
+
+    "catch": function(tryLoc) {
+      for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+        var entry = this.tryEntries[i];
+        if (entry.tryLoc === tryLoc) {
+          var record = entry.completion;
+          if (record.type === "throw") {
+            var thrown = record.arg;
+            resetTryEntry(entry);
+          }
+          return thrown;
+        }
+      }
+
+      // The context.catch method must only be called with a location
+      // argument that corresponds to a known catch block.
+      throw new Error("illegal catch attempt");
+    },
+
+    delegateYield: function(iterable, resultName, nextLoc) {
+      this.delegate = {
+        iterator: values(iterable),
+        resultName: resultName,
+        nextLoc: nextLoc
+      };
+
+      if (this.method === "next") {
+        // Deliberately forget the last sent value so that we don't
+        // accidentally pass it on to the delegate.
+        this.arg = undefined;
+      }
+
+      return ContinueSentinel;
+    }
+  };
+})(
+  // In sloppy mode, unbound `this` refers to the global object, fallback to
+  // Function constructor if we're in global strict mode. That is sadly a form
+  // of indirect eval which violates Content Security Policy.
+  (function() { return this })() || Function("return this")()
+);

+ 34 - 0
utils/ecUI.js

@@ -0,0 +1,34 @@
+const showModal = (title, content, cb) => {
+	uni.showModal({
+		title,
+		content,
+		showCancel: false,
+		complete: () => {
+			if (cb) cb();
+		}
+	});
+};
+const showLoading = (title) => {
+	uni.showLoading({
+        title,
+        mask:true,
+    });
+};
+const hideLoading = () => {
+	uni.hideLoading();
+};
+
+// #ifdef APP
+export default {
+	showModal,
+	showLoading,
+	hideLoading
+};
+// #endif
+// #ifdef MP
+module.exports = {
+	showModal,
+	showLoading,
+	hideLoading
+};
+// #endif