| @@ -0,0 +1,6 @@ | |||
| //app.js | |||
| App({ | |||
| onLaunch: function () { | |||
| }, | |||
| }) | |||
| @@ -0,0 +1,20 @@ | |||
| { | |||
| "pages":[ | |||
| "pages/index/index" | |||
| ], | |||
| "window": { | |||
| "navigationBarBackgroundColor": "#0082FE", | |||
| "navigationBarTextStyle": "white", | |||
| "navigationBarTitleText": "AiFresh蓝牙连接Demo", | |||
| "backgroundColor": "#eeeeee", | |||
| "backgroundTextStyle": "light" | |||
| }, | |||
| "plugins": { | |||
| "sdkPlugin": { | |||
| "version": "1.1.0", | |||
| "provider": "wx17e93aad47cdae1a" | |||
| } | |||
| }, | |||
| "style": "v2", | |||
| "sitemapLocation": "sitemap.json" | |||
| } | |||
| @@ -0,0 +1,2 @@ | |||
| /**app.wxss**/ | |||
| @@ -0,0 +1,305 @@ | |||
| // const plugin = requirePlugin("myPlugin").AiFresh; | |||
| const {AiFresh: plugin} = requirePlugin("sdkPlugin"); | |||
| function inArray(arr, key, val) { | |||
| for (let i = 0; i < arr.length; i++) { | |||
| if (arr[i][key] === val) { | |||
| return i; | |||
| } | |||
| } | |||
| return -1; | |||
| } | |||
| // ArrayBuffer转16进度字符串示例 | |||
| function ab2hex(buffer) { | |||
| var hexArr = Array.prototype.map.call( | |||
| new Uint8Array(buffer), | |||
| function (bit) { | |||
| return ('00' + bit.toString(16)).slice(-2) | |||
| } | |||
| ) | |||
| return hexArr.join(','); | |||
| } | |||
| Page({ | |||
| data: { | |||
| devices: [ | |||
| // { | |||
| // deviceId: "02:03:04:05:06:07", | |||
| // name: "elink", | |||
| // localName: "elink", | |||
| // mac: "02:03:04:05:06:07", | |||
| // RSSI: -69, | |||
| // advertisServiceUUIDs:[ | |||
| // "FFE0","FFC0" | |||
| // ], | |||
| // analyzeDataText:"0102003241123413" | |||
| // } | |||
| ], | |||
| mac: "", | |||
| connected: false, | |||
| text: "", | |||
| unitList_all: ['g', 'ml', 'lb:oz', 'oz', 'kg', '斤', '牛奶 ml', '水 ml', '牛奶 floz', '水 floz', 'lb'], | |||
| unitList: ['g', 'ml', 'lb:oz', 'oz'], | |||
| weight: 0, | |||
| unit: 'g', | |||
| unitIndex: 0, | |||
| min: 0, | |||
| second: 0, | |||
| }, | |||
| onLoad: function () { | |||
| }, | |||
| clearDevices() { | |||
| this.setData({ | |||
| devices: [] | |||
| }) | |||
| this.closeBluetoothAdapter() | |||
| wx.nextTick(()=>{ | |||
| this.openBluetoothAdapter() | |||
| }) | |||
| }, | |||
| tare() { | |||
| console.log("去皮") | |||
| plugin.cmdTare() | |||
| }, | |||
| getUnits() { | |||
| console.log("获取设备支持的单位") | |||
| plugin.cmdGetAllUnits() | |||
| }, | |||
| countUp() { | |||
| console.log("正计时开始") | |||
| plugin.cmdTimeCountUp() | |||
| }, | |||
| countUpPause() { | |||
| console.log("正计时暂停") | |||
| plugin.cmdTimeCountUpPause(this.data.min, this.data.second) | |||
| }, | |||
| countDown() { | |||
| console.log("倒计时开始") | |||
| if (this.data.min || this.data.second) { | |||
| plugin.cmdTimeCountDown(this.data.min, this.data.second) | |||
| } else { | |||
| plugin.cmdTimeCountDown(5, 30) | |||
| } | |||
| }, | |||
| countDownPause() { | |||
| console.log("倒计时暂停") | |||
| plugin.cmdTimeCountDownPause(this.data.min, this.data.second) | |||
| }, | |||
| resetTime() { | |||
| console.log("重置时间") | |||
| plugin.cmdTimeReset() | |||
| this.setData({ | |||
| min: 0, | |||
| second: 0, | |||
| }) | |||
| }, | |||
| selectUnit(e) { | |||
| let unit = e.currentTarget.dataset.unit | |||
| let index = e.currentTarget.dataset.index | |||
| let idx = this.data.unitList_all.indexOf(unit) | |||
| plugin.cmdSwitchUnit(idx) | |||
| }, | |||
| openBluetoothAdapter() { | |||
| wx.openBluetoothAdapter({ | |||
| success: (res) => { | |||
| console.log('openBluetoothAdapter success', res) | |||
| this.startBluetoothDevicesDiscovery() | |||
| }, | |||
| fail: (res) => { | |||
| if (res.errCode === 10001) { | |||
| wx.showToast({ | |||
| title: '请打开蓝牙', | |||
| icon:"none" | |||
| }) | |||
| wx.onBluetoothAdapterStateChange(function (res) { | |||
| console.log('onBluetoothAdapterStateChange', res) | |||
| if (res.available) { | |||
| this.startBluetoothDevicesDiscovery() | |||
| } | |||
| }) | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| getBluetoothAdapterState() { | |||
| wx.getBluetoothAdapterState({ | |||
| success: (res) => { | |||
| console.log('getBluetoothAdapterState', res) | |||
| if (res.discovering) { | |||
| this.onBluetoothDeviceFound() | |||
| } else if (res.available) { | |||
| this.startBluetoothDevicesDiscovery() | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| startBluetoothDevicesDiscovery() { | |||
| if (this._discoveryStarted) { | |||
| return | |||
| } | |||
| this._discoveryStarted = true | |||
| wx.startBluetoothDevicesDiscovery({ | |||
| allowDuplicatesKey: true, | |||
| services: [ | |||
| "FFB0", | |||
| ], | |||
| success: (res) => { | |||
| console.log('startBluetoothDevicesDiscovery success', res) | |||
| this.onBluetoothDeviceFound() | |||
| }, | |||
| }) | |||
| }, | |||
| stopBluetoothDevicesDiscovery() { | |||
| wx.stopBluetoothDevicesDiscovery() | |||
| }, | |||
| onBluetoothDeviceFound() { | |||
| wx.onBluetoothDeviceFound((res) => { | |||
| res.devices.forEach(device => { | |||
| const foundDevices = this.data.devices | |||
| const idx = inArray(foundDevices, 'deviceId', device.deviceId) | |||
| const data = {} | |||
| console.log(device) | |||
| // console.log(ab2hex(device.advertisData)) | |||
| if (idx === -1) { | |||
| data[`devices[${foundDevices.length}]`] = device | |||
| } else { | |||
| data[`devices[${idx}]`] = device | |||
| } | |||
| this.setData(data) | |||
| }) | |||
| }) | |||
| }, | |||
| createBLEConnection(e) { | |||
| const ds = e.currentTarget.dataset | |||
| const deviceId = ds.deviceId | |||
| const name = ds.name | |||
| const index = ds.index | |||
| this._device = this.data.devices[index] | |||
| console.log(this._device) | |||
| wx.createBLEConnection({ | |||
| deviceId, | |||
| success: (res) => { | |||
| let buff = this._device.advertisData.slice(-6) | |||
| let arr = new Uint8Array(buff).reverse() | |||
| let mac = ab2hex(arr).replace(/,/g,":") | |||
| this.setData({ | |||
| connected: true, | |||
| name, | |||
| deviceId, | |||
| mac: mac, | |||
| }) | |||
| console.log("createBLEConnection:success") | |||
| wx.stopBluetoothDevicesDiscovery() | |||
| this.getBLEDeviceServices(deviceId) | |||
| } | |||
| }) | |||
| this.stopBluetoothDevicesDiscovery() | |||
| }, | |||
| closeBLEConnection() { | |||
| wx.closeBLEConnection({ | |||
| deviceId: this._deviceId | |||
| }) | |||
| this.setData({ | |||
| connected: false, | |||
| min: 0, | |||
| second: 0, | |||
| weight: 0, | |||
| unitList: ['g', 'ml', 'lb:oz', 'oz'], | |||
| }) | |||
| }, | |||
| getBLEDeviceServices(deviceId) { | |||
| wx.getBLEDeviceServices({ | |||
| deviceId, | |||
| success: (res) => { | |||
| console.log('getBLEDeviceServices:', res) | |||
| for (let i = 0; i < res.services.length; i++) { | |||
| if (res.services[i].isPrimary && res.services[i].uuid.indexOf('0000FFB0') != -1) { | |||
| this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| getBLEDeviceCharacteristics(deviceId, serviceId) { | |||
| this._deviceId = deviceId | |||
| this._serviceId = serviceId | |||
| this._device.serviceId = serviceId | |||
| wx.getBLEDeviceCharacteristics({ | |||
| deviceId, | |||
| serviceId, | |||
| success: (res) => { | |||
| console.log('getBLEDeviceCharacteristics success', res.characteristics) | |||
| for (let i = 0; i < res.characteristics.length; i++) { | |||
| let item = res.characteristics[i] | |||
| if (item.uuid.indexOf('0000FFB1') != -1) { | |||
| this.uuid1 = item.uuid //下发数据 | |||
| } else if (item.uuid.indexOf('0000FFB2') != -1) { | |||
| this.uuid2 = item.uuid //监听数据 | |||
| } | |||
| } | |||
| // 打开监听 | |||
| wx.notifyBLECharacteristicValueChange({ | |||
| deviceId, | |||
| serviceId, | |||
| characteristicId: this.uuid2, | |||
| state: true, | |||
| }) | |||
| plugin.initPlugin(res.characteristics, this._device) | |||
| plugin.cmdGetAllUnits() | |||
| wx.onBLECharacteristicValueChange((characteristic) => { | |||
| let bleData = plugin.parseBleData(characteristic.value) | |||
| if (bleData.status == 1) { | |||
| this.setData({ | |||
| text: bleData.data.text, | |||
| weight: bleData.data.weight, | |||
| unit: bleData.data.unit, | |||
| unitIndex: bleData.data.unitIndex | |||
| }) | |||
| } else if (bleData.status == 2) { | |||
| this.setData({ | |||
| min: bleData.data.min, | |||
| second: bleData.data.second, | |||
| }) | |||
| } else if (bleData.status == 3) {//设备支持的单位 | |||
| // 使用返回的单位索引,从本地的所有单位列表中取出支持的单位名 | |||
| let unitList = bleData.data.unitIndexs.map(i=>{ | |||
| return this.data.unitList_all[i] | |||
| }) | |||
| this.setData({ | |||
| // unitList: bleData.data.unitList, | |||
| unitList, | |||
| }) | |||
| } else { | |||
| console.log(bleData) | |||
| } | |||
| }) | |||
| }, | |||
| fail(res) { | |||
| console.error('getBLEDeviceCharacteristics', res) | |||
| } | |||
| }) | |||
| }, | |||
| writeBLECharacteristicValue(buffer, uuid, deviceId, serviceId) { | |||
| // 向蓝牙设备发送一个二进制流数据 | |||
| wx.writeBLECharacteristicValue({ | |||
| deviceId, | |||
| serviceId, | |||
| characteristicId: uuid, | |||
| value: buffer, | |||
| success(res) { | |||
| console.log('writeBLECharacteristicValue success', res) | |||
| console.log(plugin.ab2hex(buffer)) | |||
| } | |||
| }) | |||
| }, | |||
| closeBluetoothAdapter() { | |||
| wx.closeBluetoothAdapter() | |||
| this._discoveryStarted = false | |||
| }, | |||
| }); | |||
| @@ -0,0 +1,4 @@ | |||
| { | |||
| "usingComponents": {}, | |||
| "navigationBarTitleText":"AiFresh" | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| <wxs module="utils"> | |||
| module.exports.max = function(n1, n2) { | |||
| return Math.max(n1, n2) | |||
| } | |||
| module.exports.len = function(arr) { | |||
| arr = arr || [] | |||
| return arr.length | |||
| } | |||
| module.exports.fill0 = function(num) { | |||
| if (!num) { | |||
| return '00' | |||
| } | |||
| return ('00' + num).slice(-2) | |||
| } | |||
| </wxs> | |||
| <block wx:if="{{!connected}}"> | |||
| <button bindtap="openBluetoothAdapter">开始扫描</button> | |||
| <button bindtap="stopBluetoothDevicesDiscovery">停止扫描</button> | |||
| <button bindtap="closeBluetoothAdapter">结束流程</button> | |||
| <button bindtap="clearDevices">清空列表</button> | |||
| <view class="devices_summary">已发现 {{devices.length}} 个外围设备:</view> | |||
| <view class="device_list"> | |||
| <view wx:for="{{devices}}" wx:key="index" | |||
| class="device_item"> | |||
| <view style="font-size: 16px; color: #333;">{{item.name || item.localName}}</view> | |||
| <view style="font-size: 10px">信号强度: {{item.RSSI}}dBm ({{utils.max(0, item.RSSI + 100)}}%)</view> | |||
| <view style="font-size: 10px">mac地址: {{item.deviceId}}</view> | |||
| <view | |||
| class="device_item_btn" | |||
| hover-class="btn_hover" | |||
| data-device-id="{{item.deviceId}}" | |||
| data-name="{{item.name || item.localName}}" | |||
| data-index="{{index}}" | |||
| bindtap="createBLEConnection" | |||
| >连接</view> | |||
| </view> | |||
| </view> | |||
| </block> | |||
| <view class="wrap" wx:else> | |||
| <view class="stopConnect"> | |||
| <view>{{mac}}</view> | |||
| <view class="btn_connect" hover-class="btn_hover" bindtap="closeBLEConnection">断开连接</view> | |||
| </view> | |||
| <view class="weight_wrap"> | |||
| <view class="weight_title">WEIGHT</view> | |||
| <view class="weight_value"> | |||
| <view class="value_item">{{weight}}</view> | |||
| <view class="value_item">{{unit}}</view> | |||
| </view> | |||
| </view> | |||
| <view class="timer_wrap"> | |||
| <view class="timer_title">TIMER</view> | |||
| <view class="timer_value"> | |||
| <view class="value_item">{{utils.fill0(min)}}</view> | |||
| <view>:</view> | |||
| <view class="value_item">{{utils.fill0(second)}}</view> | |||
| </view> | |||
| </view> | |||
| <view class="btn_wrap"> | |||
| <button bindtap="tare">TARE(去皮)</button> | |||
| <button bindtap="getUnits">获取设备支持的单位</button> | |||
| <button bindtap="countUp">正计时开始</button> | |||
| <button bindtap="countUpPause">正计时暂停</button> | |||
| <button bindtap="countDown">倒计时开始</button> | |||
| <button bindtap="countDownPause">倒计时暂停</button> | |||
| <button bindtap="resetTime">重置时间</button> | |||
| <view class="btn_unitList_title">切换单位</view> | |||
| <view class="btn_unitList"> | |||
| <view class="btn_unit" hover-class="btn_hover" wx:for="{{unitList}}" wx:key="index" bindtap="selectUnit" data-unit="{{item}}" data-index="{{index}}">{{item}}</view> | |||
| </view> | |||
| </view> | |||
| <view class="btn_unitList_title">{{text}}</view> | |||
| </view> | |||
| @@ -0,0 +1,159 @@ | |||
| page { | |||
| color: #333; | |||
| } | |||
| .devices_summary { | |||
| margin-top: 30px; | |||
| padding: 10px; | |||
| font-size: 16px; | |||
| } | |||
| .device_list { | |||
| min-height: 300px; | |||
| margin: 0 5px; | |||
| border: 1px solid #EEE; | |||
| width: auto; | |||
| } | |||
| .device_item { | |||
| border-bottom: 1px solid #EEE; | |||
| padding: 10px; | |||
| color: #666; | |||
| position: relative; | |||
| } | |||
| .device_item_btn{ | |||
| position: absolute; | |||
| right: 12px; | |||
| top: 50%; | |||
| transform: translateY(-50%); | |||
| width: 60px; | |||
| height: 30px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| border: 1px solid #ddd; | |||
| border-radius: 3px; | |||
| } | |||
| .connected_info { | |||
| position: fixed; | |||
| bottom: 0; | |||
| width: 100%; | |||
| background-color: #F0F0F0; | |||
| padding: 10px; | |||
| padding-bottom: 20px; | |||
| margin-bottom: env(safe-area-inset-bottom); | |||
| font-size: 14px; | |||
| min-height: 100px; | |||
| box-shadow: 0px 0px 3px 0px; | |||
| } | |||
| .connected_info .operation { | |||
| position: absolute; | |||
| display: inline-block; | |||
| right: 30px; | |||
| } | |||
| .writeInputViewBg{ | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| z-index: 1000; | |||
| background-color: rgba(0,0,0,0.5); | |||
| } | |||
| .writeInputView{ | |||
| position: fixed; | |||
| top: 30%; | |||
| left: 50%; | |||
| z-index: 1001; | |||
| background-color: #fff; | |||
| width: 80vw; | |||
| height: 300rpx; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| transform: translateX(-50%); | |||
| } | |||
| .writeInput{ | |||
| margin-top: 60rpx; | |||
| width: 90%; | |||
| height: 60rpx; | |||
| border: 1rpx solid #ccc; | |||
| } | |||
| .writeInput input{ | |||
| width: 100%; | |||
| height: 100%; | |||
| box-sizing: border-box; | |||
| } | |||
| .hint{ | |||
| padding-top: 10rpx; | |||
| font-size: 24rpx; | |||
| color: #999; | |||
| } | |||
| .btns{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 60%; | |||
| margin-top: 50rpx; | |||
| } | |||
| .stopConnect{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| padding: 10px; | |||
| } | |||
| .btn_connect{ | |||
| width: 20%; | |||
| height: 30px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: #f8f8f8; | |||
| border: 1px solid #ddd; | |||
| border-radius: 5px; | |||
| } | |||
| .weight_wrap, | |||
| .timer_wrap{ | |||
| padding: 10px; | |||
| border-top: 1px solid #ddd; | |||
| } | |||
| .timer_value, | |||
| .weight_value{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .value_item{ | |||
| margin: 0 5px; | |||
| padding: 4px 6px; | |||
| border: 1px solid #eee; | |||
| min-width: 60px; | |||
| text-align: center; | |||
| } | |||
| .btn_wrap{ | |||
| padding-top: 10px; | |||
| } | |||
| .btn_unitList_title{ | |||
| padding: 10px; | |||
| } | |||
| .btn_unitList{ | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| align-items: center; | |||
| justify-content: space-around; | |||
| } | |||
| .btn_unit{ | |||
| width: 20%; | |||
| height: 40px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: #f8f8f8; | |||
| border: 1px solid #ddd; | |||
| border-radius: 5px; | |||
| margin: 10px 0; | |||
| } | |||
| .btn_hover{ | |||
| background: rgba(0,0,0,0.1); | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| { | |||
| "description": "项目配置文件", | |||
| "packOptions": { | |||
| "ignore": [] | |||
| }, | |||
| "setting": { | |||
| "urlCheck": true, | |||
| "es6": true, | |||
| "enhance": false, | |||
| "postcss": true, | |||
| "preloadBackgroundData": false, | |||
| "minified": true, | |||
| "newFeature": false, | |||
| "coverView": true, | |||
| "nodeModules": false, | |||
| "autoAudits": false, | |||
| "showShadowRootInWxmlPanel": true, | |||
| "scopeDataCheck": false, | |||
| "uglifyFileName": false, | |||
| "checkInvalidKey": true, | |||
| "checkSiteMap": true, | |||
| "uploadWithSourceMap": true, | |||
| "compileHotReLoad": false, | |||
| "useMultiFrameRuntime": false, | |||
| "useApiHook": true, | |||
| "babelSetting": { | |||
| "ignore": [], | |||
| "disablePlugins": [], | |||
| "outputPath": "" | |||
| }, | |||
| "enableEngineNative": false, | |||
| "bundle": false, | |||
| "useIsolateContext": true, | |||
| "useCompilerModule": true, | |||
| "userConfirmedUseCompilerModuleSwitch": false, | |||
| "userConfirmedBundleSwitch": false, | |||
| "packNpmManually": false, | |||
| "packNpmRelationList": [], | |||
| "minifyWXSS": true | |||
| }, | |||
| "compileType": "miniprogram", | |||
| "libVersion": "2.14.1", | |||
| "appid": "wx942afd64e1113ff3", | |||
| "projectname": "aifresh_demo", | |||
| "debugOptions": { | |||
| "hidedInDevtools": [] | |||
| }, | |||
| "scripts": {}, | |||
| "isGameTourist": false, | |||
| "simulatorType": "wechat", | |||
| "simulatorPluginLibVersion": {}, | |||
| "condition": { | |||
| "search": { | |||
| "list": [] | |||
| }, | |||
| "conversation": { | |||
| "list": [] | |||
| }, | |||
| "game": { | |||
| "list": [] | |||
| }, | |||
| "plugin": { | |||
| "list": [] | |||
| }, | |||
| "gamePlugin": { | |||
| "list": [] | |||
| }, | |||
| "miniprogram": { | |||
| "list": [] | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| { | |||
| "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||
| "rules": [{ | |||
| "action": "allow", | |||
| "page": "*" | |||
| }] | |||
| } | |||
| @@ -0,0 +1,3 @@ | |||
| module.exports = { | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| //app.js | |||
| App({ | |||
| onLaunch: function () { | |||
| } | |||
| }) | |||
| @@ -0,0 +1,20 @@ | |||
| { | |||
| "pages":[ | |||
| "pages/index/index" | |||
| ], | |||
| "window": { | |||
| "navigationBarBackgroundColor": "#0082FE", | |||
| "navigationBarTextStyle": "white", | |||
| "navigationBarTitleText": "AiLink蓝牙连接Demo", | |||
| "backgroundColor": "#eeeeee", | |||
| "backgroundTextStyle": "light" | |||
| }, | |||
| "plugins": { | |||
| "sdkPlugin": { | |||
| "version": "1.1.0", | |||
| "provider": "wx17e93aad47cdae1a" | |||
| } | |||
| }, | |||
| "style": "v2", | |||
| "sitemapLocation": "sitemap.json" | |||
| } | |||
| @@ -0,0 +1,2 @@ | |||
| /**app.wxss**/ | |||
| @@ -0,0 +1,313 @@ | |||
| const plugin = requirePlugin("sdkPlugin").AiLink; | |||
| function inArray(arr, key, val) { | |||
| for (let i = 0; i < arr.length; i++) { | |||
| if (arr[i][key] === val) { | |||
| return i; | |||
| } | |||
| } | |||
| return -1; | |||
| } | |||
| // ArrayBuffer转16进度字符串示例 | |||
| function ab2hex(buffer) { | |||
| var hexArr = Array.prototype.map.call( | |||
| new Uint8Array(buffer), | |||
| function (bit) { | |||
| return ('00' + bit.toString(16)).slice(-2) | |||
| } | |||
| ) | |||
| return hexArr.join(','); | |||
| } | |||
| Page({ | |||
| data: { | |||
| showWriteInput:false, | |||
| devices: [ | |||
| // { | |||
| // deviceId: "02:03:04:05:06:07", | |||
| // name: "elink", | |||
| // localName: "elink", | |||
| // mac: "02:03:04:05:06:07", | |||
| // RSSI: -69, | |||
| // advertisServiceUUIDs:[ | |||
| // "FFE0","FFC0" | |||
| // ], | |||
| // analyzeDataText:"0102003241123413" | |||
| // } | |||
| ], | |||
| connected: false, | |||
| chs: [], | |||
| }, | |||
| onLoad: function () { | |||
| }, | |||
| openBluetoothAdapter() { | |||
| wx.openBluetoothAdapter({ | |||
| success: (res) => { | |||
| console.log('openBluetoothAdapter success', res) | |||
| this.startBluetoothDevicesDiscovery() | |||
| }, | |||
| fail: (res) => { | |||
| if (res.errCode === 10001) { | |||
| wx.showToast({ | |||
| title: '请打开蓝牙', | |||
| icon:"none" | |||
| }) | |||
| wx.onBluetoothAdapterStateChange(function (res) { | |||
| console.log('onBluetoothAdapterStateChange', res) | |||
| if (res.available) { | |||
| this.startBluetoothDevicesDiscovery() | |||
| } | |||
| }) | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| getBluetoothAdapterState() { | |||
| wx.getBluetoothAdapterState({ | |||
| success: (res) => { | |||
| console.log('getBluetoothAdapterState', res) | |||
| if (res.discovering) { | |||
| this.onBluetoothDeviceFound() | |||
| } else if (res.available) { | |||
| this.startBluetoothDevicesDiscovery() | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| startBluetoothDevicesDiscovery() { | |||
| if (this._discoveryStarted) { | |||
| return | |||
| } | |||
| this._discoveryStarted = true | |||
| wx.startBluetoothDevicesDiscovery({ | |||
| allowDuplicatesKey: true, | |||
| services: [ | |||
| "FFE0", | |||
| "F0A0", | |||
| ], | |||
| success: (res) => { | |||
| console.log('startBluetoothDevicesDiscovery success', res) | |||
| this.onBluetoothDeviceFound() | |||
| }, | |||
| }) | |||
| }, | |||
| stopBluetoothDevicesDiscovery() { | |||
| wx.stopBluetoothDevicesDiscovery() | |||
| }, | |||
| onBluetoothDeviceFound() { | |||
| wx.onBluetoothDeviceFound((res) => { | |||
| res.devices.forEach(device => { | |||
| if (!device.name && !device.localName) { | |||
| return | |||
| } | |||
| const foundDevices = this.data.devices | |||
| const idx = inArray(foundDevices, 'deviceId', device.deviceId) | |||
| const data = {} | |||
| console.log(device) | |||
| // console.log(ab2hex(device.advertisData)) | |||
| if(device.advertisServiceUUIDs[0].indexOf("F0A0") !== -1){ | |||
| let parseDataRes = plugin.parseBroadcastData(device.advertisData) | |||
| console.log(parseDataRes) | |||
| if(parseDataRes.status == 1) { | |||
| let analyzeData = plugin.analyzeBroadcastScaleData(parseDataRes) | |||
| console.log(analyzeData) | |||
| device.analyzeDataText = analyzeData.text | |||
| } | |||
| }else{ | |||
| let buff = device.advertisData.slice(-6) | |||
| device.mac = new Uint8Array(buff) | |||
| } | |||
| if (idx === -1) { | |||
| data[`devices[${foundDevices.length}]`] = device | |||
| } else { | |||
| data[`devices[${idx}]`] = device | |||
| } | |||
| this.setData(data) | |||
| }) | |||
| }) | |||
| }, | |||
| createBLEConnection(e) { | |||
| const ds = e.currentTarget.dataset | |||
| const index = ds.index | |||
| this._device = this.data.devices[index] | |||
| console.log(this._device) | |||
| const deviceId = ds.deviceId | |||
| const name = ds.name | |||
| this.mac = ds.mac | |||
| wx.createBLEConnection({ | |||
| deviceId, | |||
| success: (res) => { | |||
| this.setData({ | |||
| connected: true, | |||
| name, | |||
| deviceId, | |||
| }) | |||
| console.log("createBLEConnection:success") | |||
| wx.stopBluetoothDevicesDiscovery() | |||
| this.getBLEDeviceServices(deviceId) | |||
| } | |||
| }) | |||
| this.stopBluetoothDevicesDiscovery() | |||
| }, | |||
| closeBLEConnection() { | |||
| wx.closeBLEConnection({ | |||
| deviceId: this._deviceId | |||
| }) | |||
| this.setData({ | |||
| connected: false, | |||
| chs: [], | |||
| }) | |||
| }, | |||
| getBLEDeviceServices(deviceId) { | |||
| wx.getBLEDeviceServices({ | |||
| deviceId, | |||
| success: (res) => { | |||
| for (let i = 0; i < res.services.length; i++) { | |||
| if (res.services[i].isPrimary) { | |||
| this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid) | |||
| return | |||
| } | |||
| } | |||
| } | |||
| }) | |||
| }, | |||
| getBLEDeviceCharacteristics(deviceId, serviceId) { | |||
| const _this = this | |||
| this._deviceId = deviceId | |||
| this._serviceId = serviceId | |||
| this._device.serviceId = serviceId | |||
| wx.getBLEDeviceCharacteristics({ | |||
| deviceId, | |||
| serviceId, | |||
| success: (res) => { | |||
| console.log('getBLEDeviceCharacteristics success', res.characteristics) | |||
| // let uuid1, uuid2, uuid3; | |||
| for (let i = 0; i < res.characteristics.length; i++) { | |||
| let item = res.characteristics[i] | |||
| if (item.uuid.indexOf('0000FFE1') != -1) { | |||
| this.uuid1 = item.uuid //下发数据 | |||
| } else if (item.uuid.indexOf('0000FFE2') != -1) { | |||
| this.uuid2 = item.uuid //监听数据 | |||
| } else if (item.uuid.indexOf('0000FFE3') != -1) { | |||
| this.uuid3 = item.uuid //写入设置 | |||
| } | |||
| } | |||
| // 打开监听 | |||
| wx.notifyBLECharacteristicValueChange({ | |||
| deviceId, | |||
| serviceId, | |||
| characteristicId: this.uuid2, | |||
| state: true, | |||
| }) | |||
| wx.notifyBLECharacteristicValueChange({ | |||
| deviceId, | |||
| serviceId, | |||
| characteristicId: this.uuid3, | |||
| state: true, | |||
| }) | |||
| plugin.initPlugin(res.characteristics, this._device) | |||
| wx.onBLECharacteristicValueChange((characteristic) => { | |||
| let bleData = plugin.parseBleData(characteristic.value) | |||
| if (bleData.status == 0) { | |||
| console.log("握手成功") | |||
| } else if (bleData.status == 1) { | |||
| console.log(bleData) | |||
| let parseData = bleData.data //对应协议中的payload数据,可以自行解析该数据 | |||
| console.log(ab2hex(parseData)) | |||
| console.log(ab2hex(bleData.completeData)) | |||
| const idx = inArray(this.data.chs, 'uuid', characteristic.characteristicId) | |||
| const data = {} | |||
| if (idx === -1) { | |||
| data[`chs[${this.data.chs.length}]`] = { | |||
| uuid: characteristic.characteristicId, | |||
| value: ab2hex(parseData) | |||
| } | |||
| } else { | |||
| data[`chs[${idx}]`] = { | |||
| uuid: characteristic.characteristicId, | |||
| value: ab2hex(parseData) | |||
| } | |||
| } | |||
| this.setData(data) | |||
| } else { | |||
| console.log(bleData) | |||
| } | |||
| }) | |||
| }, | |||
| fail(res) { | |||
| console.error('getBLEDeviceCharacteristics', res) | |||
| } | |||
| }) | |||
| }, | |||
| writeBLECharacteristicValue(buffer, uuid, deviceId, serviceId) { | |||
| // 向蓝牙设备发送一个二进制流数据 | |||
| wx.writeBLECharacteristicValue({ | |||
| deviceId, | |||
| serviceId, | |||
| characteristicId: uuid, | |||
| value: buffer, | |||
| success(res) { | |||
| console.log('writeBLECharacteristicValue success', res) | |||
| console.log(ab2hex(buffer)) | |||
| } | |||
| }) | |||
| }, | |||
| closeBluetoothAdapter() { | |||
| wx.closeBluetoothAdapter() | |||
| this._discoveryStarted = false | |||
| }, | |||
| showWriteInputView(){ | |||
| this.setData({ | |||
| showWriteInput: true | |||
| }) | |||
| }, | |||
| hideWriteInputView(){ | |||
| this.setData({ | |||
| showWriteInput: false | |||
| }) | |||
| }, | |||
| writingCmd(e){ | |||
| this._cmd = e.detail.value | |||
| }, | |||
| submitCmd(){ | |||
| if(!this._cmd){ | |||
| return | |||
| } | |||
| let arr = [] | |||
| if(this._cmd.indexOf(",") == -1){ | |||
| arr = this._cmd.split(" ") | |||
| } else { | |||
| arr = this._cmd.split(",") | |||
| } | |||
| for(let i in arr){ | |||
| arr[i] = parseInt(arr[i],16) | |||
| } | |||
| // let arr = [ | |||
| // 0xA6, | |||
| // 0x01, | |||
| // 0x28, | |||
| // 0x29, | |||
| // 0x6A, | |||
| // //A6 01 28 29 6A | |||
| // ] | |||
| if(arr[0] == 0xA6){ | |||
| let payload = arr.slice(2,-2) | |||
| plugin.sendDataOfA6(payload) | |||
| } else if(arr[0] == 0xA7) { | |||
| let cid = [arr[1], arr[2]] // 001E | |||
| let payload = arr.slice(4,-2) | |||
| plugin.sendDataOfA7(payload) | |||
| // this.writeBLECharacteristicValue(buff, this.uuid1, this._deviceId, this._serviceId) | |||
| } | |||
| return | |||
| }, | |||
| }); | |||
| @@ -0,0 +1,5 @@ | |||
| { | |||
| "usingComponents": { | |||
| }, | |||
| "navigationBarTitleText":"AiLink" | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| <wxs module="utils"> | |||
| module.exports.max = function(n1, n2) { | |||
| return Math.max(n1, n2) | |||
| } | |||
| module.exports.len = function(arr) { | |||
| arr = arr || [] | |||
| return arr.length | |||
| } | |||
| </wxs> | |||
| <button bindtap="openBluetoothAdapter">开始扫描</button> | |||
| <button bindtap="stopBluetoothDevicesDiscovery">停止扫描</button> | |||
| <button bindtap="closeBluetoothAdapter">结束流程</button> | |||
| <view class="devices_summary">已发现 {{devices.length}} 个外围设备:</view> | |||
| <scroll-view class="device_list" scroll-y scroll-with-animation> | |||
| <view wx:for="{{devices}}" wx:key="index" | |||
| data-device-id="{{item.deviceId}}" | |||
| data-name="{{item.name || item.localName}}" | |||
| data-mac="{{item.mac}}" | |||
| data-index="{{index}}" | |||
| bindtap="createBLEConnection" | |||
| class="device_item" | |||
| hover-class="device_item_hover"> | |||
| <view style="font-size: 16px; color: #333;">{{item.name}}</view> | |||
| <view style="font-size: 10px">信号强度: {{item.RSSI}}dBm ({{utils.max(0, item.RSSI + 100)}}%)</view> | |||
| <view style="font-size: 10px">mac地址: {{item.deviceId}}</view> | |||
| <!-- <view style="font-size: 10px">Service数量: {{utils.len(item.advertisServiceUUIDs)}}</view> --> | |||
| <view style="font-size: 14px">广播数据:{{item.analyzeDataText}}</view> | |||
| </view> | |||
| </scroll-view> | |||
| <view class="connected_info" wx:if="{{connected}}"> | |||
| <view> | |||
| <text>已连接到 {{name}}</text> | |||
| <view class="operation"> | |||
| <!-- <button wx:if="{{canWrite}}" size="mini" bindtap="writeBLECharacteristicValue">写数据</button> --> | |||
| <button size="mini" bindtap="showWriteInputView">写入指令</button> | |||
| <button size="mini" bindtap="closeBLEConnection">断开连接</button> | |||
| </view> | |||
| </view> | |||
| <view wx:for="{{chs}}" wx:key="index" style="font-size: 12px; margin-top: 10px;"> | |||
| <view>特性值: {{item.value}}</view> | |||
| </view> | |||
| </view> | |||
| <view class="writeInputViewBg" wx:if="{{showWriteInput}}"></view> | |||
| <view class="writeInputView" wx:if="{{showWriteInput}}"> | |||
| <view class="writeInput"> | |||
| <input bindinput="writingCmd"></input> | |||
| </view> | |||
| <view class="hint">请输入16进制数,Byte之间用空格或英文逗号分隔</view> | |||
| <view class="btns"> | |||
| <button size="mini" bindtap="submitCmd">确定</button> | |||
| <button size="mini" bindtap="hideWriteInputView">取消</button> | |||
| </view> | |||
| </view> | |||
| @@ -0,0 +1,86 @@ | |||
| page { | |||
| color: #333; | |||
| } | |||
| .devices_summary { | |||
| margin-top: 30px; | |||
| padding: 10px; | |||
| font-size: 16px; | |||
| } | |||
| .device_list { | |||
| height: 300px; | |||
| margin: 50px 5px; | |||
| margin-top: 0; | |||
| border: 1px solid #EEE; | |||
| border-radius: 5px; | |||
| width: auto; | |||
| } | |||
| .device_item { | |||
| border-bottom: 1px solid #EEE; | |||
| padding: 10px; | |||
| color: #666; | |||
| } | |||
| .device_item_hover { | |||
| background-color: rgba(0, 0, 0, .1); | |||
| } | |||
| .connected_info { | |||
| position: fixed; | |||
| bottom: 0; | |||
| width: 100%; | |||
| background-color: #F0F0F0; | |||
| padding: 10px; | |||
| padding-bottom: 20px; | |||
| margin-bottom: env(safe-area-inset-bottom); | |||
| font-size: 14px; | |||
| min-height: 100px; | |||
| box-shadow: 0px 0px 3px 0px; | |||
| } | |||
| .connected_info .operation { | |||
| position: absolute; | |||
| display: inline-block; | |||
| right: 30px; | |||
| } | |||
| .writeInputViewBg{ | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| z-index: 1000; | |||
| background-color: rgba(0,0,0,0.5); | |||
| } | |||
| .writeInputView{ | |||
| position: fixed; | |||
| top: 30%; | |||
| left: 50%; | |||
| z-index: 1001; | |||
| background-color: #fff; | |||
| width: 80vw; | |||
| height: 300rpx; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| transform: translateX(-50%); | |||
| } | |||
| .writeInput{ | |||
| margin-top: 60rpx; | |||
| width: 90%; | |||
| height: 60rpx; | |||
| border: 1rpx solid #ccc; | |||
| } | |||
| .writeInput input{ | |||
| width: 100%; | |||
| height: 100%; | |||
| box-sizing: border-box; | |||
| } | |||
| .hint{ | |||
| padding-top: 10rpx; | |||
| font-size: 24rpx; | |||
| color: #999; | |||
| } | |||
| .btns{ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| width: 60%; | |||
| margin-top: 50rpx; | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| { | |||
| "description": "项目配置文件", | |||
| "packOptions": { | |||
| "ignore": [] | |||
| }, | |||
| "setting": { | |||
| "urlCheck": true, | |||
| "es6": true, | |||
| "enhance": false, | |||
| "postcss": true, | |||
| "preloadBackgroundData": false, | |||
| "minified": true, | |||
| "newFeature": false, | |||
| "coverView": true, | |||
| "nodeModules": false, | |||
| "autoAudits": false, | |||
| "showShadowRootInWxmlPanel": true, | |||
| "scopeDataCheck": false, | |||
| "uglifyFileName": false, | |||
| "checkInvalidKey": true, | |||
| "checkSiteMap": true, | |||
| "uploadWithSourceMap": true, | |||
| "compileHotReLoad": false, | |||
| "useMultiFrameRuntime": false, | |||
| "useApiHook": true, | |||
| "babelSetting": { | |||
| "ignore": [], | |||
| "disablePlugins": [], | |||
| "outputPath": "" | |||
| }, | |||
| "enableEngineNative": false, | |||
| "bundle": false, | |||
| "useIsolateContext": true, | |||
| "useCompilerModule": true, | |||
| "userConfirmedUseCompilerModuleSwitch": false, | |||
| "userConfirmedBundleSwitch": false, | |||
| "packNpmManually": false, | |||
| "packNpmRelationList": [], | |||
| "minifyWXSS": true | |||
| }, | |||
| "compileType": "miniprogram", | |||
| "libVersion": "2.14.1", | |||
| "appid": "wx942afd64e1113ff3", | |||
| "projectname": "ailink_demo", | |||
| "debugOptions": { | |||
| "hidedInDevtools": [] | |||
| }, | |||
| "scripts": {}, | |||
| "isGameTourist": false, | |||
| "simulatorType": "wechat", | |||
| "simulatorPluginLibVersion": {}, | |||
| "condition": { | |||
| "search": { | |||
| "list": [] | |||
| }, | |||
| "conversation": { | |||
| "list": [] | |||
| }, | |||
| "game": { | |||
| "list": [] | |||
| }, | |||
| "plugin": { | |||
| "list": [] | |||
| }, | |||
| "gamePlugin": { | |||
| "list": [] | |||
| }, | |||
| "miniprogram": { | |||
| "list": [] | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| { | |||
| "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||
| "rules": [{ | |||
| "action": "allow", | |||
| "page": "*" | |||
| }] | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| module.exports = { | |||
| } | |||