| //app.js | |||||
| App({ | |||||
| onLaunch: function () { | |||||
| }, | |||||
| }) |
| { | |||||
| "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" | |||||
| } |
| /**app.wxss**/ | |||||
| // 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 | |||||
| }, | |||||
| }); |
| { | |||||
| "usingComponents": {}, | |||||
| "navigationBarTitleText":"AiFresh" | |||||
| } |
| <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> |
| 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); | |||||
| } |
| { | |||||
| "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": [] | |||||
| } | |||||
| } | |||||
| } |
| { | |||||
| "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||||
| "rules": [{ | |||||
| "action": "allow", | |||||
| "page": "*" | |||||
| }] | |||||
| } |
| module.exports = { | |||||
| } |
| //app.js | |||||
| App({ | |||||
| onLaunch: function () { | |||||
| } | |||||
| }) |
| { | |||||
| "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" | |||||
| } |
| /**app.wxss**/ | |||||
| 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 | |||||
| }, | |||||
| }); |
| { | |||||
| "usingComponents": { | |||||
| }, | |||||
| "navigationBarTitleText":"AiLink" | |||||
| } |
| <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> |
| 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; | |||||
| } |
| { | |||||
| "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": [] | |||||
| } | |||||
| } | |||||
| } |
| { | |||||
| "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||||
| "rules": [{ | |||||
| "action": "allow", | |||||
| "page": "*" | |||||
| }] | |||||
| } |
| module.exports = { | |||||
| } |