//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 = { | |||||
} |